[
  {
    "path": ".githooks/pre-commit",
    "content": "just check\n"
  },
  {
    "path": ".github/workflows/publish.yml",
    "content": "name: publish\n\non:\n  push:\n    tags:\n      - 'v*'\n\njobs:\n  check:\n    runs-on: ubuntu-latest\n    outputs:\n      should_publish: ${{ steps.check_commit.outputs.should_publish }}\n    steps:\n      - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1\n      - name: Check commit message\n        id: check_commit\n        run: |\n          commit_msg=$(git log -1 --pretty=%B)\n          if [[ $commit_msg =~ ^chore\\(hyprshell.*\\):\\ release ]]; then\n            echo \"should_publish=true\" >> $GITHUB_OUTPUT\n          else\n            echo \"Error: Commit message must start with 'chore(hyprshell): release'\"\n            echo \"should_publish=false\" >> $GITHUB_OUTPUT\n          fi\n\n  publish:\n    needs: check\n    runs-on: ubuntu-latest\n    container:\n      image: ghcr.io/h3rmt/actions-image:latest\n      options: --privileged\n    if: needs.check.outputs.should_publish == 'true'\n    steps:\n      - run: rustup default stable\n      - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1\n        with:\n          fetch-depth: 0\n          ssh-key: ${{ secrets.SSH_KEY }}\n      - name: Install gtk4-layer-shell\n        run: ./scripts/ci/install-gtk4-layer-shell.sh\n      - name: Install cargo-workspaces\n        run: |\n          mkdir -p $HOME/.cargo/bin\n          cargo install cargo-workspaces\n          cargo ws --version\n      - name: Publish to crates.io\n        run: |\n          set -euxo pipefail\n          \n          git config user.name \"github-actions[bot]\"\n          git config user.email \"github-actions[bot]@users.noreply.github.com\"\n          \n          git fetch --tags origin hyprshell-release\n          git checkout ${{ github.ref }} -b hyprshell-release\n          git log -1 --pretty=%B\n          git push origin HEAD:hyprshell-release --force\n          git switch hyprshell-release\n          \n          current_sha=$(git rev-parse HEAD)\n          jq --arg sha \"$current_sha\" '.[\"last-release-sha\"]=$sha' release-please-config.json > tmp.json\n          mv tmp.json release-please-config.json\n          git add release-please-config.json\n          git commit -m \"chore(release): update last-release-sha\"\n          git push origin hyprshell-release\n          \n          git tag --delete ${{ github.ref_name }}\n          git push --delete origin ${{ github.ref_name }}\n          \n          version=$(echo ${{ github.ref_name }} | sed 's/v//')\n          cargo ws publish custom $version -m \"chore(release): bump versions to $version\" --no-individual-tags --allow-branch hyprshell-release --exact --yes --force \"hyprshell*\" --token ${{ secrets.CRATES_TOKEN }}\n          \n          # push current branch to hyprshell branch (for development)\n          git push origin HEAD:hyprshell-release\n          \n          gh release edit ${{ github.ref_name }} --latest --draft=false --title \"hyprshell ${{ github.ref_name }}\"\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      - name: Publish AUR package\n        uses: KSXGitHub/github-actions-deploy-aur@2ac5a4c1d7035885d46b10e3193393be8460b6f1 # v4.1.1\n        with:\n          pkgname: hyprshell\n          pkgbuild: ./packaging/pkgbuild/PKGBUILD\n          commit_username: ${{ secrets.AUR_USERNAME }}\n          commit_email: ${{ secrets.AUR_EMAIL }}\n          ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }}\n          commit_message: Update hyprshell AUR package\n          updpkgsums: true\n          ssh_keyscan_types: rsa,ecdsa,ed25519\n      - name: Publish AUR slim package\n        uses: KSXGitHub/github-actions-deploy-aur@2ac5a4c1d7035885d46b10e3193393be8460b6f1 # v4.1.1\n        with:\n          pkgname: hyprshell-slim\n          pkgbuild: ./packaging/pkgbuild/PKGBUILD-slim\n          commit_username: ${{ secrets.AUR_USERNAME }}\n          commit_email: ${{ secrets.AUR_EMAIL }}\n          ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }}\n          commit_message: Update hyprshell-slim AUR package\n          updpkgsums: true\n          ssh_keyscan_types: rsa,ecdsa,ed25519\n      - name: Build binary for x86_64-unknown-linux-gnu\n        run: |\n          version=$(echo ${{ github.ref_name }} | sed 's/v//')\n          ./scripts/ci/build-x86.sh\n          mv \"/tmp/hyprshell-x86_64.tar.zst\" \"/tmp/hyprshell-$version-x86_64.tar.zst\"\n          gh release upload ${{ github.ref_name }} \"/tmp/hyprshell-$version-x86_64.tar.zst\"\n          mv \"/tmp/hyprshell-slim-x86_64.tar.zst\" \"/tmp/hyprshell-slim-$version-x86_64.tar.zst\"\n          gh release upload ${{ github.ref_name }} \"/tmp/hyprshell-slim-$version-x86_64.tar.zst\"\n          cargo clean\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      - name: Build binary for aarch64-unknown-linux-gnu\n        run: |\n          version=$(echo ${{ github.ref_name }} | sed 's/v//')\n          ./scripts/ci/install-gtk4-layer-shell-aarch64.sh\n          ./scripts/ci/build-aarch64.sh\n          mv \"/tmp/hyprshell-aarch64.tar.zst\" \"/tmp/hyprshell-$version-aarch64.tar.zst\"\n          gh release upload ${{ github.ref_name }} \"/tmp/hyprshell-$version-aarch64.tar.zst\"\n          mv \"/tmp/hyprshell-slim-aarch64.tar.zst\" \"/tmp/hyprshell-slim-$version-aarch64.tar.zst\"\n          gh release upload ${{ github.ref_name }} \"/tmp/hyprshell-slim-$version-aarch64.tar.zst\"\n          cargo clean\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      - name: Publish AUR bin package\n        uses: KSXGitHub/github-actions-deploy-aur@2ac5a4c1d7035885d46b10e3193393be8460b6f1 # v4.1.1\n        with:\n          pkgname: hyprshell-bin\n          pkgbuild: ./packaging/pkgbuild/PKGBUILD-bin\n          commit_username: ${{ secrets.AUR_USERNAME }}\n          commit_email: ${{ secrets.AUR_EMAIL }}\n          ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }}\n          commit_message: Update hyprshell-bin AUR package\n          updpkgsums: true\n          ssh_keyscan_types: rsa,ecdsa,ed25519\n      - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16\n        with:\n          name: hyprshell\n          extraPullNames: hyprshell-dev, hyprland\n          authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}\n      - name: Run Nix Flake Build\n        run: nix build '.#packages.x86_64-linux.hyprshell' -L\n"
  },
  {
    "path": ".github/workflows/release-please.yml",
    "content": "name: release-please\n\non:\n  push:\n    branches:\n      - 'hyprshell'\n      - 'hyprshell-patch-**'\n\njobs:\n  release-please:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: googleapis/release-please-action@16a9c90856f42705d54a6fda1823352bdc62cf38 # v4.4.0\n        with:\n          token: ${{ secrets.RELEASE_PLEASE_TOKEN }}\n          config-file: release-please-config.json\n          manifest-file: .release-please-manifest.json\n          target-branch: ${{ github.ref_name }}"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: test\n\non:\n  push:\n    branches-ignore:\n      - 'release-please-**'\n      - 'hyprshell-release'\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}\n  cancel-in-progress: true\n\njobs:\n  check-nix:\n    name: Run Nix Flake Check\n    runs-on: \"ubuntu-latest\"\n    container:\n      image: ghcr.io/h3rmt/actions-image:latest\n      options: --privileged\n    steps:\n      - run: rustup default stable\n      - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1\n      - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16\n        with:\n          name: hyprshell-dev\n          extraPullNames: hyprland\n          pushFilter: \"hyprshell-(nextest|clippy|fmt|check-all-feature-combinations|check-nix-configs).*\"\n          authToken: ${{ secrets.CACHIX_AUTH_TOKEN_DEV }}\n      - name: Run Nix Flake Build deps\n        run: nix build '.#checks.x86_64-linux.hyprshell-build-deps' -L\n      - name: Run Nix Flake Check build\n        run: nix build '.#checks.x86_64-linux.hyprshell-config-check' -L\n      - name: Run Nix Flake Check clippy\n        run: nix build '.#checks.x86_64-linux.hyprshell-clippy' -L\n      - name: Run Nix Flake Check fmt\n        run: nix build '.#checks.x86_64-linux.hyprshell-fmt' -L\n      - name: Run Nix Flake Check test\n        run: nix build '.#checks.x86_64-linux.hyprshell-test' -L\n      - name: Run Nix Flake Check check-nix-configs\n        run: nix build '.#checks.x86_64-linux.hyprshell-check-nix-configs' -L\n      - name: Run Nix Flake Check check-all-feature-combinations\n        run: nix build '.#checks.x86_64-linux.hyprshell-check-all-feature-combinations' -L\n\n  check:\n    name: Run build tests\n    runs-on: \"ubuntu-latest\"\n    container:\n      image: ghcr.io/h3rmt/actions-image:latest\n      options: --privileged\n    if: github.ref == 'refs/heads/hyprshell'\n    steps:\n      - run: rustup default stable\n      - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1\n      - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1\n        with:\n          prefix-key: hyprshell\n          cache-on-failure: true\n      - name: Build binary for x86_64-unknown-linux-gnu\n        run: |\n          ./scripts/ci/install-gtk4-layer-shell.sh\n          ./scripts/ci/build-x86.sh\n      - name: Build binary for aarch64-unknown-linux-gnu\n        run: |\n          ./scripts/ci/install-gtk4-layer-shell-aarch64.sh\n          ./scripts/ci/build-aarch64.sh\n\n"
  },
  {
    "path": ".github/workflows/update.yml",
    "content": "name: update flake.lock\n\non:\n  workflow_dispatch: # allows manual triggering\n  schedule:\n    - cron: '30 2 1,15 * *' # At 02:30 on every 14th day-of-month.\n\njobs:\n  nix-flake-update:\n    permissions:\n      contents: write\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1\n      - uses: cachix/install-nix-action@0b0e072294b088b73964f1d72dfdac0951439dbd # v31.8.4\n        with:\n          github_access_token: ${{ secrets.GITHUB_TOKEN }}\n      - name: Update flake\n        run: |\n          git fetch\n          git switch hyprshell\n          nix flake update\n          git config user.name \"github-actions[bot]\"\n          git config user.email \"github-actions[bot]@users.noreply.github.com\"\n          git add flake.lock\n          git commit -m \"fix(nix-flake): update flake.lock\"\n          git push origin hyprshell\n"
  },
  {
    "path": ".gitignore",
    "content": "/target\n/*/target\n/result\n\n/test-data"
  },
  {
    "path": ".idea/.gitignore",
    "content": "# Default ignored files\n/shelf/\n/workspace.xml\n# Editor-based HTTP Client requests\n/httpRequests/\n# Datasource local storage ignored files\n/dataSources/\n/dataSources.local.xml\n"
  },
  {
    "path": ".idea/copilot.data.migration.agent.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"AgentMigrationStateService\">\n    <option name=\"migrationStatus\" value=\"COMPLETED\" />\n  </component>\n</project>"
  },
  {
    "path": ".idea/copilot.data.migration.ask.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"AskMigrationStateService\">\n    <option name=\"migrationStatus\" value=\"COMPLETED\" />\n  </component>\n</project>"
  },
  {
    "path": ".idea/copilot.data.migration.ask2agent.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"Ask2AgentMigrationStateService\">\n    <option name=\"migrationStatus\" value=\"COMPLETED\" />\n  </component>\n</project>"
  },
  {
    "path": ".idea/copilot.data.migration.edit.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"EditMigrationStateService\">\n    <option name=\"migrationStatus\" value=\"COMPLETED\" />\n  </component>\n</project>"
  },
  {
    "path": ".idea/file.template.settings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"ExportableFileTemplateSettings\">\n    <j2ee_templates>\n      <template name=\"qt.CMakeLists.txt\" reformat=\"true\" live-template-enabled=\"false\" enabled=\"false\" />\n    </j2ee_templates>\n  </component>\n</project>"
  },
  {
    "path": ".idea/hyprshell.iml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<module type=\"EMPTY_MODULE\" version=\"4\">\n  <component name=\"NewModuleRootManager\">\n    <content url=\"file://$MODULE_DIR$\">\n      <sourceFolder url=\"file://$MODULE_DIR$/gui-lib/src\" isTestSource=\"false\" />\n      <sourceFolder url=\"file://$MODULE_DIR$/src\" isTestSource=\"false\" />\n      <sourceFolder url=\"file://$MODULE_DIR$/test-global/src\" isTestSource=\"false\" />\n      <sourceFolder url=\"file://$MODULE_DIR$/crates/config-lib/src\" isTestSource=\"false\" />\n      <sourceFolder url=\"file://$MODULE_DIR$/crates/core-lib/src\" isTestSource=\"false\" />\n      <sourceFolder url=\"file://$MODULE_DIR$/crates/exec-lib/src\" isTestSource=\"false\" />\n      <sourceFolder url=\"file://$MODULE_DIR$/crates/hyprland-plugin/src\" isTestSource=\"false\" />\n      <sourceFolder url=\"file://$MODULE_DIR$/crates/launcher-lib/src\" isTestSource=\"false\" />\n      <sourceFolder url=\"file://$MODULE_DIR$/crates/windows-lib/src\" isTestSource=\"false\" />\n      <sourceFolder url=\"file://$MODULE_DIR$/dep-crates/hyprland-rs/examples\" isTestSource=\"false\" />\n      <sourceFolder url=\"file://$MODULE_DIR$/dep-crates/hyprland-rs/hyprland-macros/src\" isTestSource=\"false\" />\n      <sourceFolder url=\"file://$MODULE_DIR$/dep-crates/hyprland-rs/src\" isTestSource=\"false\" />\n      <sourceFolder url=\"file://$MODULE_DIR$/crates/clipboard-lib/src\" isTestSource=\"false\" />\n      <sourceFolder url=\"file://$MODULE_DIR$/dep-crates/wl-clipboard-rs/src\" isTestSource=\"false\" />\n      <sourceFolder url=\"file://$MODULE_DIR$/dep-crates/wl-clipboard-rs/wl-clipboard-rs-tools/src\" isTestSource=\"false\" />\n      <sourceFolder url=\"file://$MODULE_DIR$/crates/config-edit-lib/src\" isTestSource=\"false\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/config\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/gui-lib/target\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/target\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/data\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/test-global\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/result\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/crates/core-lib/target\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/crates/exec-lib/target\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/crates/hyprland-plugin/Hyprland\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/crates/launcher-lib/target\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/crates/windows-lib/target\" />\n      <excludeFolder url=\"file://$MODULE_DIR$/dep-crates/hyprland-rs/target\" />\n    </content>\n    <orderEntry type=\"inheritedJdk\" />\n    <orderEntry type=\"sourceFolder\" forTests=\"false\" />\n  </component>\n</module>"
  },
  {
    "path": ".idea/inspectionProfiles/Project_Default.xml",
    "content": "<component name=\"InspectionProjectProfileManager\">\n  <profile version=\"1.0\">\n    <option name=\"myName\" value=\"Project Default\" />\n    <inspection_tool class=\"DuplicatedCode\" enabled=\"false\" level=\"WEAK WARNING\" enabled_by_default=\"false\" />\n    <inspection_tool class=\"RsSortImplTraitMembers\" enabled=\"false\" level=\"WEAK WARNING\" enabled_by_default=\"false\" />\n  </profile>\n</component>"
  },
  {
    "path": ".idea/modules.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"ProjectModuleManager\">\n    <modules>\n      <module fileurl=\"file://$PROJECT_DIR$/.idea/hyprshell.iml\" filepath=\"$PROJECT_DIR$/.idea/hyprshell.iml\" />\n    </modules>\n  </component>\n</project>"
  },
  {
    "path": ".idea/rust.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"RsVcsConfiguration\">\n    <option name=\"rustFmt\" value=\"true\" />\n  </component>\n</project>"
  },
  {
    "path": ".idea/vcs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"VcsDirectoryMappings\">\n    <mapping directory=\"$PROJECT_DIR$\" vcs=\"Git\" />\n  </component>\n</project>"
  },
  {
    "path": ".release-please-manifest.json",
    "content": "{\n  \".\": \"4.9.5\"\n}"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n## [4.9.5](https://github.com/H3rmt/hyprshell/compare/v4.9.4...v4.9.5) (2026-03-31)\n\n\n### Bug Fixes\n\n* use cpp26 for hyprland plugin ([6fcff3b](https://github.com/H3rmt/hyprshell/commit/6fcff3b3de906497f96648f53fe20522ea5db577))\n\n## [4.9.4](https://github.com/H3rmt/hyprshell/compare/v4.9.3...v4.9.4) (2026-03-02)\n\n\n### Bug Fixes\n\n* bundle multiple plugin versions to support wider range of hyprland versions ([95908e0](https://github.com/H3rmt/hyprshell/commit/95908e067293fd4ad4381f6b11bf923a69f96637))\n* fix use correct keyboard in plugin ([805bbb4](https://github.com/H3rmt/hyprshell/commit/805bbb44c9671b57fb633e90227bc69b318c685d))\n* update plugin to new hyprland plugin api ([6bfc01d](https://github.com/H3rmt/hyprshell/commit/6bfc01de8e0461119d2e4b5b0bc4b2202c36b4c9))\n\n## [4.9.3](https://github.com/H3rmt/hyprshell/compare/v4.9.2...v4.9.3) (2026-03-02)\n\n\n### Bug Fixes\n\n* bundle multiple plugin versions to support wider range of hyprland versions ([95908e0](https://github.com/H3rmt/hyprshell/commit/95908e067293fd4ad4381f6b11bf923a69f96637))\n* update plugin to new hyprland plugin api ([6bfc01d](https://github.com/H3rmt/hyprshell/commit/6bfc01de8e0461119d2e4b5b0bc4b2202c36b4c9))\n\n## [4.9.2](https://github.com/H3rmt/hyprshell/compare/v4.9.1...v4.9.2) (2026-01-06)\n\n\n### Bug Fixes\n\n* fix release ci workflow ([eada938](https://github.com/H3rmt/hyprshell/commit/eada9385f52846c6468891cb061c5a879fd16098))\n\n## [4.9.1](https://github.com/H3rmt/hyprshell/compare/v4.9.0...v4.9.1) (2026-01-06)\n\n\n### Bug Fixes\n\n* add websearch and actions configs ([923055e](https://github.com/H3rmt/hyprshell/commit/923055ea721fd01a3abce376564cb7b2f81449dd))\n* reduce image size ([923055e](https://github.com/H3rmt/hyprshell/commit/923055ea721fd01a3abce376564cb7b2f81449dd))\n\n## [4.9.0](https://github.com/H3rmt/hyprshell/compare/v4.8.3...v4.9.0) (2026-01-05)\n\n\n### Features\n\n* add generate page ([1f9cb52](https://github.com/H3rmt/hyprshell/commit/1f9cb52edf6f48c446b92ec3398e19fe3e5cb224))\n* add terminal settings and overview keyboard shortcut chooser to generate setup ([f7ae9bf](https://github.com/H3rmt/hyprshell/commit/f7ae9bf3d2b5769627faa2735fb7c6058a6de998))\n* added data file to themes ([9e13a4a](https://github.com/H3rmt/hyprshell/commit/9e13a4a3e2273cd9be56438f0b6515e3fa593c52))\n* added debug info command ([efa1010](https://github.com/H3rmt/hyprshell/commit/efa10109c0af9ed04912f1e2066632650d460494))\n* added theme chooser ([8deccc2](https://github.com/H3rmt/hyprshell/commit/8deccc2dba445411bca4f781b93d9ddab52ac942))\n* added theme settings to config editor ([178f60b](https://github.com/H3rmt/hyprshell/commit/178f60b975a85ec54840801d627bdfd0a1bfd0b3))\n* allow custom key for switch mode ([3d07d3c](https://github.com/H3rmt/hyprshell/commit/3d07d3ceac3f1233f58498e7725c34c867450016))\n* generate config from gui ([c6237a2](https://github.com/H3rmt/hyprshell/commit/c6237a25ef6d0b676aedd48aa5260fd12e6b39f8))\n* retry getting version for 40 times ([9207c2a](https://github.com/H3rmt/hyprshell/commit/9207c2a9485211b3ba8c86c5e15cac35ca5e7c1d))\n\n\n### Bug Fixes\n\n* **deps:** update rust crate ron to 0.12.0 ([1c6038a](https://github.com/H3rmt/hyprshell/commit/1c6038a3f546d56da6ce18a2cccdeaedc7fdd90b))\n* fix bin package for aur ([4906ccb](https://github.com/H3rmt/hyprshell/commit/4906ccb8344d3022298d225841276e89b703702a))\n* fix bin package for aur ([22a9b3b](https://github.com/H3rmt/hyprshell/commit/22a9b3b33e30ce810da9a806bbc61418f85537d9))\n* fix systemd unit generation ([4318bf5](https://github.com/H3rmt/hyprshell/commit/4318bf5442ac08dfde638caa97417d5ad67bd5c9))\n* **nix-flake:** update flake.lock ([54fb1d0](https://github.com/H3rmt/hyprshell/commit/54fb1d06a1e63a3abc3004caae96a306a0cdd98d))\n* **nix-flake:** update flake.lock ([cfe263a](https://github.com/H3rmt/hyprshell/commit/cfe263a09189d92c218f2223d6b4552200777410))\n* **nix-flake:** update flake.lock ([66e4a85](https://github.com/H3rmt/hyprshell/commit/66e4a85b98b06febd33036e8f14ed12bff923941))\n* update hyprland plugin ([3d07d3c](https://github.com/H3rmt/hyprshell/commit/3d07d3ceac3f1233f58498e7725c34c867450016))\n\n\n### Code Refactoring\n\n* add remaining plugin options ([ad9359f](https://github.com/H3rmt/hyprshell/commit/ad9359f6fb99042dbc94d229a343a5dffb789186))\n* fix clippy fixes ([219f14c](https://github.com/H3rmt/hyprshell/commit/219f14c2d857975cdd6f9f6149df6085956e39ef))\n* use justfile ([c6237a2](https://github.com/H3rmt/hyprshell/commit/c6237a25ef6d0b676aedd48aa5260fd12e6b39f8))\n* use relm4 as base for adw and gtk ([d95e69e](https://github.com/H3rmt/hyprshell/commit/d95e69e783de3333458eb2aa112ed9a22844355c))\n* use relm4 as base for adw and gtk ([f2e158c](https://github.com/H3rmt/hyprshell/commit/f2e158c0611773cb96d288451b965421ee4d1b3c))\n\n\n### Documentation\n\n* add minimum gtk and adwaita versions to README.md ([c33f431](https://github.com/H3rmt/hyprshell/commit/c33f431c3ef32e8530ca56589c5e107693ef4a2c))\n\n## [4.8.3](https://github.com/H3rmt/hyprshell/compare/v4.8.2...v4.8.3) (2025-12-27)\n\n\n### Bug Fixes\n\n* fix bin package for aur ([443febe](https://github.com/H3rmt/hyprshell/commit/443febe5d54f2b32b3eb3c5c3b8d6f0af3e7276b))\n\n## [4.8.2](https://github.com/H3rmt/hyprshell/compare/v4.8.1...v4.8.2) (2025-12-25)\n\n\n### Bug Fixes\n\n* fix systemd unit generation ([f0fdc99](https://github.com/H3rmt/hyprshell/commit/f0fdc99832510eee5862df6b5826d07e8b16cb9e))\n* **nix-flake:** update flake.lock ([01531df](https://github.com/H3rmt/hyprshell/commit/01531df3820ab6346814f83abd5852a88ccf2c69))\n* **nix-flake:** update flake.lock ([b8827a3](https://github.com/H3rmt/hyprshell/commit/b8827a3b2e0528991b22eacf41ad9f07b4cbbc73))\n\n## [4.8.1](https://github.com/H3rmt/hyprshell/compare/v4.8.0...v4.8.1) (2025-11-17)\n\n\n### Bug Fixes\n\n* crash when filtering windows or clients ([#379](https://github.com/H3rmt/hyprshell/issues/379)) ([13e0105](https://github.com/H3rmt/hyprshell/commit/13e010558b47e416322c8961e5dd1a3e75008503))\n* dont check for a hyprland session on commands other than run ([c93b2dc](https://github.com/H3rmt/hyprshell/commit/c93b2dc95347f32904c45fe820fa5f9e596fcdc0))\n* increase waiting time if no initial workspace is being found ([b6e2782](https://github.com/H3rmt/hyprshell/commit/b6e27827f89670c90b11401d8931beea74dcf57c))\n* **nix-flake:** update flake.lock ([b166440](https://github.com/H3rmt/hyprshell/commit/b16644046b712881d8bafbdd99f8acda9f39b6d6))\n* use xdg notifications instead of hyprland notifications ([0db43a7](https://github.com/H3rmt/hyprshell/commit/0db43a7b905529bf5447fd373a01c78fb804972b))\n\n\n### Code Refactoring\n\n* update dependencies ([a1c00c7](https://github.com/H3rmt/hyprshell/commit/a1c00c782ae3ffac8b4a9f7c1793c5e8daddd061))\n\n\n### Documentation\n\n* updated nix docs ([facdf5a](https://github.com/H3rmt/hyprshell/commit/facdf5a3b40544b49d26886edb65b3726129ac9c))\n\n## [4.8.0](https://github.com/H3rmt/hyprshell/compare/v4.7.2...v4.8.0) (2025-11-09)\n\n\n### Features\n\n* add special workspace support ([0604d21](https://github.com/H3rmt/hyprshell/commit/0604d21371552666b8e62e29ef4a782948c864fc))\n* Add vim navigation to the switcher ([#360](https://github.com/H3rmt/hyprshell/issues/360)) ([cc0797d](https://github.com/H3rmt/hyprshell/commit/cc0797d8f18a970170619ad3b8e17ca8646d8d93))\n* added brotli compression to clipboard lib ([dd0fb3d](https://github.com/H3rmt/hyprshell/commit/dd0fb3d29939775807fa372e280b688c8001e0da))\n* added gui config editor ([bb29010](https://github.com/H3rmt/hyprshell/commit/bb29010eb262dc22335e2c51d7bc5b78042e7553))\n* added hyprshell-slim and hyprshell-bin aur packages ([bb29010](https://github.com/H3rmt/hyprshell/commit/bb29010eb262dc22335e2c51d7bc5b78042e7553))\n* added libadwaita instead of gtk4, added more config options ([bb29010](https://github.com/H3rmt/hyprshell/commit/bb29010eb262dc22335e2c51d7bc5b78042e7553))\n* remove nix wrapper fn and add hyprland input instead ([bb29010](https://github.com/H3rmt/hyprshell/commit/bb29010eb262dc22335e2c51d7bc5b78042e7553))\n\n\n### Bug Fixes\n\n* add `Edit via `hyprshell config edit`` to config file ([e9b9ca4](https://github.com/H3rmt/hyprshell/commit/e9b9ca40c705c050f91859ebe8984cf778494c7b))\n* disable hyprland plugin after loading fails once ([96cb6e6](https://github.com/H3rmt/hyprshell/commit/96cb6e6f2ab97fe4fc0f2d0eb53a13104c8685cf))\n* dont generate systemd file if config is generated in debug mode ([bb29010](https://github.com/H3rmt/hyprshell/commit/bb29010eb262dc22335e2c51d7bc5b78042e7553))\n* downgrad gtk version ([914bc34](https://github.com/H3rmt/hyprshell/commit/914bc34ceba8add1aec1691ace18740ef629b3da))\n* downgrad libadwaita version ([7b6cced](https://github.com/H3rmt/hyprshell/commit/7b6ccedb29db0af4af7ee4a96198ad41c6762ba6))\n* fix selecting client in a special workspace ([97d707c](https://github.com/H3rmt/hyprshell/commit/97d707cca2affd27f9a9cbc356224e96ecd831d2))\n* Fix version check for Hyprshell Plugin ([#373](https://github.com/H3rmt/hyprshell/issues/373)) ([da03596](https://github.com/H3rmt/hyprshell/commit/da035960aadb971c518e415ac273443eceaad19c))\n* **nix-flake:** update flake.lock ([2d6e0ee](https://github.com/H3rmt/hyprshell/commit/2d6e0eee010f88f67d97624a44ebe00abc00bd30))\n* **nix-flake:** update flake.lock ([67e720e](https://github.com/H3rmt/hyprshell/commit/67e720e953bfcd0e30fa53ba9abafbaf2dff8cd7))\n\n\n### Code Refactoring\n\n* use adw instead of gtk4 ([bb29010](https://github.com/H3rmt/hyprshell/commit/bb29010eb262dc22335e2c51d7bc5b78042e7553))\n\n\n### Documentation\n\n* add explanations to tooltips ([2d7009c](https://github.com/H3rmt/hyprshell/commit/2d7009c22efadcace58f099f7f86173ec1f8dbcb))\n* update readme ([bb29010](https://github.com/H3rmt/hyprshell/commit/bb29010eb262dc22335e2c51d7bc5b78042e7553))\n\n## [4.7.2](https://github.com/H3rmt/hyprshell/compare/v4.7.1...v4.7.2) (2025-10-14)\n\n\n### Bug Fixes\n\n* added more logging to creation of windows ([2c57b0f](https://github.com/H3rmt/hyprshell/commit/2c57b0ffb3213b164053090c7c6ef47e8ac0c0ed))\n\n## [4.7.1](https://github.com/H3rmt/hyprshell/compare/v4.7.0...v4.7.1) (2025-10-03)\n\n\n### Bug Fixes\n\n* docs mentioned style.css instead of styles.css as the default location for the CSS file ([a32a317](https://github.com/H3rmt/hyprshell/commit/a32a3171442357ae886fd5a07367a692097dccaf))\n* **nix-flake:** update flake.lock ([944c2b4](https://github.com/H3rmt/hyprshell/commit/944c2b4ad012d7ab963f20c73e6d310d94c1cb33))\n\n## [4.7.0](https://github.com/H3rmt/hyprshell/compare/v4.6.4...v4.7.0) (2025-09-23)\n\n\n### Features\n\n* add actions plugin ([559cc8a](https://github.com/H3rmt/hyprshell/commit/559cc8a81e6a55a09c0f637b503e646e80fc2570))\n* add vim keybinds (https://github.com/H3rmt/hyprshell/issues/185) ([dca2dcc](https://github.com/H3rmt/hyprshell/commit/dca2dccc4199c5823959edfef56115d7b010c177))\n\n\n### Bug Fixes\n\n* **nix-flake:** update flake.lock ([590315d](https://github.com/H3rmt/hyprshell/commit/590315d396ff3811f9115f47dd52c228bd23e5bc))\n* remove and update options from homemanager module ([2ae00bf](https://github.com/H3rmt/hyprshell/commit/2ae00bf10c17ab64685fc7458b880b68b7eddf97))\n\n## [4.6.4](https://github.com/H3rmt/hyprshell/compare/v4.6.3...v4.6.4) (2025-09-14)\n\n\n### Bug Fixes\n\n* allow multiple instances (use wayland socket as part of APPID for gtk) ([8b8bce8](https://github.com/H3rmt/hyprshell/commit/8b8bce807a8d0ad9491396532f2bbfcde037ad0b))\n* allow setting custom hyprland package to fix nix plugin build ([28d0e67](https://github.com/H3rmt/hyprshell/commit/28d0e677eaa4f7032cdacee8fa9ff279188b797b))\n* always apply layerrules ([fbc9cb8](https://github.com/H3rmt/hyprshell/commit/fbc9cb853b986d82b4aac5307b5cb9e8465dcaab))\n* disable gestures disabling (changed in Hyprland 51) ([8b8bce8](https://github.com/H3rmt/hyprshell/commit/8b8bce807a8d0ad9491396532f2bbfcde037ad0b))\n* fix https://github.com/H3rmt/hyprshell/issues/336 by converting open to switch ([5c55ba6](https://github.com/H3rmt/hyprshell/commit/5c55ba6abcd5adb28eb9d216d80e300d942a6997))\n* fix meta for wrapped program ([eabdefb](https://github.com/H3rmt/hyprshell/commit/eabdefbe4ee1cd81c295e049bd3ab0473b5660d0))\n* reload follow mouse prev value on config reload ([8a43141](https://github.com/H3rmt/hyprshell/commit/8a431419941aa6641fc2abaf5e73b576d6f2c40a))\n\n\n### Code Refactoring\n\n* move crates to crate folder ([68633c6](https://github.com/H3rmt/hyprshell/commit/68633c6d776383e2384af9faa7456f15f2fcda1d))\n\n## [4.6.3](https://github.com/H3rmt/hyprshell/compare/v4.6.2...v4.6.3) (2025-09-11)\n\n\n### Bug Fixes\n\n* allow for removal of shell completions ([9699d32](https://github.com/H3rmt/hyprshell/commit/9699d32212ce1c58784d7c83f925ad043f359a4f))\n* cancel key events when opening hyprshell overview and switch ([92e4ea7](https://github.com/H3rmt/hyprshell/commit/92e4ea7a7990719071eae626feeb3f43911a4def))\n* check for nothing enabled in config ([66b6820](https://github.com/H3rmt/hyprshell/commit/66b682089fb94b426c3d8f7c615b097dfe9f40f4))\n* **deps:** update rust crate regex to v1.11.2 ([272fd94](https://github.com/H3rmt/hyprshell/commit/272fd94aeb8ae47ae7f3523e47ecd162e869cf15))\n* fix reload of hyprland if hyprland config was reloaded ([9c2dd76](https://github.com/H3rmt/hyprshell/commit/9c2dd76e4b0c2ee791f6b4fa80aeb6ea5ca017de))\n* ignore virtual keyboard inputs ([9699d32](https://github.com/H3rmt/hyprshell/commit/9699d32212ce1c58784d7c83f925ad043f359a4f))\n* include /usr/share/applications/mimeapps ([7022cb2](https://github.com/H3rmt/hyprshell/commit/7022cb2c4d5a436cd1ccd63b50b944265a40e9a3))\n* override of desktop files (https://github.com/H3rmt/hyprshell/issues/334) ([18fe033](https://github.com/H3rmt/hyprshell/commit/18fe03365799ce372af832a61335a5cf6f8f029c))\n* plugin now still works if overview or switch are disabled ([66b6820](https://github.com/H3rmt/hyprshell/commit/66b682089fb94b426c3d8f7c615b097dfe9f40f4))\n* print `No runs` if hyprshell data launch-history doesnt find any runs ([c67ab1b](https://github.com/H3rmt/hyprshell/commit/c67ab1b62c23cbf4f5f047a284f62b61a144adfa))\n* use Layer::Top for launcher (fix https://github.com/H3rmt/hyprshell/issues/327) ([8389d58](https://github.com/H3rmt/hyprshell/commit/8389d5871be307919dd0bb37cbc8bcf7f010b8aa))\n\n## [4.6.2](https://github.com/H3rmt/hyprshell/compare/v4.6.1...v4.6.2) (2025-09-07)\n\n\n### Bug Fixes\n\n* fix nix packaging ([efe38a2](https://github.com/H3rmt/hyprshell/commit/efe38a2508c50fc6eb9aecb39f1f65fe7047a28c))\n* remove zip dependencies ([efe38a2](https://github.com/H3rmt/hyprshell/commit/efe38a2508c50fc6eb9aecb39f1f65fe7047a28c))\n* use monochrome if path in launcher is a valid path but doesn't exist ([0f59ca3](https://github.com/H3rmt/hyprshell/commit/0f59ca33d4946e1247c5ca937d7f399a03a00f44))\n\n\n### Code Refactoring\n\n* better nix caching ([0f59ca3](https://github.com/H3rmt/hyprshell/commit/0f59ca33d4946e1247c5ca937d7f399a03a00f44))\n\n## [4.6.1](https://github.com/H3rmt/hyprshell/compare/v4.6.0...v4.6.1) (2025-09-04)\n\n\n### Bug Fixes\n\n* **deps:** update deps ([108057a](https://github.com/H3rmt/hyprshell/commit/108057aa10525cb710f7785bd7ed3221bbf7c0e8))\n* hyprland plugin now build without make in OUT_DIR ([31bdfb1](https://github.com/H3rmt/hyprshell/commit/31bdfb1392c4086ee6de6805826666ece2a0c18f))\n\n\n### Documentation\n\n* update nix docs ([eac79f1](https://github.com/H3rmt/hyprshell/commit/eac79f1b2dd2f3d5cc634d2cbcecdca40d8cdd4c))\n\n## [4.6.0](https://github.com/H3rmt/hyprshell/compare/v4.5.0...v4.6.0) (2025-09-04)\n\n\n### Features\n\n* added shell completions ([a74fa47](https://github.com/H3rmt/hyprshell/commit/a74fa4777a2f905b6a0f0269401c564e60068692))\n* added toml to ron migration (toml dropped, as it can't store None values) ([9d1e370](https://github.com/H3rmt/hyprshell/commit/9d1e370b7c2223171a6fb92d0aba0f9cc2a9ca01))\n* better config migrations (allow multi version migrations) ([299d388](https://github.com/H3rmt/hyprshell/commit/299d38816d02dadd97160e7a13088f6aaca2d4ea))\n* enhance ini parsing and added new cli command to get, list and set default apps ([2036a3c](https://github.com/H3rmt/hyprshell/commit/2036a3cb1a3588b97eaea5967e8900cff73726c8))\n* show info when new version detected ([e80fe65](https://github.com/H3rmt/hyprshell/commit/e80fe65a86da07448936babfd4a59ee33340217f))\n\n\n### Bug Fixes\n\n* apply user style with user priority ([b700ee0](https://github.com/H3rmt/hyprshell/commit/b700ee0e4954b9e463ba64263953dacfc36ad097))\n* close overview with open key ([aa5be3f](https://github.com/H3rmt/hyprshell/commit/aa5be3f560bd1fd7bf0026d8c9e09b3f4b4d15b4))\n* **deps:** update rust crate anyhow to v1.0.99 ([a7e96f3](https://github.com/H3rmt/hyprshell/commit/a7e96f388deb7c132bf5bd2f35e88be3f7d56d45))\n* **deps:** update rust crate notify to v8.1.0 ([611cde3](https://github.com/H3rmt/hyprshell/commit/611cde3cb681f030a038439f321421d4e875222e))\n* enable show_actions_submenu for nix users ([8c19498](https://github.com/H3rmt/hyprshell/commit/8c1949892c46da3ff24548ccd904741694339fb0))\n* exclude empty workspaces in switch mode ([10786eb](https://github.com/H3rmt/hyprshell/commit/10786eb5001f1ed7abc3bb2483741b30e410152f))\n* exit app when removing / adding monitors ([032a047](https://github.com/H3rmt/hyprshell/commit/032a047597cab6f2dbe0c6e4482c8d05eb7fbdca))\n* fix cargo install cargo-workspaces ([e62a334](https://github.com/H3rmt/hyprshell/commit/e62a33465633ae87653fed679549b4cfc988f73b))\n* fix cargo ws publish, allow buildscript to run make ([14b5b5a](https://github.com/H3rmt/hyprshell/commit/14b5b5a2ab9e6501c2c125203f44808a80314649))\n* fix missing version in dependency of custom hyprland-rs ([94747cc](https://github.com/H3rmt/hyprshell/commit/94747cc81f554faf034bb5b7c5ec04dcbad03119))\n* fix publish workflow check commit ([29e440f](https://github.com/H3rmt/hyprshell/commit/29e440f6f0685e93d24291196b16623d7721bcd6))\n* fixed select window in overview ([71080a9](https://github.com/H3rmt/hyprshell/commit/71080a9211bd63de4aa0c1405810dfa1126c180c))\n* **nix-flake:** update flake.lock ([918e40b](https://github.com/H3rmt/hyprshell/commit/918e40beb8e70649e52ffcf8dd21747bcdc3f27f))\n* **nix-flake:** update flake.lock ([4eeaa57](https://github.com/H3rmt/hyprshell/commit/4eeaa5710aa7503b9a1307c8016879fd8df664ec))\n* **plugin:** fix open overview after mouse button press ([bd03613](https://github.com/H3rmt/hyprshell/commit/bd0361332ca313d527daec85d7cfdc0d057a5fc1))\n* reload desktop files, etc. after opening launcher ([a679985](https://github.com/H3rmt/hyprshell/commit/a67998550abd8069c47a6d2bd762b72208a70b2f))\n* style changes, liquid gras css updated ([6871827](https://github.com/H3rmt/hyprshell/commit/687182774094aa572441ece0ad9b44c52612a196))\n* typos in home manager configuration ([a9fc51e](https://github.com/H3rmt/hyprshell/commit/a9fc51e6e8d1e23302e17cc905f2f8285744c9fc))\n* use bash to start apps ([c86dee1](https://github.com/H3rmt/hyprshell/commit/c86dee15733bc86f0ab81cceeb84bb1671876da3))\n* use new hyprland-rs Instance ([c519605](https://github.com/H3rmt/hyprshell/commit/c51960581592469653380adfc03d3ea2f78e2e3a))\n* use toml extension on lookup config file ([0ab9e9d](https://github.com/H3rmt/hyprshell/commit/0ab9e9dee21e70e80a815dbea7d833a32f5497cc))\n\n\n### Code Refactoring\n\n* add hyprland plugin ([1412e7a](https://github.com/H3rmt/hyprshell/commit/1412e7a46a3945d7a2f76dd1d1c5ac675160a17e))\n* add hyprland plugin ([33ced1c](https://github.com/H3rmt/hyprshell/commit/33ced1cab64d2864de3f3b3d41f53484ec9adedb))\n* better animations for launcher ([9c2a71c](https://github.com/H3rmt/hyprshell/commit/9c2a71cb275516adbd98ef084a9f5dcd564dedb2))\n* build plugin at runtime ([400a93b](https://github.com/H3rmt/hyprshell/commit/400a93bbc340f933d473c393f4d71fcf2b5339ad))\n* check if set desktop file is valid ([b7cfa98](https://github.com/H3rmt/hyprshell/commit/b7cfa982a371300bf523e12dbfd06f2734bc7ebd))\n* fix nix wrap program ([5895432](https://github.com/H3rmt/hyprshell/commit/58954329f521daabaf67cb0c67a3f130ef9f26cc))\n* implement plugin for switch mode ([9e1193e](https://github.com/H3rmt/hyprshell/commit/9e1193e14b7335af4c017593b2bcafb0a1882f90))\n* more strict clippy rules ([fea4993](https://github.com/H3rmt/hyprshell/commit/fea4993df001461e1e0cc7ba64ccedfda605bb3c))\n* return Ok / Err from socket ([61e09b7](https://github.com/H3rmt/hyprshell/commit/61e09b799860451290d68f46a0b98dc279ae2962))\n* separate config crate ([9d1e370](https://github.com/H3rmt/hyprshell/commit/9d1e370b7c2223171a6fb92d0aba0f9cc2a9ca01))\n* split launcher plugin into 2 data ([c0ff0b7](https://github.com/H3rmt/hyprshell/commit/c0ff0b74cce58f0eec04f79754971863770ebac3))\n* store clippy lints in cargo.toml ([4387f52](https://github.com/H3rmt/hyprshell/commit/4387f52881a05780efa34de0457271363400c121))\n* use different dirs for debug mode ([045def3](https://github.com/H3rmt/hyprshell/commit/045def381c611950851d4819b973a791ea896a2c))\n* use global desktopfile and mime cache ([ea64c40](https://github.com/H3rmt/hyprshell/commit/ea64c408bbb4ca1d84eabadab3d6a9db9632b1de))\n* use keymaps in hyprland plugin ([e0cd4da](https://github.com/H3rmt/hyprshell/commit/e0cd4daae2ed88ab7129a1f2c4dffca98d78d371))\n* use make to improve plugin build time ([df032f4](https://github.com/H3rmt/hyprshell/commit/df032f4a1559dd13e60131a731e4d6e8d449de98))\n* using plugin for all keyboard interactions ([ae39988](https://github.com/H3rmt/hyprshell/commit/ae39988bc3206089fb1b729d98b1ead4df1f32b9))\n* using plugin for all keyboard interactions ([9dfa549](https://github.com/H3rmt/hyprshell/commit/9dfa5494bfd526d3acace3730f0edd9f7fbbe1eb))\n\n\n### Documentation\n\n* update CONFIGURE.md ([#304](https://github.com/H3rmt/hyprshell/issues/304)) ([9e590a0](https://github.com/H3rmt/hyprshell/commit/9e590a0339b547dfceea07ee8165eda649b1c8ec))\n* updated docs ([d713230](https://github.com/H3rmt/hyprshell/commit/d713230fe5435b1f75e78b53c8e749423283a8af))\n\n## [4.5.0](https://github.com/H3rmt/hyprshell/compare/v4.4.3...v4.5.0) (2025-06-27)\n\n\n### Features\n\n* added path plugin ([910aa35](https://github.com/H3rmt/hyprshell/commit/910aa357abc27c4c6f801d19920feed4e05549f1))\n\n\n### Documentation\n\n* update screenshots ([e9b8c7c](https://github.com/H3rmt/hyprshell/commit/e9b8c7ce5b2915ec13adf2cbd8994a3eb669408f))\n\n## [4.4.3](https://github.com/H3rmt/hyprshell/compare/v4.4.2...v4.4.3) (2025-06-26)\n\n\n### Bug Fixes\n\n* fix modifier keys to launch again... ([d31ee66](https://github.com/H3rmt/hyprshell/commit/d31ee669c8da6460b1b0821b9b66783fd10c4a0e))\n* use correct keys for switch mode ([b1c3353](https://github.com/H3rmt/hyprshell/commit/b1c335325f68ca1c5810fac772072640d5db464f))\n\n\n### Code Refactoring\n\n* changed PKGBUILD ([b3f207d](https://github.com/H3rmt/hyprshell/commit/b3f207d7bc0c27892d30fa2420053f27b8e714e6))\n\n## [4.4.2](https://github.com/H3rmt/hyprshell/compare/v4.4.1...v4.4.2) (2025-06-26)\n\n\n### Bug Fixes\n\n* fix launcher keybinds ([05b2867](https://github.com/H3rmt/hyprshell/commit/05b28670edef4ca23e47100785b90b57b8311c06))\n* fix modifier keys to launch, added launch_modifier ([19ba571](https://github.com/H3rmt/hyprshell/commit/19ba57169ae4c77e1e5331c764828f7f67703b90))\n* **nix-flake:** update flake.lock ([abeaea5](https://github.com/H3rmt/hyprshell/commit/abeaea56cb568cb8e30ab8289e194ce10c46ec26))\n* run flake update ci on hyprshell branch ([b39d435](https://github.com/H3rmt/hyprshell/commit/b39d435af9ceb60b25667bf70965499828f1f719))\n\n\n### Code Refactoring\n\n* simplify flake ([431536c](https://github.com/H3rmt/hyprshell/commit/431536cddc88606ebe2246ddb755c10a2db51643))\n\n\n### Documentation\n\n* update nix docs ([d0f45f1](https://github.com/H3rmt/hyprshell/commit/d0f45f1fdaeac348e25d5c5f7c95f76cabefb3d0))\n\n## [4.4.1](https://github.com/H3rmt/hyprshell/compare/v4.4.0...v4.4.1) (2025-06-24)\n\n\n### Bug Fixes\n\n* run flake update ci on hyprshell branch ([b39d435](https://github.com/H3rmt/hyprshell/commit/b39d435af9ceb60b25667bf70965499828f1f719))\n\n\n### Code Refactoring\n\n* simplify flake ([431536c](https://github.com/H3rmt/hyprshell/commit/431536cddc88606ebe2246ddb755c10a2db51643))\n\n\n### Documentation\n\n* update nix docs ([d0f45f1](https://github.com/H3rmt/hyprshell/commit/d0f45f1fdaeac348e25d5c5f7c95f76cabefb3d0))\n\n## [4.4.0](https://github.com/H3rmt/hyprshell/compare/v4.3.1...v4.4.0) (2025-06-24)\n\n\n### Features\n\n* add tui question for switch&gt;show_workspaces ([8e0d925](https://github.com/H3rmt/hyprshell/commit/8e0d9254ec9e0556a1f7b214acbb70a98710c1ca))\n* added show_workspaces flag ([bbba547](https://github.com/H3rmt/hyprshell/commit/bbba5472ed493b4ce5f0b4efe47e98c303e734b6))\n\n\n### Bug Fixes\n\n* dont allow opening overview and switch at the same time. ([7b61fd5](https://github.com/H3rmt/hyprshell/commit/7b61fd58627a7fe5be85c4322fd506b57b8685f0))\n* dont launch plugin entries when typing num instead of ctrl + num ([7b61fd5](https://github.com/H3rmt/hyprshell/commit/7b61fd58627a7fe5be85c4322fd506b57b8685f0))\n* fix nix strip_html_from_workspace_title ([e3f02ea](https://github.com/H3rmt/hyprshell/commit/e3f02ea902fd84fe6201fe29bf221e9804100f57))\n* fix nix version setting ([06fd3f7](https://github.com/H3rmt/hyprshell/commit/06fd3f7f0de5e3f8c7ae80eb99e98f953766c81e))\n* generate correct keybinds for opening overview with super + &lt;key&gt;, fix [#254](https://github.com/H3rmt/hyprshell/issues/254) ([9d52a57](https://github.com/H3rmt/hyprshell/commit/9d52a57baa17a0b41897073b3602619bc04f53d4))\n* mark the current workspace as active if the overview is opened without an active client ([f6eaa02](https://github.com/H3rmt/hyprshell/commit/f6eaa0212782e35d2d0d051f77da53b3efaeda7c))\n* removed old nix navigate assertions ([8151fba](https://github.com/H3rmt/hyprshell/commit/8151fba64718e69e7ed3a9d46bccfd54a84329d6))\n\n\n### Code Refactoring\n\n* add better nix checks and switch to nix only for ci ([b5f8682](https://github.com/H3rmt/hyprshell/commit/b5f86823ed599f2f133b6cba8271248417fbe03f))\n* added check-if-default command for ci ([db912d1](https://github.com/H3rmt/hyprshell/commit/db912d1f1b6919e71288b9cc75e703071bce559e))\n* separate nix code utils ([a3b61e8](https://github.com/H3rmt/hyprshell/commit/a3b61e869c6ff60b4b755f4b2c977ffbc4d82d91))\n\n\n### Documentation\n\n* update CONFIGURE.md ([24b9799](https://github.com/H3rmt/hyprshell/commit/24b979918340dd76515686996cf836e61cd96694))\n\n## [4.3.1](https://github.com/H3rmt/hyprshell/compare/v4.3.0...v4.3.1) (2025-06-21)\n\n\n### Bug Fixes\n\n* repair launcher control keys ([78147fa](https://github.com/H3rmt/hyprshell/commit/78147fa354e3b961b19ce8a9a147601434d71d06))\n\n## [4.3.0](https://github.com/H3rmt/hyprshell/compare/v4.2.12...v4.3.0) (2025-06-21)\n\n\n### Features\n\n* switch to gtk key handling ([65a0ad5](https://github.com/H3rmt/hyprshell/commit/65a0ad5f482707cab8339c3c01195ff9b5557c1a))\n\n\n### Bug Fixes\n\n* **deps:** update rust crate libc to v0.2.174 ([b6d1089](https://github.com/H3rmt/hyprshell/commit/b6d10891ce2bc1f649a6af7d62f2f7f2fa09d74b))\n* fix closing on mod keys other than open key ([d6aba16](https://github.com/H3rmt/hyprshell/commit/d6aba16a2eedfee40bd74feae95750d75c2edf85))\n* fix colored output for explain command ([5cbf8ed](https://github.com/H3rmt/hyprshell/commit/5cbf8ede5d3323b6d7484ab1b638f26842165e83))\n\n\n### Code Refactoring\n\n* remove launcher dependency of overview/switch crate ([df40faa](https://github.com/H3rmt/hyprshell/commit/df40faaec4bf5e5466575d6189194b97e303ac78))\n* remove submaps (10/10) ([c7551ea](https://github.com/H3rmt/hyprshell/commit/c7551ea526583841b5de1b8071ee22a6d5b158fd))\n* remove submaps (3/?) ([9f0c09e](https://github.com/H3rmt/hyprshell/commit/9f0c09e32d8a2ead763f38810f06b71e7dfa93e9))\n* remove submaps (4/?) ([765c88c](https://github.com/H3rmt/hyprshell/commit/765c88c331c3b0e7d834857ef8dc76f235a342ad))\n* remove submaps (5/?) ([65b31cf](https://github.com/H3rmt/hyprshell/commit/65b31cf95ec0edb16357cd865f8d2fa6a22f4e6a))\n* remove submaps (6/?) ([0bc4396](https://github.com/H3rmt/hyprshell/commit/0bc43963a7783a03fc0681f43549e2cd55a56bc7))\n* remove submaps (7/?) ([fdc797d](https://github.com/H3rmt/hyprshell/commit/fdc797d52cba759167a93f88e03b66609ef54f78))\n* remove submaps (8/?) ([9de4678](https://github.com/H3rmt/hyprshell/commit/9de46780f4b2cfeb0f5f4fe9e39c67aebc9e8730))\n* remove submaps (9/?) ([b3f0209](https://github.com/H3rmt/hyprshell/commit/b3f02096c69aa90ca49eebd02b922fb8a127c1b2))\n\n## [4.2.12](https://github.com/H3rmt/hyprshell/compare/v4.2.11...v4.2.12) (2025-06-20)\n\n\n### Bug Fixes\n\n* repair ci ([eaf5391](https://github.com/H3rmt/hyprshell/commit/eaf5391a28a9821caaec626baab2a78211ee7cdd))\n* repair ci ([e0f8af6](https://github.com/H3rmt/hyprshell/commit/e0f8af675a8702fa50c062030b00c51d2f0d4c30))\n* repair ci ([ca782ce](https://github.com/H3rmt/hyprshell/commit/ca782ce03493b3d01f1e30540b7869ff20b0ad1e))\n* repair ci ([761bd1b](https://github.com/H3rmt/hyprshell/commit/761bd1b3444f35c32924efbdd1fc375452600096))\n* repair ci ([0aadfcc](https://github.com/H3rmt/hyprshell/commit/0aadfcc95262f34f49fd535fc88a35f767997423))\n* show toast when using switch mode ([a34a9bb](https://github.com/H3rmt/hyprshell/commit/a34a9bbe2460dd44213fc5c15d7c34a140b19315))\n* use release branch in ci to create new commits ([d8a489e](https://github.com/H3rmt/hyprshell/commit/d8a489e80070f3a3c7d9d451a9f4b04f703fb2d9))\n\n## [4.2.11](https://github.com/H3rmt/hyprshell/compare/v4.2.10...v4.2.11) (2025-06-20)\n\n\n### Bug Fixes\n\n* repair ci ([ca782ce](https://github.com/H3rmt/hyprshell/commit/ca782ce03493b3d01f1e30540b7869ff20b0ad1e))\n\n## [4.2.10](https://github.com/H3rmt/hyprshell/compare/v4.2.9...v4.2.10) (2025-06-20)\n\n\n### Bug Fixes\n\n* repair ci ([761bd1b](https://github.com/H3rmt/hyprshell/commit/761bd1b3444f35c32924efbdd1fc375452600096))\n* repair ci ([0aadfcc](https://github.com/H3rmt/hyprshell/commit/0aadfcc95262f34f49fd535fc88a35f767997423))\n* show toast when using switch mode ([a34a9bb](https://github.com/H3rmt/hyprshell/commit/a34a9bbe2460dd44213fc5c15d7c34a140b19315))\n* use release branch in ci to create new commits ([d8a489e](https://github.com/H3rmt/hyprshell/commit/d8a489e80070f3a3c7d9d451a9f4b04f703fb2d9))\n\n## [4.2.9](https://github.com/H3rmt/hyprshell/compare/v4.2.8...v4.2.9) (2025-06-20)\n\n\n### Bug Fixes\n\n* repair ci ([0aadfcc](https://github.com/H3rmt/hyprshell/commit/0aadfcc95262f34f49fd535fc88a35f767997423))\n\n## [4.2.8](https://github.com/H3rmt/hyprshell/compare/v4.2.7...v4.2.8) (2025-06-20)\n\n\n### Bug Fixes\n\n* use release branch in ci to create new commits ([d8a489e](https://github.com/H3rmt/hyprshell/commit/d8a489e80070f3a3c7d9d451a9f4b04f703fb2d9))\n\n## [4.2.7](https://github.com/H3rmt/hyprshell/compare/v4.2.6...v4.2.7) (2025-06-20)\n\n\n### Bug Fixes\n\n* show toast when using switch mode ([a34a9bb](https://github.com/H3rmt/hyprshell/commit/a34a9bbe2460dd44213fc5c15d7c34a140b19315))\n\n## [4.2.6](https://github.com/H3rmt/hyprshell/compare/v4.2.5...v4.2.6) (2025-06-20)\n\n\n### Bug Fixes\n\n* show toast when using switch mode ([a34a9bb](https://github.com/H3rmt/hyprshell/commit/a34a9bbe2460dd44213fc5c15d7c34a140b19315))\n\n## [4.2.5](https://github.com/H3rmt/hyprshell/compare/v4.2.4...v4.2.5) (2025-06-11)\n\n\n### Bug Fixes\n\n* fix run programs ([3997d2a](https://github.com/H3rmt/hyprshell/commit/3997d2a85e77d2d0e3a6799b17518ad5886aca74))\n\n## [4.2.4](https://github.com/H3rmt/hyprswitch/compare/v4.2.3...v4.2.4) (2025-06-11)\n\n\n### Bug Fixes\n\n* diable gestures and input:follow_mouse on start and reset after close ([6a516b2](https://github.com/H3rmt/hyprswitch/commit/6a516b23e9124f577e16d2475962f8e3347237ae))\n\n## [4.2.3](https://github.com/H3rmt/hyprswitch/compare/v4.2.2...v4.2.3) (2025-06-11)\n\n\n### Bug Fixes\n\n* fix storage of input:follow_mouse setting ([62ab0f2](https://github.com/H3rmt/hyprswitch/commit/62ab0f2f90a7235e3e2d6a8aa9267ebff4c16348))\n* fix systemd exit ([26ae103](https://github.com/H3rmt/hyprswitch/commit/26ae103729696a02069c873bad76c2edbe9dcdf6))\n\n## [4.2.2](https://github.com/H3rmt/hyprswitch/compare/v4.2.1...v4.2.2) (2025-06-11)\n\n\n### Bug Fixes\n\n* add HYPRSHELL_RELOAD_TIMEOUT to change timeout ([764cbb2](https://github.com/H3rmt/hyprswitch/commit/764cbb211ee0a2a1382443b34e58e8a4a035fdb9))\n\n## [4.2.1](https://github.com/H3rmt/hyprswitch/compare/v4.2.0...v4.2.1) (2025-06-11)\n\n\n### Bug Fixes\n\n* add show_actions_submenu with default false ([c0828d6](https://github.com/H3rmt/hyprswitch/commit/c0828d6706157728e4af7742a8e97f7190a8eec0))\n* file watchers work again ([73c7ebf](https://github.com/H3rmt/hyprswitch/commit/73c7ebf0914442c85b8af49e920fdbdd87be10a9))\n* use correct path to generate config ([112e7d2](https://github.com/H3rmt/hyprswitch/commit/112e7d26acde6dd98805b69b807b6a5004763a96))\n\n## [4.2.0](https://github.com/H3rmt/hyprswitch/compare/v4.1.1...v4.2.0) (2025-06-11)\n\n\n### Features\n\n* better window selection on empty workspace ([24b36b3](https://github.com/H3rmt/hyprswitch/commit/24b36b38cc0b127fe2de2a59c223936ffc9c988e))\n* **nix:** Add `show_when_empty` ([8ebb333](https://github.com/H3rmt/hyprswitch/commit/8ebb333cd0dad0ff919ea1790136c8e6120d4560))\n\n\n### Bug Fixes\n\n* close socket after restarting app ([b4a8f2e](https://github.com/H3rmt/hyprswitch/commit/b4a8f2e61d503fb6eee7850ce1ce6f33dfab72bf))\n* debounce reload ([5858cfd](https://github.com/H3rmt/hyprswitch/commit/5858cfd475f1e3b604f6d035bdc5507977833af5))\n* **deps:** update rust crate clap to v4.5.40 ([19272f4](https://github.com/H3rmt/hyprswitch/commit/19272f40aec98c2820919f4d5924533f12b15fc9))\n* **deps:** update rust crate toml to v0.8.23 ([6268be8](https://github.com/H3rmt/hyprswitch/commit/6268be8a74f85eb90dc1d7d94a5057ce6534ce89))\n* handle sigterm and reset submap ([1516024](https://github.com/H3rmt/hyprswitch/commit/1516024a04d46f4433f52984197bf6f39eeee6a6))\n* improved file watcher, file descriptor limit was reached if reloaded too many times ([b4a8f2e](https://github.com/H3rmt/hyprswitch/commit/b4a8f2e61d503fb6eee7850ce1ce6f33dfab72bf))\n* selecting a client with filtering form workspace without an enabled client now selects the first valid client depending on the direction instead of a first client in the workspace ([2a72d8b](https://github.com/H3rmt/hyprswitch/commit/2a72d8ba63ff7c20412d1a9a1dcd7da861c8204b))\n* toml config plugins for launcher ([5858cfd](https://github.com/H3rmt/hyprswitch/commit/5858cfd475f1e3b604f6d035bdc5507977833af5))\n\n## [4.1.1](https://github.com/H3rmt/hyprswitch/compare/v4.1.0...v4.1.1) (2025-06-02)\n\n\n### Bug Fixes\n\n* release workflow now uses deploy keys ([e2c9b89](https://github.com/H3rmt/hyprswitch/commit/e2c9b89f10505a6617f74b3e05c4129b162029ec))\n\n## [4.1.0](https://github.com/H3rmt/hyprswitch/compare/v4.0.4...v4.1.0) (2025-06-02)\n\n\n### Features\n\n* added kill_bind if hyprshell crashes ([5e0b0fa](https://github.com/H3rmt/hyprswitch/commit/5e0b0fa2cf8b7c7d4902fafff0b0dc4b2d03a84a))\n* better parsing of desktop files(ini) to add DesktopActions in launcher ([a304809](https://github.com/H3rmt/hyprswitch/commit/a3048098b1702277cf25e0454e0a7dd3d48c61ee))\n* faster open speeds by applying submaps earlier ([a304809](https://github.com/H3rmt/hyprswitch/commit/a3048098b1702277cf25e0454e0a7dd3d48c61ee))\n\n\n### Bug Fixes\n\n* use new ini parser everywhere ([8db2643](https://github.com/H3rmt/hyprswitch/commit/8db2643a7c5e5b51a316d4654268c38bc2202be4))\n\n## [4.0.4](https://github.com/H3rmt/hyprswitch/compare/v4.0.3...v4.0.4) (2025-06-01)\n\n\n### Bug Fixes\n\n* more debugging for default browser to fix [#188](https://github.com/H3rmt/hyprswitch/issues/188) ([551bfd0](https://github.com/H3rmt/hyprswitch/commit/551bfd0b58aedbccc93236595beab63dcf9195dc))\n\n## [4.0.3](https://github.com/H3rmt/hyprswitch/compare/v4.0.2...v4.0.3) (2025-06-01)\n\n\n### Bug Fixes\n\n* fixed icon scaling ([5a0489a](https://github.com/H3rmt/hyprswitch/commit/5a0489a39742d89abed3760096c3dccee2fa5845))\n* remove launch animation from plugins after close ([685b7bd](https://github.com/H3rmt/hyprswitch/commit/685b7bdf205cafc48e9a21aaccd9428e774905d9))\n* use dbus to open if no browser was found ([c074a56](https://github.com/H3rmt/hyprswitch/commit/c074a56ac7603c14754a4b20a65eeb095e2a103d))\n\n## [4.0.2](https://github.com/H3rmt/hyprswitch/compare/v4.0.1...v4.0.2) (2025-05-31)\n\n\n### Bug Fixes\n\n* fixed the PKGBUILD for arch ([89c06ba](https://github.com/H3rmt/hyprswitch/commit/89c06baa318157827e7042adeaa4ca274b251756))\n\n## [4.0.1](https://github.com/H3rmt/hyprswitch/compare/v4.0.0...v4.0.1) (2025-05-31)\n\n\n### Bug Fixes\n\n* added the PKGBUILD for arch ([93ca69b](https://github.com/H3rmt/hyprswitch/commit/93ca69b15061f4ad8e4f1bcb674dba59c278571b))\n\n## [4.0.0](https://github.com/H3rmt/hyprswitch/compare/v0.8.2...v4.0.0) (2025-05-31)\n\n\n### Features\n\n* add animation to plugin close launch ([b944274](https://github.com/H3rmt/hyprswitch/commit/b9442742b5a357966061c66e594b9104d158fe7b))\n* add calc plugin ([06c8a41](https://github.com/H3rmt/hyprswitch/commit/06c8a41db42d482b777f158fcf9eb1b23708cc13))\n* add debug command ([00afa1e](https://github.com/H3rmt/hyprswitch/commit/00afa1e34b9716a041d3bd33734700836075bd70))\n* Add NixOS `home-manager` module ([cd20717](https://github.com/H3rmt/hyprswitch/commit/cd207178a0cfd44c7ad1069880ef35532f5547ae))\n* add run shell commands from launcher ([879cbba](https://github.com/H3rmt/hyprswitch/commit/879cbba0597281c14b07dadfe03149b4323caf6f))\n* add websearch plugin ([1a079d1](https://github.com/H3rmt/hyprswitch/commit/1a079d1552036f8713ba69f33271475ff4a41103))\n* added `data` command to see LaunchHistory ([8e3de53](https://github.com/H3rmt/hyprswitch/commit/8e3de53b31834c8a034d28d26d72ebcbbd4d9815))\n* added click on clients and workspaces in overview and switch ([328fc3b](https://github.com/H3rmt/hyprswitch/commit/328fc3b432b28ad1e390be10f945e1425708d430))\n* added config file migrations ([db2f6cd](https://github.com/H3rmt/hyprswitch/commit/db2f6cd9fb3c08ab2f9858fb9c1dac61540353b5))\n* added custom args for hyprshell systemd ([aa01139](https://github.com/H3rmt/hyprswitch/commit/aa01139aebfe2dcd717b670ac6ce557f93c2f1d0))\n* added show_when_empty ([6f916d5](https://github.com/H3rmt/hyprswitch/commit/6f916d5b0355293eb0d4007b3c996deddb943c0d))\n* added systemd generation (use --no-systemd to disable) ([97c2c7f](https://github.com/H3rmt/hyprswitch/commit/97c2c7f88c3ba221863a43b8adbfb50b444fa841))\n* better debug commands ([b17a393](https://github.com/H3rmt/hyprswitch/commit/b17a393b04201beab8b582a340d1bb80bef5cda2))\n* click on entry in launcher works ([87076ef](https://github.com/H3rmt/hyprswitch/commit/87076ef8c715fc0f7e29a7c2187aa59f17514df5))\n* NixOS Support ([#171](https://github.com/H3rmt/hyprswitch/issues/171)) ([d42f12b](https://github.com/H3rmt/hyprswitch/commit/d42f12be62c08d3764bc91b034bc7aa05d531608))\n* rewrite hyprswitch ([198cd0f](https://github.com/H3rmt/hyprswitch/commit/198cd0f5ae03210b46cfaba8dbf5f8d30fcc77a9))\n* rewrite hyprswitch ([0d834ab](https://github.com/H3rmt/hyprswitch/commit/0d834ab64cb607d2bc10ca7b13d2642728592f50))\n\n\n### Bug Fixes\n\n* add aur publish ([fc4f9ab](https://github.com/H3rmt/hyprswitch/commit/fc4f9ab4f040646ff075930a86457bf7d4f3e77c))\n* add nix back ([9efadcd](https://github.com/H3rmt/hyprswitch/commit/9efadcdc37ae0c639ab36a1880e3d48b1b6c51a2))\n* allow hold of tab and arrow keys to switch ([e0aaa57](https://github.com/H3rmt/hyprswitch/commit/e0aaa570f54fce0155631c5720805886a0c275a1))\n* arrow keys in the launcher ([179ca7b](https://github.com/H3rmt/hyprswitch/commit/179ca7b45e865875584a4c658a0455ea00af0bc6))\n* background loading of icons ([f5c8ff0](https://github.com/H3rmt/hyprswitch/commit/f5c8ff0e29713432726bb841bafd8dd6729331fa))\n* better icon detection ([453888e](https://github.com/H3rmt/hyprswitch/commit/453888e61551946cfa3dec92409df606f7aa04db))\n* check for config file extensions at start ([7a41bb2](https://github.com/H3rmt/hyprswitch/commit/7a41bb2bf8abea7b2bc2efc2544deb26243faf7c))\n* ci release ([4a0f45f](https://github.com/H3rmt/hyprswitch/commit/4a0f45f466d47a12e6cab70d2e71c835287287a0))\n* close launcher on esc ([a003b82](https://github.com/H3rmt/hyprswitch/commit/a003b8227b709a7bb186e2b3703ec70f3f16dd7d))\n* detect socat path at runtime ([312f667](https://github.com/H3rmt/hyprswitch/commit/312f6677165a023466a115d8a61f2927b31adc71))\n* don't exit launcher when no items ([a4356b7](https://github.com/H3rmt/hyprswitch/commit/a4356b783c148f61736fcd9e4af23d63b1be7c85))\n* don't unset all CSS styles at the start, only necessary ([b42d25c](https://github.com/H3rmt/hyprswitch/commit/b42d25c1d342d10a5af378e1ebcae167a83ca01f))\n* filter apps in the launcher by name, exec and details ([ab0f23e](https://github.com/H3rmt/hyprswitch/commit/ab0f23e43c4ce540e14fe3f0ac515fa52ec56ab9))\n* fix ci publish ([bb1e659](https://github.com/H3rmt/hyprswitch/commit/bb1e65987ac7c371b29cbf09000bb4f117f5b0e9))\n* fix ci release ([e498ca1](https://github.com/H3rmt/hyprswitch/commit/e498ca19f86fcf2d4668fa7582f9320232fd194d))\n* fix ci release ([6dc00c7](https://github.com/H3rmt/hyprswitch/commit/6dc00c719ef7beaea70c86f7851c6de8cdf9117e))\n* fix focus window problem ([3c702a1](https://github.com/H3rmt/hyprswitch/commit/3c702a1da63d56168aa6e7ba2932842c55f97c8d))\n* fix multiple run week caches not being added together ([c027209](https://github.com/H3rmt/hyprswitch/commit/c027209d6f01ec10210420182ea3283380f8e74f))\n* Fix Nix Build ([02bd2c4](https://github.com/H3rmt/hyprswitch/commit/02bd2c400616cb96cd21ccc6b2f530143fcb511b))\n* fix overflow for selecting workspaces ([9efadcd](https://github.com/H3rmt/hyprswitch/commit/9efadcdc37ae0c639ab36a1880e3d48b1b6c51a2))\n* fix overview click on client or workspace ([71d445c](https://github.com/H3rmt/hyprswitch/commit/71d445c0f310f986b769eb420b55e21e9559d94d))\n* fix panic when listening for changes on nonexisting file ([2e02509](https://github.com/H3rmt/hyprswitch/commit/2e025099b4b259844a6f5192a4e9db3db10a00ba))\n* fix right alt bind ([4394240](https://github.com/H3rmt/hyprswitch/commit/43942408b0febe18026e278d2e4cffd7eece25db))\n* fix slow start times ([0c4bb95](https://github.com/H3rmt/hyprswitch/commit/0c4bb9585806de61f73e885026083e49b2fe2048))\n* fix switch mode monitor select ([b8c1d44](https://github.com/H3rmt/hyprswitch/commit/b8c1d440d47fe4a6ca2c1690e1b0be65b81de1df))\n* fix versions ([899b2cc](https://github.com/H3rmt/hyprswitch/commit/899b2cc95018a342c2bf45d0fdf0ef971616be7b))\n* fix versions ([e9565fd](https://github.com/H3rmt/hyprswitch/commit/e9565fd627c85ed4fa8fcf26cd35c06ceeaeb2b7))\n* fix versions ([8d9a5ee](https://github.com/H3rmt/hyprswitch/commit/8d9a5eeeee5d74fef9501fec39c5d8692061ec97))\n* fix versions ([05f3da6](https://github.com/H3rmt/hyprswitch/commit/05f3da60197e59d6a882fa0282127ae62091f879))\n* fix versions ([b4e0380](https://github.com/H3rmt/hyprswitch/commit/b4e0380cced9b3ae35e3d8368fb336edc5530274))\n* fix versions ([14e4a72](https://github.com/H3rmt/hyprswitch/commit/14e4a725dfe6b0d70c235db80069238338ec2890))\n* fix versions ([89711a6](https://github.com/H3rmt/hyprswitch/commit/89711a628c45d6286742ac274ad4ec2fe487faed))\n* fix wrong name in hm module ([99386d5](https://github.com/H3rmt/hyprswitch/commit/99386d56c0e40e65bc491e2b87cf28ef83305f6d))\n* force command now accepts args ([7a41bb2](https://github.com/H3rmt/hyprswitch/commit/7a41bb2bf8abea7b2bc2efc2544deb26243faf7c))\n* get socat path at buildtime ([a4356b7](https://github.com/H3rmt/hyprswitch/commit/a4356b783c148f61736fcd9e4af23d63b1be7c85))\n* make css optional ([b0c36ee](https://github.com/H3rmt/hyprswitch/commit/b0c36eeb3f9dfbe9a85933505dc618cd0c231308))\n* move scripts ([97329b7](https://github.com/H3rmt/hyprswitch/commit/97329b723d4c90e674fa74d65fb4397252266b85))\n* moved size_factor to scale for a more sensible default and bounds check ([a85f3b8](https://github.com/H3rmt/hyprswitch/commit/a85f3b8948211711acf232b9834f4c9c2afadf61))\n* nix allow source and text attributes ([dcd9e41](https://github.com/H3rmt/hyprswitch/commit/dcd9e417071e80cfd489eaefc8908a6b12a324eb))\n* nix json config fixes ([ced4a45](https://github.com/H3rmt/hyprswitch/commit/ced4a45ef6b8361663b30062298f3084f2219a37))\n* **nix:** Fix HM Module ([b8367b4](https://github.com/H3rmt/hyprswitch/commit/b8367b4de9dcd2a5b6e49a3e9322534657de4886))\n* open windows earlier ([b42d25c](https://github.com/H3rmt/hyprswitch/commit/b42d25c1d342d10a5af378e1ebcae167a83ca01f))\n* reload desktop maps on close ([204358d](https://github.com/H3rmt/hyprswitch/commit/204358dc20ad49ff44d79444f707d20a4535d0da))\n* remove size_factor from config ([77b53bd](https://github.com/H3rmt/hyprswitch/commit/77b53bd205d32c0619d244a601cea8304f4a4b9c))\n* remove socat dependency ([01758a6](https://github.com/H3rmt/hyprswitch/commit/01758a6d6384c5ad73a841c3f2e8b90ee9912393))\n* search for installed terminals from PATH ([453888e](https://github.com/H3rmt/hyprswitch/commit/453888e61551946cfa3dec92409df606f7aa04db))\n* show recent windows on one screen only ([f6a3016](https://github.com/H3rmt/hyprswitch/commit/f6a301689827ebb27afc10d445715c070a5762f1))\n* some focus fixes ([9fa8009](https://github.com/H3rmt/hyprswitch/commit/9fa80093f943f6941aeaeb424a262cb3b4c40ec6))\n* sort launcher applications by shorted exec instead of full (removed /bin/flatpak...) ([b17a393](https://github.com/H3rmt/hyprswitch/commit/b17a393b04201beab8b582a340d1bb80bef5cda2))\n* speedup animation ([179ca7b](https://github.com/H3rmt/hyprswitch/commit/179ca7b45e865875584a4c658a0455ea00af0bc6))\n* switch window if no results are present in launcher ([50a81a0](https://github.com/H3rmt/hyprswitch/commit/50a81a0503f8f259374fdafab73e383f930902fe))\n* try all config extensions if file missing ([6cd4799](https://github.com/H3rmt/hyprswitch/commit/6cd4799198c7b170537135a611c8ed88b97aa62f))\n* try to fix publication to creates.io ([c911a07](https://github.com/H3rmt/hyprswitch/commit/c911a078e40655fd869df317479a6a93cce508b2))\n* update documentation ([229921d](https://github.com/H3rmt/hyprswitch/commit/229921d82167d59670cdeab488677b372ecafe73))\n* Update Nix Package ([a147d38](https://github.com/H3rmt/hyprswitch/commit/a147d385ee5928e0616d232f741657069242272f))\n* use char instead of String for key for websearch plugins ([eba4282](https://github.com/H3rmt/hyprswitch/commit/eba42823569cbf19dcb35cf37cc78db7bcdb0e3b))\n\n\n### Documentation\n\n* fix css explain images ([d86d566](https://github.com/H3rmt/hyprswitch/commit/d86d5667201031915100391c1eeb9571e763f370))\n\n\n### Continuous Integration\n\n* fix ci release ([a24657b](https://github.com/H3rmt/hyprswitch/commit/a24657bb9e237e69ff2f8687577114c25de921fe))\n* fix ci release ([e02df4a](https://github.com/H3rmt/hyprswitch/commit/e02df4a193718664762ba8d7e22c63814f061de3))\n* fix ci release ([d8d481d](https://github.com/H3rmt/hyprswitch/commit/d8d481d672809a4f8907a156eed1705caa27a9aa))\n* fix release-please again ([824bf03](https://github.com/H3rmt/hyprswitch/commit/824bf032ab3b131121b16d9c16ce9f6a5215c580))\n* fix release-please again ([bce2335](https://github.com/H3rmt/hyprswitch/commit/bce23355cd3f477e95179acadd5d1401544b1822))\n* fix releases ([06ad9fc](https://github.com/H3rmt/hyprswitch/commit/06ad9fc8cc85a4a6fe3584510bce0efbd1aa1425))\n* switch CI back to normal repo ([9aec89c](https://github.com/H3rmt/hyprswitch/commit/9aec89c4705d1c9683d30274aa442512e3665493))\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[workspace]\nmembers = [\"crates/exec-lib\", \"crates/core-lib\", \"crates/windows-lib\", \"crates/launcher-lib\", \"crates/config-lib\", \"crates/hyprland-plugin\", \"crates/clipboard-lib\", \"dep-crates/hyprland-rs\", \"dep-crates/hyprland-rs/hyprland-macros\", \"dep-crates/wl-clipboard-rs\", \"crates/config-edit-lib\"]\n\n[workspace.package]\nversion = \"4.9.5\"\ndescription = \"A modern GTK4-based window switcher and application launcher for Hyprland\"\nlicense = \"MIT\"\nauthors = [\"h3rmt\"]\nkeywords = [\"hyprland\"]\nrepository = \"https://github.com/h3rmt/hyprshell/\"\n# update when nixpkgs has a newer rustc\nrust-version = \"1.91.0\"\nedition = \"2024\"\n\n[workspace.dependencies]\nanyhow = { version = \"1.0.100\" }\ntracing = { version = \"0.1.44\" }\nserde_json = { version = \"1.0.148\" }\nserde = { version = \"1.0.228\", features = [\"derive\"] }\nasync-channel = { version = \"2.5.0\", default-features = true }\nsemver = { version = \"1.0.27\" }\ntest-log = { version = \"0.2.19\", default-features = false, features = [\"trace\", \"unstable\"] }\n\nrelm4 = { version = \"0.10.0\", default-features = false, features = [\"libadwaita\", \"macros\", \"gnome_48\"] }\nrelm4-components = \"0.10.0\"\ngtk4-layer-shell = { version = \"0.7.1\" }\n\nconfig-lib = { path = \"crates/config-lib\", package = \"hyprshell-config-lib\", version = \"=4.9.5\" }\ncore-lib = { path = \"crates/core-lib\", package = \"hyprshell-core-lib\", version = \"=4.9.5\" }\nexec-lib = { path = \"crates/exec-lib\", package = \"hyprshell-exec-lib\", version = \"=4.9.5\" }\nhyprland-plugin = { path = \"crates/hyprland-plugin\", package = \"hyprshell-hyprland-plugin\", version = \"=4.9.5\" }\n\n[package]\nname = \"hyprshell\"\nreadme = \"README.md\"\ncategories = []\ndocumentation = \"https://docs.rs/hyprshell\"\nversion = \"4.9.5\"\nedition.workspace = true\ndescription.workspace = true\nlicense.workspace = true\nauthors.workspace = true\nkeywords.workspace = true\nrepository.workspace = true\nrust-version.workspace = true\nexclude = [\".idea/*\", \".github/*\"]\n\n[dependencies]\nanyhow.workspace = true\ntracing.workspace = true\nrelm4.workspace = true\nasync-channel.workspace = true\nsemver.workspace = true\nclap = { version = \"4.5.47\", features = [\"derive\"] }\nclap_complete = \"4.5.57\"\ntracing-subscriber = { version = \"0.3.20\", features = [\"fmt\", \"env-filter\", \"ansi\"], default-features = false }\nsignal-hook = { version = \"0.4.1\" }\n\nconfig-lib.workspace = true\ncore-lib.workspace = true\nexec-lib.workspace = true\nclipboard-lib = { path = \"crates/clipboard-lib\", package = \"hyprshell-clipboard-lib\", version = \"=4.9.5\" }\nwindows-lib = { path = \"crates/windows-lib\", package = \"hyprshell-windows-lib\", version = \"=4.9.5\" }\nlauncher-lib = { path = \"crates/launcher-lib\", package = \"hyprshell-launcher-lib\", version = \"=4.9.5\" }\nconfig-edit-lib = { path = \"crates/config-edit-lib\", package = \"hyprshell-config-edit-lib\", version = \"=4.9.5\", optional = true }\n\n[dev-dependencies]\ntest-log.workspace = true\n\n[features]\ndefault = [\"json5_config\", \"gui_settings_editor\", \"launcher_calc\", \"debug_command\", \"clipboard_compress_lz4\", \"clipboard_compress_zstd\", \"clipboard_compress_brotli\", \"clipboard_encrypt_chacha20poly1305\", \"clipboard_encrypt_aes_gcm\"]\nslim = [\"gui_settings_editor\", \"debug_command\", \"clipboard_compress_lz4\"]\njson5_config = [\"config-lib/json5_config\"]\ngui_settings_editor = [\"dep:config-edit-lib\"]\nlauncher_calc = [\"launcher-lib/calc\", \"config-lib/launcher_calc_plugin\", \"config-edit-lib?/launcher_calc_plugin\"]\ndebug_command = []\nci_config_check = [\"config-lib/ci_no_default_config_values\"]\nclipboard_compress_lz4 = [\"clipboard-lib/compress_lz4\"]\nclipboard_compress_zstd = [\"clipboard-lib/compress_zstd\"]\nclipboard_compress_brotli = [\"clipboard-lib/compress_brotli\"]\nclipboard_encrypt_chacha20poly1305 = [\"clipboard-lib/encrypt_chacha20poly1305\"]\nclipboard_encrypt_aes_gcm = [\"clipboard-lib/encrypt_aes_gcm\"]\n\n\n[profile.release]\nstrip = \"debuginfo\"\nlto = true\nopt-level = 3\n\n[lints]\nworkspace = true\n\n[workspace.lints.clippy]\nall = { level = \"warn\", priority = -1 }\npedantic = { level = \"warn\", priority = -1 }\nnursery = { level = \"warn\", priority = -1 }\nunwrap_used = \"deny\"\nsingle_match = \"allow\"\nmatch_wildcard_for_single_variants = \"allow\"\nstruct_excessive_bools = \"allow\"\nmissing_errors_doc = \"allow\"\ncast_possible_truncation = \"allow\"\n# This is broken\nuninlined_format_args = \"warn\"\nunnecessary_debug_formatting = \"warn\"\nunnecessary_wraps = \"warn\"\npathbuf_init_then_push = \"warn\"\nprint_stdout = \"warn\"\nprint_stderr = \"warn\"\n"
  },
  {
    "path": "DEVELOPMENT.md",
    "content": "# Development Guide\n\nWelcome to the Hyprshell development guide. This document provides information on how to set up your environment, the project structure, and common development tasks.\n\n## Prerequisites\n\nTo develop for Hyprshell, you need to have the following installed:\n\n- **Rust**: Latest stable version (minimum `1.92.0`).\n- **GTK4 & Libadwaita**: Development headers for GTK4 and Libadwaita.\n- **GTK4 Layer Shell**: Development headers for [gtk4-layer-shell](https://github.com/wmww/gtk4-layer-shell).\n- **Hyprland**: Minimum version `0.52.1`. Development headers (`hyprland-devel`) are needed for the plugin.\n- **just**: A handy command runner used for various development tasks.\n\n### Installing `just`\n\nWe use [just](https://github.com/casey/just) to automate common tasks. You can install it using your package manager:\n\n- **Arch Linux**: `sudo pacman -S just`\n- **Fedora**: `sudo dnf install just`\n- **Nix**: `nix-shell -p just` or add it to your flake.\n- **Cargo**: `cargo install just`\n\n## Project Structure\n\nHyprshell is organized as a Rust workspace with multiple crates and some vendored dependencies.\n\n### Directories\n\n- `crates/`: Contains the internal libraries that make up Hyprshell.\n    - `core-lib`: Fundamental types and utilities.\n    - `config-lib`: Configuration loading, generation, and migration.\n    - `config-edit-lib`: The GUI settings editor.\n    - `exec-lib`: Hyprland specific logic and plugin management.\n    - `launcher-lib`: Logic for the application launcher.\n    - `windows-lib`: Logic for the window switcher.\n    - `clipboard-lib`: Clipboard management and history.\n    - `hyprland-plugin`: A C++ Hyprland plugin used to capture keyboard events.\n- `dep-crates/`: Contains forks or local versions of external dependencies.\n    - `hyprland-rs`: A fork of the Hyprland IPC library.\n    - `wl-clipboard-rs`: A fork of the Wayland clipboard library.\n- `src/`: Contains the main entry point for the `hyprshell` binary.\n- `scripts/`: Various helper scripts for CI and development.\n- `nix/`: Nix-related files for building and development shells.\n- `docs/`: Documentation files.\n- `packaging/`: Files for packaging Hyprshell.\n\n## Common Tasks\n\nWe use `just` to run common development tasks. Run `just` without arguments to see a full list of available commands.\n\n### Development\n\n- `just run`: Run the application in debug mode (prints available commands).\n- `just run release`: Run the application in release mode.\n- `just run-run`: Run the application process in debug mode.\n\n### Environment Variables\n\nUseful environment variables for development:\n\n- `HYPRSHELL_EXPERIMENTAL=1`: Enables experimental features.\n- `HYPRSHELL_LOG_MODULE_PATH=1`: Adds module path to logs (use with `-vv`)."
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2025 Enrico Stemmer\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Hyprshell\n\n[![crates.io](https://img.shields.io/crates/v/hyprshell.svg)](https://crates.io/crates/hyprshell) [![Docs](https://docs.rs/built/badge.svg)](https://docs.rs/hyprshell)\n\n![img.png](.github/imgs/img-3.png)\n\n## Overview\n\nHyprshell _(previously hyprswitch)_ is a Rust-based GUI designed to enhance window management in [Hyprland](https://github.com/hyprwm/Hyprland).\nIt provides a powerful and customizable interface for switching between windows using keyboard shortcuts and GUI.\nThe application also includes a launcher for running applications, doing calculations, etc.\n\n## Features\n\n- **Window Switching**: Switch between windows using keyboard shortcuts in a GUI.\n- **Customizable Keybindings**: Define your own keybindings for window switching and GUI interactions.\n- **Config**: Interactive [config file](docs/CONFIGURE.md) generation for easy setup.\n- **Launcher Integration**: Launch applications directly from the GUI, sorted by usage frequency.\n- **Launcher Plugins**: Different plugins like Web search, actions or calculations can be enabled.\n- **Theming**: Customize the GUI appearance (gtk4) using [CSS](docs/CONFIGURE.md).\n- **Settings App**: Customize the application using a settings app.\n- **Dynamic Configuration**: Automatically reloads configuration/style changes without restarting the application.\n- **Debug commands**: Many [Commands](docs/DEBUG.md) to debug desktop files, icons and default applications.\n\n## Installation\n\n**Minimum hyprland version: 0.52.1**\n\n[![Packaging status](https://repology.org/badge/vertical-allrepos/hyprshell.svg)](https://repology.org/project/hyprshell/versions)\n\n### Arch Linux (AUR)\n\n```bash\nparu -S hyprshell\n# or\nyay -S hyprshell\n```\n\nUse `hyprshell-bin` for the pre-built binaries from GitHub releases.\n\nUse `hyprshell-slim` for the [slim](#feature-flags) version (faster buildtime).\n\n### Binary pre-built packages (only for x86_64 and aarch64)\n\nDownload and extract from the latest release on the [releases](https://github.com/h3rmt/hyprshell/releases) page.\n\n### NixOS\n\nThis repository contains a `flake` and with a type-save `home-manager` module for configuration.\n\nHyprshell is also available in `nixpkgs` repository and can be configured using a generic `home-manager` module.\n\nMore information can be found in the [NixOS](docs/NIX.md) section.\n\n### From Source\n\nhyprland, gtk4[v4_18], libadwaita[v1_8] and [gtk4-layer-shell](https://github.com/wmww/gtk4-layer-shell)[1.1.1] must be installed\n\n```bash\ncargo install hyprshell\n```\n\nBuild with less features in [slim](#feature-flags) mode\n\n```bash\ncargo install hyprshell --no-default-features --features \"slim\"\n```\n\n**hyprland-devel is needed for the hyprland headers (needed to build hyprland plugin)**\n\nFedora: `sudo dnf install gtk4-layer-shell-devel libadwaita-devel hyprland-devel`\n\nArch: `sudo pacman -Sy gtk4-layer-shell libadwaita hyprland`\n\nMinimum required rustc version: `1.92.0`\n\n## Usage\n\nRun `hyprshell --help` to see available commands and options.\n\n### Config\n\nTo generate a default configuration file, run:\n\n```bash\nhyprshell config generate\n```\n\nThis launches an interactive prompt to set up your configuration.\nThe generated file will be located at `~/.config/hyprshell/config.ron`.\nIf you want to modify these settings, look at the [Documentation](docs/CONFIGURE.md) for the config file.\n\nTo validate your configuration file, run:\n\n```bash\nhyprshell config explain\n```\n\nThis checks for any syntax errors or issues in your configuration file and shows a `explanation` of how to use hyprshell.\n\nTo edit the configuration file run `hyprshell config edit`. This launches the settings editor.\n\n### Initialization\n\nEnable the systemd service (generated with `hyprshell config generate`) [recommended]:\n\n```bash\nsystemctl --user enable --now hyprshell.service\n```\n\nOr add the following to your Hyprland configuration (`~/.config/hypr/hyprland.conf`):\n\n```ini\nexec-once = hyprshell run &\n```\n\n![image.png](.github/imgs/swappy-20250420_000818.png)\n![img.png](.github/imgs/img-2.png)\n\n### Debugging\n\nDebug commands are provided to help troubleshoot desktop files, icons, default applications and launcher functionality, see [Debug.md](docs/DEBUG.md) for detailed information about available commands and their usage.\n\n### Feature Flags\n\n✅ = included in the default feature set.\n\n✨ = included in the slim feature set. (build with ``--no-default-features --features \"slim\"``)\n\n- `gui_settings_editor`✅✨: Adds the `hyprshell config edit` command to open the settings editor.\n- `json5_config`✅: Adds support for a json5 config file.\n- `launcher_calc`✅: Adds support for the calc plugin in the launcher.\n- `debug_command`✅✨: Adds the `hyprshell debug` command to debug icons, desktop files, etc.\n- `clipboard_compress_lz4`✅✨: Adds support for compressing clipboard content using lz4.\n- `clipboard_compress_brotli`✅: Adds support for compressing clipboard content using brotli.\n- `clipboard_compress_zstd`✅: Adds support for compressing clipboard content using zstd.\n- `clipboard_encrypt_chacha20poly1305`✅: Adds support for encrypting clipboard content using chacha20poly1305.\n- `clipboard_encrypt_aes_gcm`✅: Adds support for encrypting clipboard content using aes_256_gcm.\n- `ci_config_check`: (!used for ci tests) Adds a command to check if the loaded config is equal to the default config or the full config. Also diables loading of configs without all values.\n\n### Env Variables\n\n- `HYPRSHELL_NO_LISTENERS`: Disable all config listeners (config file, css file, hyprland config, monitor count)\n- `HYPRSHELL_NO_ALL_ICONS`: Don't check for all icons on fs and just use the ones provided by the `gtk4` icon theme.\n- `HYPRSHELL_RELOAD_TIMEOUT`: Set the timeout for reloading the config file in milliseconds (default: `1500`).\n- `HYPRSHELL_LOG_MODULE_PATH`: Add the module path to each log message. (use with -vv)\n- `HYPRSHELL_NO_USE_PLUGIN`: Disable the use of the hyprland plugin to capture switch mode events.\n- `HYPRSHELL_EXPERIMENTAL`: Enables experimental features (grep through the source code for `\"HYPRSHELL_EXPERIMENTAL\"` to see them)\n- `HYPRSHELL_RUN_ACTIONS_IN_DEBUG`: Run actions from launcher plugin in debug mode\n"
  },
  {
    "path": "crates/clipboard-lib/Cargo.toml",
    "content": "[package]\nname = \"hyprshell-clipboard-lib\"\ndocumentation = \"https://docs.rs/hyprshell-clipboard-lib\"\nversion = \"4.9.5\"\ndescription = \"A library monitoring and storing clipboard contents\"\nedition.workspace = true\nlicense.workspace = true\nauthors.workspace = true\nkeywords.workspace = true\nrepository.workspace = true\nrust-version.workspace = true\n\n[dependencies]\nanyhow.workspace = true\ntracing.workspace = true\ncore-lib.workspace = true\nwl_clipboard = { package = \"hyprshell-wl-clipboard-rs\", path = \"../../dep-crates/wl-clipboard-rs\", version = \"=4.9.5\" }\nfast_image_resize = { version = \"5.3.0\", features = [\"image\"] }\nbitcode = { version = \"0.6.9\" }\nimage = { version = \"0.25.8\", features = [\"png\", \"jpeg\", \"webp\", \"gif\"], default-features = false }\nlz4_flex = { version = \"0.12.0\", optional = true }\nzstd = { version = \"0.13.3\", optional = true }\nbrotli = { version = \"8.0.2\", optional = true }\nsecret-service = { version = \"5.0.0\", features = [\"rt-tokio-crypto-rust\"], default-features = false, optional = true }\nchacha20poly1305 = { version = \"0.11.0-rc.2\", optional = true }\naes-gcm = { version = \"0.11.0-rc.2\", optional = true }\ncrypto-common = { version = \"0.2.0-rc.9\", optional = true }\n\n[features]\ncompress_lz4 = [\"dep:lz4_flex\"]\ncompress_zstd = [\"dep:zstd\"]\ncompress_brotli = [\"dep:brotli\"]\nencrypt_chacha20poly1305 = [\"dep:chacha20poly1305\", \"dep:secret-service\", \"dep:crypto-common\"]\nencrypt_aes_gcm = [\"dep:aes-gcm\", \"dep:secret-service\", \"dep:crypto-common\"]\n\n[lints]\nworkspace = true\n"
  },
  {
    "path": "crates/clipboard-lib/src/config/mod.rs",
    "content": "#![allow(dead_code)]\n\n#[derive(Debug, Clone)]\npub struct Config {\n    pub encryption: Encryption,\n    pub compression: Compression,\n    pub image_conv_filter: ConvolutionFilterType,\n}\n\n#[derive(Debug, Copy, Clone, Default)]\npub enum Encryption {\n    #[default]\n    None,\n    #[cfg(feature = \"encrypt_chacha20poly1305\")]\n    ChaCha20Poly1305,\n    #[cfg(feature = \"encrypt_aes_gcm\")]\n    AesGcm,\n}\n\n#[derive(Debug, Copy, Clone, Default)]\npub enum Compression {\n    #[default]\n    None,\n    #[cfg(feature = \"compress_lz4\")]\n    Lz4,\n    #[cfg(feature = \"compress_zstd\")]\n    Zstd(u8),\n    #[cfg(feature = \"compress_brotli\")]\n    Brotli(u8),\n}\n\n#[derive(Debug, Copy, Clone, Default)]\npub enum ConvolutionFilterType {\n    Box,\n    Bilinear,\n    Hamming,\n    CatmullRom,\n    Mitchell,\n    Gaussian,\n    #[default]\n    Lanczos3,\n}\n\nimpl From<ConvolutionFilterType> for fast_image_resize::FilterType {\n    fn from(value: ConvolutionFilterType) -> Self {\n        match value {\n            ConvolutionFilterType::Box => Self::Box,\n            ConvolutionFilterType::Bilinear => Self::Bilinear,\n            ConvolutionFilterType::Hamming => Self::Hamming,\n            ConvolutionFilterType::CatmullRom => Self::CatmullRom,\n            ConvolutionFilterType::Mitchell => Self::Mitchell,\n            ConvolutionFilterType::Gaussian => Self::Gaussian,\n            ConvolutionFilterType::Lanczos3 => Self::Lanczos3,\n        }\n    }\n}\n"
  },
  {
    "path": "crates/clipboard-lib/src/lib.rs",
    "content": "mod config;\npub mod store;\npub(crate) mod util;\n"
  },
  {
    "path": "crates/clipboard-lib/src/store/listen.rs",
    "content": "use crate::config::{Compression, Config, ConvolutionFilterType, Encryption};\nuse crate::store::mime::{filer_mimes, get_preferred_mime};\nuse crate::store::save_image::compress_and_store_image;\nuse crate::store::save_map::compress_and_store_map;\nuse crate::store::save_text::store_text;\nuse core_lib::WarnWithDetails;\nuse std::any::Any;\nuse std::collections::HashMap;\nuse std::path::PathBuf;\nuse std::sync::{Arc, RwLock};\nuse std::thread;\nuse tracing::{debug, warn};\nuse wl_clipboard::paste::{CallbackData, Seat, get_all_contents_callback};\n\n/// # Panics\npub fn test_clipboard(_data_dir: PathBuf, cache_dir: PathBuf) {\n    debug!(\"Starting clipboard listener\");\n    let cl_config = Arc::new(RwLock::new(conf()));\n\n    let config_clone = cl_config.clone();\n    let _ = cl_config.type_id();\n\n    let handle_values = move |val: CallbackData| -> bool {\n        let (mut mimes, load) = match val {\n            Ok(r) => r,\n            Err(err) => {\n                warn!(\"Failed to get clipboard contents: {err:?}\");\n                return false;\n            }\n        };\n        let config = {\n            let Ok(config) = config_clone.read() else {\n                return true; // lock poisoned, aboard clipboard listener\n            };\n            config.clone()\n        };\n\n        filer_mimes(&mut mimes);\n\n        let Some(pref_mime) = get_preferred_mime(&mimes) else {\n            warn!(\"No preferred MIME type found, available: {mimes:?}\");\n            return false;\n        };\n\n        // load data for all mime types\n        let mut data = HashMap::new();\n        for mime in &mimes {\n            data.insert(\n                mime.clone(),\n                load(mime.clone()).expect(\"mime type despawned while loading clipboard data\"),\n            );\n        }\n\n        if pref_mime.starts_with(\"image/\") {\n            let pref_data = data\n                .get(&pref_mime)\n                .expect(\"Preferred MIME type not found\")\n                .clone();\n            let cache_dir_clone = cache_dir.clone();\n            let config_clone = config.clone();\n            thread::spawn(move || {\n                compress_and_store_image(pref_data, &config_clone, &cache_dir_clone)\n                    .warn_details(\"Failed to store clipboard image\")\n            });\n        } else {\n            let pref_data = data.get(&pref_mime).expect(\"Preferred MIME type not found\");\n            let text = String::from_utf8_lossy(pref_data);\n            store_text(&text, &config, &cache_dir).warn_details(\"Failed to store clipboard text\");\n        }\n        let cache_dir_clone = cache_dir.clone();\n        thread::spawn(move || compress_and_store_map(data, &config, &cache_dir_clone));\n        false\n    };\n\n    let _ = get_all_contents_callback(Seat::Unspecified, Box::new(handle_values))\n        .expect(\"Failed to start clipboard listener\")\n        .join();\n    warn!(\"Clipboard listener stopped\");\n}\n\nfn conf() -> Config {\n    let mut config = Config {\n        encryption: Encryption::default(),\n        compression: Compression::default(),\n        image_conv_filter: ConvolutionFilterType::Lanczos3,\n    };\n    config.encryption = Encryption::default();\n    config.compression = Compression::default();\n    #[cfg(feature = \"compress_brotli\")]\n    {\n        config.compression = Compression::Brotli(6);\n    }\n    #[cfg(feature = \"compress_zstd\")]\n    {\n        config.compression = Compression::Zstd(16);\n    }\n    #[cfg(feature = \"encrypt_aes_gcm\")]\n    {\n        config.encryption = Encryption::AesGcm;\n    }\n    #[cfg(feature = \"compress_lz4\")]\n    {\n        config.compression = Compression::Lz4;\n    }\n    #[cfg(feature = \"encrypt_chacha20poly1305\")]\n    {\n        config.encryption = Encryption::ChaCha20Poly1305;\n    }\n    config\n}\n"
  },
  {
    "path": "crates/clipboard-lib/src/store/mime.rs",
    "content": "use std::collections::HashSet;\nuse tracing::trace;\n\nstatic MIME_TYPES_PRIO: &[&str] = &[\n    \"image/png\",\n    \"image/jpg\",\n    \"image/jpeg\",\n    \"image/webp\",\n    \"image/gif\",\n    \"text/plain;charset=utf-8\",\n    \"UTF8_STRING\",\n    \"STRING\",\n    \"TEXT\",\n    \"text/*\",\n];\n\nstatic MIME_TYPES_IMAGES_PRIO: &[&str] = &[\n    \"image/png\",\n    \"image/jpg\",\n    \"image/jpeg\",\n    \"image/webp\",\n    \"image/gif\",\n];\n\npub fn get_preferred_mime(mime_types: &HashSet<String>) -> Option<String> {\n    // Find by priority\n    for (index, mime) in MIME_TYPES_PRIO.iter().enumerate() {\n        if mime.ends_with(\"/*\") {\n            let prefix = &mime[..mime.len() - 1];\n            if let Some(mt) = mime_types.iter().find(|x| x.starts_with(prefix)) {\n                trace!(\"Chosen MIME type: {mt:?} from prio({index}), by prefix {prefix}*\");\n                return Some(mt.clone());\n            }\n        } else if let Some(mt) = mime_types.iter().find(|x| x == mime) {\n            trace!(\"Chosen MIME type: {mt:?} from prio({index}), by exact match {mime}\");\n            return Some(mt.clone());\n        }\n    }\n    None\n}\n\npub fn filer_mimes(mime_types: &mut HashSet<String>) {\n    let count = mime_types.len();\n\n    // remove audio\n    mime_types.retain(|mt| !mt.starts_with(\"audio/\"));\n\n    // Retain only one image/ MIME type.\n    let image_mime = MIME_TYPES_IMAGES_PRIO\n        .iter()\n        .find(|preferred| mime_types.contains(&(**preferred).to_string()))\n        .map(|&preferred| preferred.to_string())\n        .or_else(|| {\n            mime_types\n                .iter()\n                .find(|mt| mt.starts_with(\"image/\"))\n                .cloned()\n        });\n\n    mime_types.retain(|mt| {\n        if mt.starts_with(\"image/\") {\n            image_mime.as_ref() == Some(mt)\n        } else {\n            true\n        }\n    });\n\n    trace!(\n        \"Available MIME types: {:?}, removed {} elements\",\n        mime_types,\n        count - mime_types.len()\n    );\n}\n"
  },
  {
    "path": "crates/clipboard-lib/src/store/mod.rs",
    "content": "mod listen;\nmod mime;\nmod save_image;\nmod save_map;\nmod save_text;\npub(crate) mod util;\nmod write;\n\npub use listen::test_clipboard;\n"
  },
  {
    "path": "crates/clipboard-lib/src/store/save_image.rs",
    "content": "use crate::store::util::create_storage_path;\nuse anyhow::Context;\nuse image::{ImageEncoder, ImageReader};\nuse std::fs::File;\nuse std::io::{Cursor, Write};\nuse std::path::Path;\nuse tracing::trace;\n\nuse crate::config::Config;\nuse crate::store::write::get_storage_writer;\nuse fast_image_resize::images::Image;\nuse fast_image_resize::{IntoImageView, ResizeAlg, ResizeOptions, Resizer};\nuse image::codecs::png::PngEncoder;\n\nconst IMAGE_HEIGHT: u32 = 150;\n\npub fn compress_and_store_image(\n    pref_data: Vec<u8>,\n    config: &Config,\n    cache_dir: &Path,\n) -> anyhow::Result<()> {\n    let now = std::time::SystemTime::now();\n    let img2 = ImageReader::new(Cursor::new(pref_data))\n        .with_guessed_format()?\n        .decode()?;\n    trace!(\n        \"Loaded image in {:?}, Image size: {}x{}\",\n        now.elapsed()?,\n        img2.width(),\n        img2.height()\n    );\n    let now = std::time::SystemTime::now();\n\n    #[allow(clippy::cast_sign_loss, clippy::cast_precision_loss)]\n    let mut dst_image = Image::new(\n        (img2.width() as f32 * (IMAGE_HEIGHT as f32 / img2.height() as f32)) as u32,\n        IMAGE_HEIGHT,\n        img2.pixel_type()\n            .context(\"Failed to get pixel type for clipboard image\")?,\n    );\n\n    let mut resizer = Resizer::new();\n    resizer.resize(\n        &img2,\n        &mut dst_image,\n        &ResizeOptions::new().resize_alg(ResizeAlg::Convolution(config.image_conv_filter.into())),\n    )?;\n    trace!(\n        \"Resized image size: {}x{} in {:?}\",\n        dst_image.width(),\n        dst_image.height(),\n        now.elapsed()?\n    );\n\n    let mut cursor = Cursor::new(Vec::new());\n    {\n        let (mut write, _ext) = get_storage_writer(&mut cursor, config, false);\n        PngEncoder::new(&mut write).write_image(\n            dst_image.buffer(),\n            dst_image.width(),\n            dst_image.height(),\n            img2.color().into(),\n        )?;\n    }\n\n    let storage_path = create_storage_path(cache_dir, \"images\", \"png\")\n        .context(\"Failed to get storage path for clipboard image\")?;\n    let mut file = File::create(&storage_path).context(\"Failed to create clipboard image file\")?;\n    file.write_all(&cursor.into_inner())\n        .context(\"Failed to write clipboard data\")?;\n    trace!(\n        \"Wrote image to {:?} ({} bytes)\",\n        storage_path.display(),\n        file.metadata().map(|m| m.len()).unwrap_or(0)\n    );\n    Ok(())\n}\n"
  },
  {
    "path": "crates/clipboard-lib/src/store/save_map.rs",
    "content": "use crate::config::Config;\nuse crate::store::util::create_storage_path;\nuse crate::store::write::get_storage_writer;\nuse anyhow::Context;\nuse std::collections::HashMap;\nuse std::fs::File;\nuse std::io::{Cursor, Write};\nuse std::path::Path;\nuse std::time::SystemTime;\nuse tracing::{trace, warn};\n\n#[derive(Debug, bitcode::Encode, bitcode::Decode)]\npub enum ClipboardDataType {\n    Alias(Box<str>),\n    Data(Vec<u8>),\n}\n\npub fn compress_and_store_map(data: HashMap<String, Vec<u8>>, config: &Config, cache_dir: &Path) {\n    let combined_size = data.values().map(Vec::len).sum::<usize>();\n    let (data, contains_image) = deduplicate_clipboard_entries(data, true);\n    let compressed_combined_size = data\n        .values()\n        .filter_map(|dt| {\n            if let ClipboardDataType::Data(d) = dt {\n                Some(d.len())\n            } else {\n                None\n            }\n        })\n        .sum::<usize>();\n    trace!(\n        \"Combined size: {} bytes, compressed size {} bytes, storing {} aliased and {} data entries\",\n        combined_size,\n        compressed_combined_size,\n        data.values()\n            .filter(|dt| matches!(dt, ClipboardDataType::Alias(_)))\n            .count(),\n        data.values()\n            .filter(|dt| matches!(dt, ClipboardDataType::Data(_)))\n            .count()\n    );\n\n    // dont compress if contains image\n    if let Err(err) = store_map(&data, config, !contains_image, cache_dir) {\n        warn!(\"Failed to store clipboard data: {err}\");\n    }\n}\n\nfn store_map(\n    data: &HashMap<Box<str>, ClipboardDataType>,\n    config: &Config,\n    compress: bool,\n    cache_dir: &Path,\n) -> anyhow::Result<()> {\n    let now = SystemTime::now();\n    let mut cursor = Cursor::new(Vec::new());\n    let ext = {\n        let (mut writer, ext) = get_storage_writer(&mut cursor, config, compress);\n        let encoded = bitcode::encode(data);\n        writer\n            .write_all(&encoded)\n            .context(\"Failed to write encoded clipboard data\")?;\n        ext\n    };\n    let storage_path = create_storage_path(cache_dir, \"data\", &format!(\"bin.{ext}\"))\n        .context(\"Failed to get storage path for clipboard data\")?;\n    let mut file = File::create(&storage_path).context(\"Failed to create clipboard data file\")?;\n    file.write_all(&cursor.into_inner())\n        .context(\"Failed to write clipboard data\")?;\n\n    trace!(\n        \"Wrote clipboard data to {} ({} bytes) in {:?}\",\n        storage_path.display(),\n        file.metadata().map(|m| m.len()).unwrap_or(0),\n        now.elapsed()?\n    );\n    Ok(())\n}\n\npub fn deduplicate_clipboard_entries(\n    data: HashMap<String, Vec<u8>>,\n    dedup: bool,\n) -> (HashMap<Box<str>, ClipboardDataType>, bool) {\n    let time = std::time::Instant::now();\n    let mut image_found = false;\n    let mut dedupted = 0u16;\n    let mut map: HashMap<Box<str>, ClipboardDataType> = HashMap::new();\n    'outer: for (mime, data) in data {\n        if mime.starts_with(\"image/\") {\n            image_found = true;\n        }\n        if dedup {\n            for (f_mime, f_dt) in map.iter().filter(|(m, _)| !m.starts_with(\"image/\")) {\n                if let ClipboardDataType::Data(check_data) = f_dt\n                    && data.eq(check_data)\n                {\n                    trace!(\"Deduped MIME type {mime} to {f_mime}\");\n                    map.insert(\n                        mime.into_boxed_str(),\n                        ClipboardDataType::Alias(f_mime.clone()),\n                    );\n                    dedupted += 1;\n                    continue 'outer;\n                }\n            }\n        }\n        map.insert(mime.into_boxed_str(), ClipboardDataType::Data(data));\n    }\n    trace!(\n        \"Deduplication took {:?}, dedupted {dedupted} entries\",\n        time.elapsed()\n    );\n    (map, image_found)\n}\n"
  },
  {
    "path": "crates/clipboard-lib/src/store/save_text.rs",
    "content": "use crate::config::Config;\nuse crate::store::util::create_storage_path;\nuse crate::store::write::get_storage_writer;\nuse anyhow::Context;\nuse std::fs::File;\nuse std::io::{Cursor, Write};\nuse std::path::Path;\nuse tracing::trace;\n\npub fn store_text(text: &str, config: &Config, cache_dir: &Path) -> anyhow::Result<()> {\n    let compress = text.len() > 100;\n    let mut cursor = Cursor::new(Vec::new());\n    let ext = {\n        let (mut write, ext) = get_storage_writer(&mut cursor, config, compress);\n        write\n            .write_all(text.as_bytes())\n            .context(\"Failed to write text to clipboard\")?;\n        ext\n    };\n    let storage_path = create_storage_path(cache_dir, \"text\", &format!(\"txt.{ext}\"))\n        .context(\"Failed to get storage path for clipboard data\")?;\n    let mut file = File::create(&storage_path).context(\"Failed to create clipboard data file\")?;\n    file.write_all(&cursor.into_inner())\n        .context(\"Failed to write clipboard data\")?;\n\n    trace!(\n        \"Wrote text to {} ({} bytes)\",\n        storage_path.display(),\n        file.metadata().map(|m| m.len()).unwrap_or(0)\n    );\n    Ok(())\n}\n"
  },
  {
    "path": "crates/clipboard-lib/src/store/util.rs",
    "content": "use anyhow::Context;\nuse core_lib::util::get_boot_id;\nuse std::fs;\nuse std::path::{Path, PathBuf};\nuse std::time::{SystemTime, UNIX_EPOCH};\n\n/// # Panics if time went backwards or no `boot_id` is available\npub fn create_storage_path(cache_dir: &Path, path: &str, ext: &str) -> anyhow::Result<PathBuf> {\n    let now_millis = SystemTime::now()\n        .duration_since(UNIX_EPOCH)\n        .context(\"Time went backwards?\")?\n        .as_millis();\n    let get_boot_id = get_boot_id().clone().context(\"Failed to get boot_id\")?;\n    let path = cache_dir\n        .to_path_buf()\n        .join(\"clipboard\")\n        .join(path)\n        .join(get_boot_id);\n    fs::create_dir_all(&path).context(\"Failed to create storage directory\")?;\n    Ok(path.join(format!(\"{now_millis}.{ext}\")))\n}\n"
  },
  {
    "path": "crates/clipboard-lib/src/store/write.rs",
    "content": "use crate::config::{Compression, Config, Encryption};\nuse std::io::Write;\n\npub fn get_storage_writer<'a, I: Write + 'a>(\n    writer: I,\n    config: &Config,\n    compress: bool,\n) -> (Box<dyn Write + 'a>, &'static str) {\n    let base: Box<dyn Write> = match config.encryption {\n        Encryption::None => Box::new(writer),\n        #[cfg(any(feature = \"encrypt_chacha20poly1305\", feature = \"encrypt_aes_gcm\"))]\n        _ => {\n            let config_val = match config.encryption {\n                #[cfg(feature = \"encrypt_chacha20poly1305\")]\n                Encryption::ChaCha20Poly1305 => crate::util::crypt::Config::ChaCha20Poly1305,\n                #[cfg(feature = \"encrypt_aes_gcm\")]\n                Encryption::AesGcm => crate::util::crypt::Config::AesGcm,\n                _ => unreachable!(),\n            };\n            match crate::util::secret_service::get_hyprshell_key() {\n                Ok(key) => Box::new(crate::util::crypt::SecretEncryptWriter::new(\n                    writer, key, config_val,\n                )),\n                Err(err) => {\n                    tracing::warn!(\"Failed to load/generate new encryption key: {err:?}\");\n                    Box::new(writer)\n                }\n            }\n        }\n    };\n    match (compress, config.compression) {\n        (false, _) | (true, Compression::None) => (base, \"raw\"),\n        #[cfg(feature = \"compress_lz4\")]\n        (true, Compression::Lz4) => (\n            Box::new(crate::util::lz4_compressor::LZ4CompressWriter::new(base)),\n            \"lz4\",\n        ),\n        #[cfg(feature = \"compress_brotli\")]\n        (true, Compression::Brotli(level)) => (\n            Box::new(crate::util::brotli_compressor::BrotliCompressWriter::new(\n                base, level,\n            )),\n            \"br\",\n        ),\n        #[cfg(feature = \"compress_zstd\")]\n        (true, Compression::Zstd(level)) => (\n            Box::new(crate::util::zstd_compressor::ZstdCompressWriter::new(\n                base, level,\n            )),\n            \"zstd\",\n        ),\n    }\n}\n"
  },
  {
    "path": "crates/clipboard-lib/src/util/brotli_compressor.rs",
    "content": "use brotli::CompressorWriter;\n\nuse brotli::enc::BrotliEncoderParams;\nuse std::io::Write;\nuse tracing::warn;\n\npub struct BrotliCompressWriter<W: Write> {\n    encoder: CompressorWriter<W>,\n}\n\nimpl<W: Write> BrotliCompressWriter<W> {\n    pub fn new(writer: W, mut level: u8) -> Self {\n        // use 6 as default compression level\n        if level > 11 {\n            warn!(\"Brotli compression level out of range, clamping to 11\");\n            level = 11;\n        }\n        let params = BrotliEncoderParams::default();\n        Self {\n            #[allow(clippy::cast_sign_loss)]\n            encoder: CompressorWriter::new(writer, 4096, u32::from(level), params.lgwin as u32),\n        }\n    }\n}\n\nimpl<W: Write> Write for BrotliCompressWriter<W> {\n    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {\n        self.encoder.write(buf)\n    }\n\n    fn flush(&mut self) -> std::io::Result<()> {\n        self.encoder.flush()\n    }\n}\n"
  },
  {
    "path": "crates/clipboard-lib/src/util/crypt.rs",
    "content": "use anyhow::Context;\nuse crypto_common::Generate;\nuse std::io::Write;\nuse tracing::trace;\n\n#[cfg(feature = \"encrypt_chacha20poly1305\")]\ntype K = chacha20poly1305::Key;\n#[cfg(all(not(feature = \"encrypt_chacha20poly1305\"), feature = \"encrypt_aes_gcm\"))]\ntype K = aes_gcm::Key<aes_gcm::Aes256Gcm>;\n\npub fn generate_new_key() -> anyhow::Result<Vec<u8>> {\n    K::try_generate()\n        .map_err(|_| anyhow::anyhow!(\"Failed to generate new encryption key\"))\n        .map(|k| k.to_vec())\n}\n\npub struct SecretEncryptWriter<W: Write> {\n    key: Vec<u8>,\n    buffer: Vec<u8>,\n    writer: W,\n    config: Config,\n}\n\npub enum Config {\n    #[cfg(feature = \"encrypt_chacha20poly1305\")]\n    ChaCha20Poly1305,\n    #[cfg(feature = \"encrypt_aes_gcm\")]\n    AesGcm,\n}\n\nimpl<W: Write> SecretEncryptWriter<W> {\n    pub const fn new(writer: W, key: Vec<u8>, config: Config) -> Self {\n        Self {\n            buffer: Vec::new(),\n            key,\n            writer,\n            config,\n        }\n    }\n\n    pub fn encrypt(&self, cleartext: &[u8]) -> anyhow::Result<Vec<u8>> {\n        trace!(\"length of cleartext: {}\", cleartext.len());\n        match self.config {\n            #[cfg(feature = \"encrypt_chacha20poly1305\")]\n            Config::ChaCha20Poly1305 => {\n                use chacha20poly1305::{KeyInit, aead::Aead};\n                let nonce = chacha20poly1305::Nonce::try_generate()\n                    .map_err(|_| anyhow::anyhow!(\"Failed to generate nonce\"))?;\n                let cypher = chacha20poly1305::ChaCha20Poly1305::new_from_slice(&self.key)\n                    .context(\"Failed to generate cypher with encryption key\")?;\n                let obsf = cypher\n                    .encrypt(&nonce, cleartext)\n                    .context(\"Encryption failed\")?;\n                let mut nonce = nonce.to_vec();\n                nonce.extend_from_slice(&obsf);\n                Ok(nonce)\n            }\n            #[cfg(feature = \"encrypt_aes_gcm\")]\n            Config::AesGcm => {\n                use aes_gcm::{KeyInit, aead::Aead};\n                let nonce = aes_gcm::Nonce::try_generate()\n                    .map_err(|_| anyhow::anyhow!(\"Failed to generate nonce\"))?;\n                let cypher = aes_gcm::Aes256Gcm::new_from_slice(&self.key)\n                    .context(\"Failed to generate cypher with encryption key\")?;\n                let obsf = cypher\n                    .encrypt(&nonce, cleartext)\n                    .context(\"Encryption failed\")?;\n                let mut nonce = nonce.to_vec();\n                nonce.extend_from_slice(&obsf);\n                Ok(nonce)\n            }\n        }\n    }\n}\n\nimpl<W: Write> Write for SecretEncryptWriter<W> {\n    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {\n        self.buffer.extend_from_slice(buf);\n        Ok(buf.len())\n    }\n\n    fn flush(&mut self) -> std::io::Result<()> {\n        self.writer.flush()\n    }\n}\n\nimpl<W: Write> Drop for SecretEncryptWriter<W> {\n    fn drop(&mut self) {\n        if !self.buffer.is_empty() {\n            if let Ok(encrypted) = self.encrypt(&self.buffer) {\n                trace!(\"Writing {} bytes encrypted data to writer\", encrypted.len());\n                let _ = self.writer.write_all(&encrypted);\n            }\n            self.buffer.clear();\n        }\n        let _ = self.writer.flush();\n    }\n}\n\n// pub fn decrypt(&self, obsf: &[u8]) -> anyhow::Result<Vec<u8>> {\n//     type NonceSize = <ChaCha20Poly1305 as AeadCore>::NonceSize;\n//     let cipher = generate_cypher(&self.key).context(\"Failed to generate cipher\")?;\n//     let (nonce_bytes, ciphertext) = obsf.split_at(NonceSize::to_usize());\n//     let nonce =\n//         Nonce::<ChaCha20Poly1305>::try_from(nonce_bytes).context(\"Failed to parse nonce\")?;\n//     let out = cipher\n//         .decrypt(&nonce, ciphertext)\n//         .context(\"Decryption failed\")?;\n//     Ok(out)\n// }\n"
  },
  {
    "path": "crates/clipboard-lib/src/util/lz4_compressor.rs",
    "content": "use lz4_flex::frame::FrameEncoder;\nuse std::io::Write;\nuse tracing::warn;\n\npub struct LZ4CompressWriter<W: Write> {\n    encoder: FrameEncoder<W>,\n}\n\nimpl<W: Write> LZ4CompressWriter<W> {\n    pub fn new(writer: W) -> Self {\n        Self {\n            encoder: FrameEncoder::new(writer),\n        }\n    }\n}\n\nimpl<W: Write> Write for LZ4CompressWriter<W> {\n    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {\n        self.encoder.write(buf)\n    }\n\n    fn flush(&mut self) -> std::io::Result<()> {\n        self.encoder.flush()\n    }\n}\n\nimpl<W: Write> Drop for LZ4CompressWriter<W> {\n    fn drop(&mut self) {\n        if let Err(err) = self.encoder.try_finish() {\n            warn!(\"Failed to finish compressor: {err:?}\");\n        }\n    }\n}\n"
  },
  {
    "path": "crates/clipboard-lib/src/util/mod.rs",
    "content": "#[cfg(feature = \"compress_brotli\")]\npub mod brotli_compressor;\n#[cfg(any(feature = \"encrypt_chacha20poly1305\", feature = \"encrypt_aes_gcm\"))]\npub mod crypt;\n#[cfg(feature = \"compress_lz4\")]\npub mod lz4_compressor;\n#[cfg(any(feature = \"encrypt_chacha20poly1305\", feature = \"encrypt_aes_gcm\"))]\npub mod secret_service;\n#[cfg(feature = \"compress_zstd\")]\npub mod zstd_compressor;\n"
  },
  {
    "path": "crates/clipboard-lib/src/util/secret_service.rs",
    "content": "use crate::util::crypt::generate_new_key;\nuse anyhow::Context;\nuse secret_service::EncryptionType;\nuse secret_service::blocking::SecretService;\nuse std::collections::HashMap;\nuse std::sync::OnceLock;\nuse tracing::warn;\n\nfn get_secret_service() -> Option<&'static SecretService<'static>> {\n    static SERVICE: OnceLock<Option<SecretService>> = OnceLock::new();\n    SERVICE\n        .get_or_init(|| {\n            SecretService::connect(EncryptionType::Dh).map_or_else(\n                |e| {\n                    warn!(\"Failed to connect to Secret Service: {e}\");\n                    None\n                },\n                Some,\n            )\n        })\n        .as_ref()\n}\n\npub fn get_hyprshell_key() -> anyhow::Result<Vec<u8>> {\n    let service =\n        get_secret_service().ok_or_else(|| anyhow::anyhow!(\"Secret Service not available\"))?;\n    let collection = service\n        .get_default_collection()\n        .context(\"Failed to get default collection\")?;\n    let items =\n        collection.search_items(HashMap::from([(\"application\", \"Hyprshell Clipboard Key\")]))?;\n    let key = if items.is_empty() {\n        // instead generate a new key and insert it into the collection\n        let key = generate_new_key().context(\"Failed to generate new encryption key\")?;\n        collection\n            .create_item(\n                \"hyprshell\",\n                HashMap::from([(\"application\", \"Hyprshell Clipboard Key\")]),\n                &key,\n                true,\n                \"application/octet-stream\",\n            )\n            .context(\"Failed to create new secret service item for hyprshell key\")?;\n        key\n    } else {\n        items[0]\n            .get_secret()\n            .context(\"Failed to get hyprshell key from secret service\")?\n    };\n    Ok(key)\n}\n"
  },
  {
    "path": "crates/clipboard-lib/src/util/zstd_compressor.rs",
    "content": "use std::io::Write;\nuse tracing::warn;\nuse zstd::Encoder;\n\npub struct ZstdCompressWriter<'a, W: Write> {\n    encoder: Encoder<'a, W>,\n}\n\nimpl<W: Write> ZstdCompressWriter<'_, W> {\n    pub fn new(writer: W, mut level: u8) -> Self {\n        // use 16 as default compression level\n        if level > 22 {\n            warn!(\"Zstd compression level out of range, clamping to 22\");\n            level = 22;\n        }\n        Self {\n            encoder: Encoder::new(writer, i32::from(level)).expect(\"Failed to create encoder\"),\n        }\n    }\n}\n\nimpl<W: Write> Write for ZstdCompressWriter<'_, W> {\n    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {\n        self.encoder.write(buf)\n    }\n\n    fn flush(&mut self) -> std::io::Result<()> {\n        self.encoder.flush()\n    }\n}\n\nimpl<W: Write> Drop for ZstdCompressWriter<'_, W> {\n    fn drop(&mut self) {\n        if let Err(err) = self.encoder.do_finish() {\n            warn!(\"Failed to finish compressor: {err:?}\");\n        }\n    }\n}\n"
  },
  {
    "path": "crates/config-edit-lib/Cargo.toml",
    "content": "[package]\nname = \"hyprshell-config-edit-lib\"\ndocumentation = \"https://docs.rs/hyprshell-config-edit-lib\"\nversion = \"4.9.5\"\ndescription = \"A library for editing the config file with a gui\"\nedition.workspace = true\nlicense.workspace = true\nauthors.workspace = true\nkeywords.workspace = true\nrepository.workspace = true\nrust-version.workspace = true\n\n[dependencies]\ntracing.workspace = true\nconfig-lib.workspace = true\ncore-lib.workspace = true\nrelm4.workspace = true\nrelm4-components = \"0.10.0\"\n\n# needed because relm4 version is too old for v1_8\n_adw = { version = \"0.8.1\", package = \"libadwaita\", features = [\"v1_8\"] }\n\n[features]\nlauncher_calc_plugin = []\n\n[lints]\nworkspace = true\n"
  },
  {
    "path": "crates/config-edit-lib/src/components/changes.rs",
    "content": "use crate::flags_csv;\nuse crate::structs::{Config, Plugins};\nuse relm4::adw::ActionRow;\nuse relm4::adw::gtk::SelectionMode;\nuse relm4::adw::prelude::*;\nuse relm4::gtk;\nuse relm4::{ComponentParts, ComponentSender, RelmWidgetExt, SimpleComponent};\nuse std::collections::HashSet;\nuse tracing::trace;\n\n#[derive(Debug)]\npub struct Changes {\n    config: Config,\n    prev_config: Config,\n    list: gtk::ListBox,\n    how_to_use: gtk::TextView,\n}\n\n#[derive(Debug)]\npub enum ChangesInput {\n    SetConfig(Config),\n    SetPrevConfig(Config),\n}\n\n#[derive(Debug)]\npub struct ChangesInit {\n    pub config: Config,\n}\n\n#[derive(Debug)]\npub enum ChangesOutput {\n    ChangesExist(bool),\n}\n\n#[allow(unused_assignments)]\n#[relm4::component(pub)]\nimpl SimpleComponent for Changes {\n    type Init = ChangesInit;\n    type Input = ChangesInput;\n    type Output = ChangesOutput;\n\n    view! {\n        #[root]\n        gtk::Box {\n            set_orientation: gtk::Orientation::Vertical,\n            set_margin_all: 10,\n            set_spacing: 15,\n            #[name=\"list\"]\n            gtk::ListBox {\n                set_show_separators: false,\n                set_halign: gtk::Align::Center,\n                set_valign: gtk::Align::Start,\n                set_hexpand: true,\n                set_selection_mode: SelectionMode::None,\n                set_css_classes: &[\"items-list\", \"boxed-list\"]\n            },\n            #[name=\"how_to_use\"]\n            gtk::TextView {\n                set_editable: false,\n                set_sensitive: false,\n                set_align: gtk::Align::Fill,\n                set_hexpand: true,\n                set_vexpand: true,\n                set_css_classes: &[\"changes-text\"]\n            }\n        }\n    }\n\n    fn init(\n        init: Self::Init,\n        root: Self::Root,\n        _sender: ComponentSender<Self>,\n    ) -> ComponentParts<Self> {\n        #[allow(unused_assignments)]\n        let widgets = view_output!();\n        let model = Self {\n            config: init.config.clone(),\n            prev_config: init.config,\n            list: widgets.list.clone(),\n            how_to_use: widgets.how_to_use.clone(),\n        };\n        ComponentParts { model, widgets }\n    }\n\n    fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>) {\n        trace!(\"changes::update: {message:?}\");\n        match message {\n            ChangesInput::SetConfig(config) => {\n                self.config = config;\n            }\n            ChangesInput::SetPrevConfig(config) => {\n                self.prev_config = config;\n            }\n        }\n        let changes = generate_items(&self.list, &self.config, &self.prev_config);\n\n        let text = config_lib::explain(&(self.config.clone().into()), None, false);\n        self.how_to_use.buffer().set_text(&text);\n\n        sender\n            .output_sender()\n            .emit(ChangesOutput::ChangesExist(changes));\n    }\n}\n\n#[allow(clippy::too_many_lines)]\npub fn generate_items(changes: &gtk::ListBox, config: &Config, prev_config: &Config) -> bool {\n    while let Some(child) = changes.first_child() {\n        changes.remove(&child);\n    }\n\n    match (prev_config.windows.enabled, config.windows.enabled) {\n        (false, false) => {}\n        (true, false) => {\n            add_info(changes, \"Disabled Windows\");\n        }\n        (_, true) => {\n            if !prev_config.windows.enabled {\n                add_info(changes, \"Enabled Windows\");\n            }\n\n            #[allow(clippy::cast_sign_loss)]\n            if (prev_config.windows.scale - config.windows.scale).abs() > 0.001 {\n                add_info_subtitle(\n                    changes,\n                    \"Changed windows scale\",\n                    format!(\"{} -> {}\", prev_config.windows.scale, config.windows.scale),\n                );\n            }\n            if prev_config.windows.items_per_row != config.windows.items_per_row {\n                add_info_subtitle(\n                    changes,\n                    \"Changed windows items per row\",\n                    format!(\n                        \"{} -> {}\",\n                        prev_config.windows.items_per_row, config.windows.items_per_row\n                    ),\n                );\n            }\n            match (\n                prev_config.windows.overview.enabled,\n                config.windows.overview.enabled,\n            ) {\n                (false, false) => {}\n                (true, false) => {\n                    add_info(changes, \"Disabled Overview\");\n                }\n                (_, true) => {\n                    if !prev_config.windows.overview.enabled {\n                        add_info(changes, \"Enabled Overview\");\n                    }\n                    if prev_config.windows.overview.modifier != config.windows.overview.modifier {\n                        add_info_subtitle(\n                            changes,\n                            \"Changed overview modifier\",\n                            format!(\n                                \"{} -> {}\",\n                                prev_config.windows.overview.modifier,\n                                config.windows.overview.modifier\n                            ),\n                        );\n                    }\n                    if prev_config.windows.overview.key != config.windows.overview.key {\n                        add_info_subtitle(\n                            changes,\n                            \"Changed overview key\",\n                            format!(\n                                \"{} -> {}\",\n                                prev_config.windows.overview.key, config.windows.overview.key,\n                            ),\n                        );\n                    }\n                    if prev_config.windows.overview.same_class != config.windows.overview.same_class\n                        || prev_config.windows.overview.current_monitor\n                            != config.windows.overview.current_monitor\n                        || prev_config.windows.overview.current_workspace\n                            != config.windows.overview.current_workspace\n                    {\n                        add_info_subtitle(\n                            changes,\n                            \"Changed overview filter by\",\n                            format!(\n                                \"{} -> {}\",\n                                flags_csv!(\n                                    prev_config.windows.overview,\n                                    same_class,\n                                    current_monitor,\n                                    current_workspace\n                                ),\n                                flags_csv!(\n                                    config.windows.overview,\n                                    same_class,\n                                    current_monitor,\n                                    current_workspace\n                                ),\n                            ),\n                        );\n                    }\n                    if prev_config.windows.overview.launcher.launch_modifier\n                        != config.windows.overview.launcher.launch_modifier\n                    {\n                        add_info_subtitle(\n                            changes,\n                            \"Changed overview launcher launch modifier\",\n                            format!(\n                                \"{} -> {}\",\n                                prev_config.windows.overview.launcher.launch_modifier,\n                                config.windows.overview.launcher.launch_modifier\n                            ),\n                        );\n                    }\n                    if prev_config.windows.overview.launcher.max_items\n                        != config.windows.overview.launcher.max_items\n                    {\n                        add_info_subtitle(\n                            changes,\n                            \"Changed overview launcher max items\",\n                            format!(\n                                \"{} -> {}\",\n                                prev_config.windows.overview.launcher.max_items,\n                                config.windows.overview.launcher.max_items\n                            ),\n                        );\n                    }\n                    if prev_config.windows.overview.launcher.show_when_empty\n                        != config.windows.overview.launcher.show_when_empty\n                    {\n                        add_info_subtitle(\n                            changes,\n                            \"Changed overview launcher show when empty\",\n                            format!(\n                                \"{} -> {}\",\n                                prev_config.windows.overview.launcher.show_when_empty,\n                                config.windows.overview.launcher.show_when_empty\n                            ),\n                        );\n                    }\n                    if prev_config.windows.overview.launcher.width\n                        != config.windows.overview.launcher.width\n                    {\n                        add_info_subtitle(\n                            changes,\n                            \"Changed overview launcher width\",\n                            format!(\n                                \"{} -> {}\",\n                                prev_config.windows.overview.launcher.width,\n                                config.windows.overview.launcher.width\n                            ),\n                        );\n                    }\n                    match (\n                        &prev_config.windows.overview.launcher.default_terminal,\n                        &config.windows.overview.launcher.default_terminal,\n                    ) {\n                        (None, None) => {}\n                        (Some(_), None) => {\n                            add_info(changes, \"Disabled overview launcher default terminal\");\n                        }\n                        (None, Some(dt)) => {\n                            add_info_subtitle(\n                                changes,\n                                \"Enabled overview launcher default terminal\",\n                                format!(\"{dt:?}\"),\n                            );\n                        }\n                        (Some(pdt), Some(cdt)) => {\n                            if pdt != cdt {\n                                add_info_subtitle(\n                                    changes,\n                                    \"Changed overview launcher default terminal\",\n                                    format!(\"{pdt:?} -> {cdt:?}\"),\n                                );\n                            }\n                        }\n                    }\n                    add_plugin_changes(\n                        changes,\n                        &prev_config.windows.overview.launcher.plugins,\n                        &config.windows.overview.launcher.plugins,\n                    );\n                }\n            }\n            match (\n                &prev_config.windows.switch.enabled,\n                &config.windows.switch.enabled,\n            ) {\n                (false, false) => {}\n                (true, false) => {\n                    add_info(changes, \"Disabled Switch view\");\n                }\n                (_, true) => {\n                    if !prev_config.windows.switch.enabled {\n                        add_info(changes, \"Enabled Switch view\");\n                    }\n\n                    if prev_config.windows.switch.key != config.windows.switch.key {\n                        add_info_subtitle(\n                            changes,\n                            \"Changed switch key\",\n                            format!(\n                                \"{} -> {}\",\n                                prev_config.windows.switch.key, config.windows.switch.key\n                            ),\n                        );\n                    }\n                    if prev_config.windows.switch.modifier != config.windows.switch.modifier {\n                        add_info_subtitle(\n                            changes,\n                            \"Changed switch modifier\",\n                            format!(\n                                \"{} -> {}\",\n                                prev_config.windows.switch.modifier, config.windows.switch.modifier\n                            ),\n                        );\n                    }\n                    if prev_config.windows.switch.same_class != config.windows.switch.same_class\n                        || prev_config.windows.switch.current_monitor\n                            != config.windows.switch.current_monitor\n                        || prev_config.windows.switch.current_workspace\n                            != config.windows.switch.current_workspace\n                    {\n                        add_info_subtitle(\n                            changes,\n                            \"Changed switch filter by\",\n                            format!(\n                                \"{} -> {}\",\n                                flags_csv!(\n                                    prev_config.windows.switch,\n                                    same_class,\n                                    current_monitor,\n                                    current_workspace\n                                ),\n                                flags_csv!(\n                                    config.windows.switch,\n                                    same_class,\n                                    current_monitor,\n                                    current_workspace\n                                ),\n                            ),\n                        );\n                    }\n                    if prev_config.windows.switch.switch_workspaces\n                        != config.windows.switch.switch_workspaces\n                    {\n                        add_info_subtitle(\n                            changes,\n                            \"Changed switch switch workspaces\",\n                            format!(\n                                \"{} -> {}\",\n                                prev_config.windows.switch.switch_workspaces,\n                                config.windows.switch.switch_workspaces\n                            ),\n                        );\n                    }\n                }\n            }\n            match (\n                &prev_config.windows.switch_2.enabled,\n                &config.windows.switch_2.enabled,\n            ) {\n                (false, false) => {}\n                (true, false) => {\n                    add_info(changes, \"Disabled Switch 2 view\");\n                }\n                (_, true) => {\n                    if !prev_config.windows.switch_2.enabled {\n                        add_info(changes, \"Enabled Switch 2 view\");\n                    }\n\n                    if prev_config.windows.switch_2.key != config.windows.switch_2.key {\n                        add_info_subtitle(\n                            changes,\n                            \"Changed switch 2 key\",\n                            format!(\n                                \"{} -> {}\",\n                                prev_config.windows.switch_2.key, config.windows.switch_2.key\n                            ),\n                        );\n                    }\n                    if prev_config.windows.switch_2.modifier != config.windows.switch_2.modifier {\n                        add_info_subtitle(\n                            changes,\n                            \"Changed switch 2 modifier\",\n                            format!(\n                                \"{} -> {}\",\n                                prev_config.windows.switch_2.modifier,\n                                config.windows.switch_2.modifier\n                            ),\n                        );\n                    }\n                    if prev_config.windows.switch_2.same_class != config.windows.switch_2.same_class\n                        || prev_config.windows.switch_2.current_monitor\n                            != config.windows.switch_2.current_monitor\n                        || prev_config.windows.switch_2.current_workspace\n                            != config.windows.switch_2.current_workspace\n                    {\n                        add_info_subtitle(\n                            changes,\n                            \"Changed switch 2 filter by\",\n                            format!(\n                                \"{} -> {}\",\n                                flags_csv!(\n                                    prev_config.windows.switch_2,\n                                    same_class,\n                                    current_monitor,\n                                    current_workspace\n                                ),\n                                flags_csv!(\n                                    config.windows.switch_2,\n                                    same_class,\n                                    current_monitor,\n                                    current_workspace\n                                ),\n                            ),\n                        );\n                    }\n                    if prev_config.windows.switch_2.switch_workspaces\n                        != config.windows.switch_2.switch_workspaces\n                    {\n                        add_info_subtitle(\n                            changes,\n                            \"Changed switch 2 switch workspaces\",\n                            format!(\n                                \"{} -> {}\",\n                                prev_config.windows.switch_2.switch_workspaces,\n                                config.windows.switch_2.switch_workspaces\n                            ),\n                        );\n                    }\n                }\n            }\n        }\n    }\n\n    if changes.first_child().is_none() {\n        add_info(changes, \"No changes\");\n        false\n    } else {\n        true\n    }\n}\n\n#[allow(clippy::too_many_lines)]\nfn add_plugin_changes(changes: &gtk::ListBox, prev: &Plugins, current: &Plugins) {\n    match (&prev.applications.enabled, &current.applications.enabled) {\n        (false, false) => {}\n        (true, false) => {\n            add_info(changes, \"Disabled Application Plugin\");\n        }\n        (_, true) => {\n            if !prev.applications.enabled {\n                add_info(changes, \"Enabled Application Plugin\");\n            }\n\n            if prev.applications.show_execs != current.applications.show_execs {\n                add_info_subtitle(\n                    changes,\n                    \"Changed application plugin show execs\",\n                    format!(\n                        \"{} -> {}\",\n                        prev.applications.show_execs, current.applications.show_execs\n                    ),\n                );\n            }\n\n            if prev.applications.show_actions_submenu != current.applications.show_actions_submenu {\n                add_info_subtitle(\n                    changes,\n                    \"Changed application plugin show actions\",\n                    format!(\n                        \"{} -> {}\",\n                        prev.applications.show_actions_submenu,\n                        current.applications.show_actions_submenu\n                    ),\n                );\n            }\n\n            if prev.applications.run_cache_weeks != current.applications.run_cache_weeks {\n                add_info_subtitle(\n                    changes,\n                    \"Changed application plugin run cache weeks\",\n                    format!(\n                        \"{} -> {}\",\n                        prev.applications.run_cache_weeks, current.applications.run_cache_weeks\n                    ),\n                );\n            }\n        }\n    }\n\n    match (&prev.terminal.enabled, &current.terminal.enabled) {\n        (true, false) => {\n            add_info(changes, \"Disabled Terminal Plugin\");\n        }\n        (false, true) => {\n            add_info(changes, \"Enabled Terminal Plugin\");\n        }\n        _ => {}\n    }\n\n    match (&prev.shell.enabled, &current.shell.enabled) {\n        (true, false) => {\n            add_info(changes, \"Disabled Shell Plugin\");\n        }\n        (false, true) => {\n            add_info(changes, \"Enabled Shell Plugin\");\n        }\n        _ => {}\n    }\n\n    match (&prev.calc.enabled, &current.calc.enabled) {\n        (true, false) => {\n            add_info(changes, \"Disabled Calculator Plugin\");\n        }\n        (false, true) => {\n            add_info(changes, \"Enabled Calculator Plugin\");\n        }\n        _ => {}\n    }\n\n    match (&prev.path.enabled, &current.path.enabled) {\n        (true, false) => {\n            add_info(changes, \"Disabled Path Plugin\");\n        }\n        (false, true) => {\n            add_info(changes, \"Enabled Path Plugin\");\n        }\n        _ => {}\n    }\n\n    match (&prev.websearch.enabled, &current.websearch.enabled) {\n        (false, false) => {}\n        (true, false) => {\n            add_info(changes, \"Disabled Websearch Plugin\");\n        }\n        (_, true) => {\n            if !prev.websearch.enabled {\n                add_info(changes, \"Enabled Websearch Plugin\");\n            }\n\n            let prev_engines = &prev.websearch.engines;\n            let cur_engines = &current.websearch.engines;\n\n            let prev_keys: HashSet<_> = prev_engines.iter().map(|e| e.key).collect();\n            let cur_keys: HashSet<_> = cur_engines.iter().map(|e| e.key).collect();\n\n            for e in cur_engines.iter().filter(|e| !prev_keys.contains(&e.key)) {\n                add_info_subtitle(\n                    changes,\n                    \"Added Websearch engine\",\n                    format!(\"{} ({})\", e.name, e.key),\n                );\n            }\n\n            for e in prev_engines.iter().filter(|e| !cur_keys.contains(&e.key)) {\n                add_info_subtitle(\n                    changes,\n                    \"Removed Websearch engine\",\n                    format!(\"{} ({})\", e.name, e.key),\n                );\n            }\n        }\n    }\n}\n\nfn add_info(changes: &gtk::ListBox, text: &str) {\n    let label = ActionRow::builder().title(text).build();\n    changes.append(&label);\n}\n\nfn add_info_subtitle(changes: &gtk::ListBox, text: &str, subtitle: String) {\n    let label = ActionRow::builder().title(text).subtitle(subtitle).build();\n    changes.append(&label);\n}\n"
  },
  {
    "path": "crates/config-edit-lib/src/components/footer.rs",
    "content": "use relm4::adw::prelude::*;\nuse relm4::gtk::Orientation;\nuse relm4::{ComponentParts, ComponentSender, SimpleComponent, gtk};\nuse std::path::Path;\nuse tracing::trace;\n\n#[derive(Debug)]\npub struct Footer {\n    config_file: Box<Path>,\n    changes: bool,\n    generate: bool,\n}\n\n#[derive(Debug)]\npub enum FooterInput {\n    ChangesExist(bool),\n    GenerateMode(bool),\n}\n\n#[derive(Debug)]\npub struct FooterInit {\n    pub config_file: Box<Path>,\n}\n\n#[derive(Debug)]\npub enum FooterOutput {\n    Close,\n    Save,\n    Reset,\n    Reload,\n    Abort,\n}\n\n#[relm4::component(pub)]\nimpl SimpleComponent for Footer {\n    type Init = FooterInit;\n    type Input = FooterInput;\n    type Output = FooterOutput;\n\n    view! {\n        gtk::ActionBar  {\n            #[wrap(Some)]\n            set_center_widget = &gtk::Box {\n                set_spacing: 20,\n                set_hexpand: true,\n                set_css_classes: &[\"footer\"],\n                set_orientation: Orientation::Horizontal,\n                gtk::LinkButton {\n                    set_label: &format!(\"Hyprshell v{}\", env!(\"CARGO_PKG_VERSION\")),\n                    set_uri: &format!(\"https://github.com/H3rmt/hyprshell/tree/v{}\", env!(\"CARGO_PKG_VERSION\")),\n                },\n                gtk::Box {\n                    set_spacing: 10,\n                    set_hexpand: true,\n                    set_halign: gtk::Align::End,\n                    set_orientation: Orientation::Horizontal,\n                    gtk::Button {\n                        set_label: \"Reload from Disk\",\n                        #[watch]\n                        set_visible: !model.generate,\n                        set_sensitive: true,\n                        set_css_classes: &[\"destructive-action\"],\n                        connect_clicked[sender] => move |_| sender.output_sender().emit(FooterOutput::Reload),\n                    },\n                    gtk::Button {\n                        set_label: \"Reset\",\n                        #[watch]\n                        set_visible: !model.generate,\n                        #[watch]\n                        set_sensitive: model.changes,\n                        set_css_classes: &[\"destructive-action\"],\n                        connect_clicked[sender] => move |_| sender.output_sender().emit(FooterOutput::Reset),\n                    },\n                    gtk::Button {\n                        set_label: \"Save Changes\",\n                        #[watch]\n                        set_visible: !model.generate,\n                        #[watch]\n                        set_sensitive: model.changes,\n                        set_css_classes: &[\"suggested-action\"],\n                        set_tooltip_text: Some(&format!(\"Config file: {}\", model.config_file.display())),\n                        connect_clicked[sender] => move |_| sender.output_sender().emit(FooterOutput::Save),\n                    },\n                    gtk::Button {\n                        set_label: \"Abort Generate\",\n                        #[watch]\n                        set_visible: model.generate,\n                        set_css_classes: &[\"destructive-action\"],\n                        connect_clicked[sender] => move |_| sender.output_sender().emit(FooterOutput::Abort),\n                    },\n                    gtk::Button {\n                        set_label: \"Close\",\n                        set_css_classes: &[\"destructive-action\"],\n                        connect_clicked[sender] => move |_| sender.output_sender().emit(FooterOutput::Close),\n                    }\n                }\n            }\n        }\n    }\n\n    fn init(\n        init: Self::Init,\n        root: Self::Root,\n        sender: ComponentSender<Self>,\n    ) -> ComponentParts<Self> {\n        let model = Self {\n            config_file: init.config_file,\n            changes: false,\n            generate: false,\n        };\n        let widgets = view_output!();\n        ComponentParts { model, widgets }\n    }\n\n    fn update(&mut self, message: Self::Input, _sender: ComponentSender<Self>) {\n        trace!(\"footer::update: {message:?}\");\n        match message {\n            FooterInput::ChangesExist(changes) => {\n                self.changes = changes;\n            }\n            FooterInput::GenerateMode(generate) => {\n                self.generate = generate;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "crates/config-edit-lib/src/components/generate/main.rs",
    "content": "use crate::components::generate::step0::{Step0, Step0Init, Step0Input};\nuse crate::components::generate::step1::{LauncherPlugins, Step1, Step1Init, Step1Input};\nuse crate::components::generate::step2::{Step2, Step2Init, Step2Input};\nuse crate::components::generate::step3::{SearchEngines, Step3, Step3Init, Step3Input};\nuse crate::components::generate::step4::{Step4, Step4Init, Step4Input};\nuse crate::structs::ConfigModifier;\nuse crate::util::{ScrollToPosition, default_config};\nuse config_lib::SearchEngine;\nuse relm4::gtk::prelude::{BoxExt, ButtonExt, OrientableExt, WidgetExt};\nuse relm4::gtk::{Align, Justification};\nuse relm4::{\n    Component, ComponentController, ComponentParts, ComponentSender, Controller, RelmWidgetExt,\n    SimpleComponent,\n};\nuse relm4::{adw, gtk};\nuse std::path::Path;\nuse tracing::trace;\n\n#[derive(Debug)]\npub struct Generate {\n    themes_carousel: adw::Carousel,\n\n    step: usize,\n    step_boxes: Vec<gtk::Box>,\n    explain_label: gtk::Label,\n\n    step0: Controller<Step0>,\n    step0_data: Option<(ConfigModifier, String)>,\n    step1: Controller<Step1>,\n    step1_data: LauncherPlugins,\n    step2: Controller<Step2>,\n    step2_data: Option<String>,\n    step3: Controller<Step3>,\n    step3_data: SearchEngines,\n    step4: Controller<Step4>,\n    step4_data: Option<(ConfigModifier, String)>,\n}\n\n#[derive(Debug)]\npub enum GenerateInput {\n    Start,\n    Advance(usize),\n    Back(usize),\n    SetStep0(Option<(ConfigModifier, String)>),\n    SetStep1(LauncherPlugins),\n    SetStep2(Option<String>),\n    SetStep3(SearchEngines),\n    SetStep4(Option<(ConfigModifier, String)>),\n}\n\n#[derive(Debug)]\npub struct GenerateInit {\n    pub system_data_dir: Box<Path>,\n}\n\n#[derive(Debug)]\npub enum GenerateOutput {\n    Finish(crate::Config),\n}\n\nstruct Out {\n    overview: Option<(ConfigModifier, String)>,\n    launcher: LauncherPlugins,\n    default_terminal: Option<String>,\n    launcher_websearch_plugin: SearchEngines,\n    switch: Option<(ConfigModifier, String)>,\n}\n\nconst MAX_STEP: usize = 5;\n\n#[allow(unused_assignments)]\n#[relm4::component(pub)]\nimpl SimpleComponent for Generate {\n    type Init = GenerateInit;\n    type Input = GenerateInput;\n    type Output = GenerateOutput;\n\n    view! {\n        gtk::Box {\n            set_orientation: gtk::Orientation::Vertical,\n            set_spacing: 10,\n            gtk::Box {\n                #[local_ref]\n                themes_carousel -> adw::Carousel {\n                    set_visible: true,\n                    #[local_ref]\n                    step0 -> gtk::Box {},\n                    #[local_ref]\n                    step1 -> gtk::Box {},\n                    #[local_ref]\n                    step2 -> gtk::Box {},\n                    #[local_ref]\n                    step3 -> gtk::Box {},\n                    #[local_ref]\n                    step4 -> gtk::Box {},\n                    #[local_ref]\n                    step5 -> gtk::Box {},\n                },\n                set_expand: true,\n                set_spacing: 20,\n                set_margin_all: 20,\n                #[transition = \"SlideUpDown\"]\n                #[name=\"step0_stack\"]\n                match model.step {\n                    0 => *model.step0.widget(),\n                    1 => *model.step1.widget(),\n                    2 => *model.step2.widget(),\n                    3 => *model.step3.widget(),\n                    4 => *model.step4.widget(),\n                    5 => {\n                        gtk::Box {\n                            set_orientation: gtk::Orientation::Vertical,\n                            set_hexpand: true,\n                            set_spacing: 20,\n                            #[local_ref]\n                            explain_label -> gtk::Label {\n                                set_css_classes: &[],\n                                set_align: Align::Center,\n                                set_justify: Justification::Left,\n                            },\n                        }\n                    },\n                    _ => gtk::Label::new(Some(\"INVALID GENERATE STEP\")) {}\n                },\n            },\n            adw::CarouselIndicatorDots {\n                set_carousel: Some(themes_carousel),\n            },\n            gtk::Box {\n                set_spacing: 25,\n                set_halign: Align::Center,\n                gtk::Button {\n                    set_label: \"Back\",\n                    set_css_classes: &[\"suggested-action\", \"pill\"],\n                    connect_clicked[sender] => move |_| sender.input(GenerateInput::Back(1)),\n                },\n                gtk::Button {\n                    #[watch]\n                    set_label: if model.step == MAX_STEP { \"Finish\" } else { \"Next\" },\n                    set_css_classes: &[\"suggested-action\", \"pill\"],\n                    connect_clicked[sender] => move |_| sender.input(GenerateInput::Advance(1)),\n                }\n            }\n        }\n    }\n\n    fn init(\n        init: Self::Init,\n        root: Self::Root,\n        sender: ComponentSender<Self>,\n    ) -> ComponentParts<Self> {\n        let path = init.system_data_dir.join(\"setup_preview\");\n        let step0 = Step0::builder()\n            .launch(Step0Init {\n                system_data_dir: path.clone().into_boxed_path(),\n            })\n            .forward(sender.input_sender(), GenerateInput::SetStep0);\n        let step1 = Step1::builder()\n            .launch(Step1Init {})\n            .forward(sender.input_sender(), GenerateInput::SetStep1);\n        let step2 = Step2::builder()\n            .launch(Step2Init {})\n            .forward(sender.input_sender(), GenerateInput::SetStep2);\n        let step3 = Step3::builder()\n            .launch(Step3Init {})\n            .forward(sender.input_sender(), GenerateInput::SetStep3);\n        let step4 = Step4::builder()\n            .launch(Step4Init {\n                system_data_dir: path.into_boxed_path(),\n            })\n            .forward(sender.input_sender(), GenerateInput::SetStep4);\n\n        let themes_carousel = adw::Carousel::builder().build();\n        let mut step_boxes = vec![];\n        for _ in 0..=MAX_STEP {\n            step_boxes.push(gtk::Box::builder().build());\n        }\n\n        let explain_label = gtk::Label::default();\n\n        let model = Self {\n            step: 0,\n            step_boxes,\n            step0,\n            step0_data: None,\n            step1,\n            step1_data: LauncherPlugins::default(),\n            step2,\n            step2_data: None,\n            step3,\n            step3_data: SearchEngines::default(),\n            step4,\n            step4_data: None,\n            explain_label,\n            themes_carousel,\n        };\n\n        let themes_carousel = &model.themes_carousel;\n\n        let step0 = &model.step_boxes[0];\n        let step1 = &model.step_boxes[1];\n        let step2 = &model.step_boxes[2];\n        let step3 = &model.step_boxes[3];\n        let step4 = &model.step_boxes[4];\n        let step5 = &model.step_boxes[5];\n        let explain_label = &model.explain_label;\n        let widgets = view_output!();\n\n        widgets.step0_stack.set_transition_duration(500);\n        ComponentParts { model, widgets }\n    }\n\n    #[allow(clippy::too_many_lines)]\n    fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>) {\n        trace!(\"launcher::update: {message:?}\");\n        match message {\n            GenerateInput::Advance(by) => {\n                if self.step + by <= MAX_STEP {\n                    self.step += by;\n                    self.themes_carousel.scroll_to_pos(self.step, false);\n                } else {\n                    let conf = Out {\n                        overview: self.step0_data.clone(),\n                        launcher: self.step1_data,\n                        default_terminal: self.step2_data.clone(),\n                        launcher_websearch_plugin: self.step3_data,\n                        switch: self.step4_data.clone(),\n                    }\n                    .into();\n                    sender.output_sender().emit(GenerateOutput::Finish(conf));\n                }\n                match self.step {\n                    // advanced to the launcher setup\n                    1 => {\n                        // skip launcher config if overview disabled\n                        if self.step0_data.is_none() {\n                            sender.input(GenerateInput::Advance(3));\n                        }\n                    }\n                    // advanced to websearch plugin\n                    3 => {\n                        // skip websearch plugin config if websearch disabled\n                        if !self.step1_data.search_the_web {\n                            sender.input(GenerateInput::Advance(1));\n                        }\n                    }\n                    5 => {\n                        let conf: crate::Config = Out {\n                            overview: self.step0_data.clone(),\n                            launcher: self.step1_data,\n                            default_terminal: self.step2_data.clone(),\n                            launcher_websearch_plugin: self.step3_data,\n                            switch: self.step4_data.clone(),\n                        }\n                        .into();\n                        let conf: config_lib::Config = conf.into();\n                        let explanation = config_lib::explain(&conf, None, false);\n                        self.explain_label.set_label(&explanation);\n                    }\n                    _ => {}\n                }\n            }\n            GenerateInput::Back(by) => {\n                #[allow(clippy::cast_possible_wrap)]\n                if (self.step as i64) - (by as i64) >= 0 {\n                    self.step -= by;\n                    self.themes_carousel.scroll_to_pos(self.step, false);\n                }\n                match self.step {\n                    // stepped back to launcher setup\n                    3 => {\n                        // skip launcher config if overview disabled\n                        if self.step0_data.is_none() {\n                            sender.input(GenerateInput::Back(3));\n                        }\n                        // skip websearch plugin config if websearch disabled\n                        if !self.step1_data.search_the_web {\n                            sender.input(GenerateInput::Back(1));\n                        }\n                    }\n                    _ => {}\n                }\n            }\n            GenerateInput::Start => {\n                self.step = 0;\n                self.themes_carousel.scroll_to_pos(self.step, false);\n\n                let default = crate::Config::from(default_config());\n                self.step0_data = Some((\n                    default.windows.overview.modifier,\n                    default.windows.overview.key,\n                ));\n                self.step1_data = LauncherPlugins::default();\n                self.step2_data = None;\n                self.step3_data = SearchEngines::default();\n                self.step4_data =\n                    Some((default.windows.switch.modifier, default.windows.switch.key));\n                self.step0\n                    .emit(Step0Input::SetData(self.step0_data.clone()));\n                self.step1.emit(Step1Input::SetData(self.step1_data));\n                self.step2\n                    .emit(Step2Input::SetData(self.step2_data.clone()));\n                self.step3.emit(Step3Input::SetData(self.step3_data));\n                self.step4\n                    .emit(Step4Input::SetData(self.step4_data.clone()));\n            }\n            GenerateInput::SetStep0(data) => {\n                self.step0_data = data;\n            }\n            GenerateInput::SetStep1(data) => {\n                self.step1_data = data;\n            }\n            GenerateInput::SetStep2(step) => {\n                self.step2_data = step;\n            }\n            GenerateInput::SetStep3(data) => {\n                self.step3_data = data;\n            }\n            GenerateInput::SetStep4(data) => {\n                self.step4_data = data;\n            }\n        }\n    }\n}\n\nimpl From<Out> for crate::Config {\n    fn from(val: Out) -> Self {\n        let mut config = Self::from(default_config());\n        if let Some(overview) = &val.overview {\n            config.windows.overview.enabled = true;\n            config.windows.overview.modifier = overview.0;\n            config.windows.overview.key.clone_from(&overview.1);\n\n            config\n                .windows\n                .overview\n                .launcher\n                .plugins\n                .applications\n                .enabled = val.launcher.launch_applications;\n            config.windows.overview.launcher.plugins.terminal.enabled =\n                val.launcher.run_commands_in_terminal;\n            config.windows.overview.launcher.plugins.shell.enabled =\n                val.launcher.run_commands_in_background;\n            config.windows.overview.launcher.plugins.websearch.enabled =\n                val.launcher.search_the_web;\n            config.windows.overview.launcher.plugins.calc.enabled =\n                val.launcher.calculate_math_expressions;\n            config.windows.overview.launcher.plugins.actions.enabled = val.launcher.run_actions;\n\n            config.windows.overview.launcher.default_terminal = val.default_terminal;\n\n            let mut vec = vec![];\n            if val.launcher_websearch_plugin.google {\n                vec.push(WEB_SEARCH_ENGINES\n                    .iter()\n                    .find(|(n, _)| *n == \"Google\")\n                    .expect(\"engine not found: Google\")\n                    .1());\n            }\n            if val.launcher_websearch_plugin.startpage {\n                vec.push(WEB_SEARCH_ENGINES\n                    .iter()\n                    .find(|(n, _)| *n == \"Startpage\")\n                    .expect(\"engine not found: Startpage\")\n                    .1());\n            }\n            if val.launcher_websearch_plugin.duckduckgo {\n                vec.push(WEB_SEARCH_ENGINES\n                    .iter()\n                    .find(|(n, _)| *n == \"DuckDuckGo\")\n                    .expect(\"engine not found: DuckDuckGo\")\n                    .1());\n            }\n            if val.launcher_websearch_plugin.bing {\n                vec.push(WEB_SEARCH_ENGINES\n                    .iter()\n                    .find(|(n, _)| *n == \"Bing\")\n                    .expect(\"engine not found: Bing\")\n                    .1());\n            }\n            if val.launcher_websearch_plugin.wikipedia {\n                vec.push(WEB_SEARCH_ENGINES\n                    .iter()\n                    .find(|(n, _)| *n == \"Wikipedia\")\n                    .expect(\"engine not found: Wikipedia\")\n                    .1());\n            }\n            if val.launcher_websearch_plugin.chatgpt {\n                vec.push(WEB_SEARCH_ENGINES\n                    .iter()\n                    .find(|(n, _)| *n == \"ChatGpt\")\n                    .expect(\"engine not found: ChatGpt\")\n                    .1());\n            }\n            if val.launcher_websearch_plugin.youtube {\n                vec.push(WEB_SEARCH_ENGINES\n                    .iter()\n                    .find(|(n, _)| *n == \"YouTube\")\n                    .expect(\"engine not found: YouTube\")\n                    .1());\n            }\n            if val.launcher_websearch_plugin.reddit {\n                vec.push(WEB_SEARCH_ENGINES\n                    .iter()\n                    .find(|(n, _)| *n == \"Reddit\")\n                    .expect(\"engine not found: Reddit\")\n                    .1());\n            }\n            config.windows.overview.launcher.plugins.websearch.engines = vec;\n        } else {\n            config.windows.overview.enabled = false;\n        }\n\n        if let Some(switch) = &val.switch {\n            config.windows.switch.enabled = true;\n            config.windows.switch.modifier = switch.0;\n            config.windows.switch.key.clone_from(&switch.1);\n        } else {\n            config.windows.switch.enabled = false;\n        }\n        config\n    }\n}\n\n#[allow(clippy::type_complexity)]\npub const WEB_SEARCH_ENGINES: &[(&str, fn() -> SearchEngine)] = &[\n    (\"Google\", || SearchEngine {\n        url: \"https://www.google.com/search?q={}\".into(),\n        name: \"Google\".into(),\n        key: 'g',\n    }),\n    (\"Startpage\", || SearchEngine {\n        url: \"https://www.startpage.com/sp/search?query={}\".into(),\n        name: \"Startpage\".into(),\n        key: 's',\n    }),\n    (\"DuckDuckGo\", || SearchEngine {\n        url: \"https://duckduckgo.com/?q={}\".into(),\n        name: \"DuckDuckGo\".into(),\n        key: 'd',\n    }),\n    (\"Bing\", || SearchEngine {\n        url: \"https://www.bing.com/search?q={}\".into(),\n        name: \"Bing\".into(),\n        key: 'b',\n    }),\n    (\"Wikipedia\", || SearchEngine {\n        url: \"https://en.wikipedia.org/wiki/Special:Search?search={}\".into(),\n        name: \"Wikipedia\".into(),\n        key: 'w',\n    }),\n    (\"ChatGpt\", || SearchEngine {\n        url: \"https://chatgpt.com/?q={}\".into(),\n        name: \"ChatGpt\".into(),\n        key: 'c',\n    }),\n    (\"YouTube\", || SearchEngine {\n        url: \"https://www.youtube.com/results?search_query={}\".into(),\n        name: \"YouTube\".into(),\n        key: 'y',\n    }),\n    (\"Reddit\", || SearchEngine {\n        url: \"https://www.reddit.com/search?q={}\".into(),\n        name: \"Reddit\".into(),\n        key: 'r',\n    }),\n];\n"
  },
  {
    "path": "crates/config-edit-lib/src/components/generate/mod.rs",
    "content": "mod main;\npub use main::*;\nmod step0;\nmod step1;\nmod step2;\nmod step3;\nmod step4;\n"
  },
  {
    "path": "crates/config-edit-lib/src/components/generate/step0.rs",
    "content": "use crate::components::shortcut_dialog::{\n    KeyboardShortcut, KeyboardShortcutInit, KeyboardShortcutInput, KeyboardShortcutOutput,\n};\nuse crate::structs::ConfigModifier;\nuse crate::util::{SelectRow, mod_key_to_string};\nuse relm4::adw::gtk::{Align, Justification};\nuse relm4::adw::prelude::*;\nuse relm4::gtk::{SelectionMode, gio};\nuse relm4::{\n    Component, ComponentController, ComponentParts, ComponentSender, Controller, RelmWidgetExt,\n    SimpleComponent, WidgetRef,\n};\nuse relm4::{adw, gtk};\nuse std::path::Path;\nuse tracing::trace;\n\n#[derive(Debug)]\npub struct Step0 {\n    keyboard_shortcut: Controller<KeyboardShortcut>,\n    list_box: gtk::ListBox,\n    button: adw::ButtonRow,\n}\n\n#[derive(Debug)]\npub enum Step0Input {\n    // external set method\n    SetData(Option<(ConfigModifier, String)>),\n    // internal set method\n    ISetData(Option<(ConfigModifier, String)>),\n    OpenKeyboardShortcut(gtk::Widget),\n}\n\n#[derive(Debug)]\npub struct Step0Init {\n    pub system_data_dir: Box<Path>,\n}\n\n#[allow(unused_assignments)]\n#[relm4::component(pub)]\nimpl SimpleComponent for Step0 {\n    type Init = Step0Init;\n    type Input = Step0Input;\n    type Output = Option<(ConfigModifier, String)>;\n\n    view! {\n        #[root]\n        gtk::Box {\n            set_orientation: gtk::Orientation::Vertical,\n            set_hexpand: true,\n            set_spacing: 20,\n            gtk::Label::new(Some(\"Key combination to open the overview and launcher\")) {\n                set_css_classes: &[\"title-1\"],\n                set_align: Align::Center,\n                set_justify: Justification::Center,\n            },\n            #[local_ref]\n            list_box -> gtk::ListBox {\n                set_halign: Align::Center,\n                set_valign: Align::Start,\n                set_hexpand: true,\n                set_selection_mode: SelectionMode::Single,\n                set_css_classes: &[\"boxed-list\", \"generate-min-width\"],\n                connect_row_activated[sender] => move |_, row| {\n                    if let Some(wdg) = row.downcast_ref::<adw::ActionRow>() {\n                        let title = wdg.title().to_string();\n                        trace!(\"press title: {title}\");\n                        match &*title {\n                            \"Disabled\" => {\n                                sender.input(Step0Input::ISetData(None));\n                            }\n                            \"Super\" => {\n                                sender.input(Step0Input::ISetData(Some((ConfigModifier::Super, \"Super_L\".to_string()))));\n                            }\n                            \"Super + Tab\" => {\n                                sender.input(Step0Input::ISetData(Some((ConfigModifier::Super, \"Tab\".to_string()))));\n                            }\n                            \"Ctrl\" => {\n                                sender.input(Step0Input::ISetData(Some((ConfigModifier::Ctrl, \"Ctrl_L\".to_string()))));\n                            }\n                            \"Ctrl + Tab\" => {\n                                sender.input(Step0Input::ISetData(Some((ConfigModifier::Ctrl, \"Tab\".to_string()))));\n                            }\n                            \"Alt\" => {\n                                sender.input(Step0Input::ISetData(Some((ConfigModifier::Alt, \"Alt_L\".to_string()))));\n                            }\n                            \"Alt + Tab\" => {\n                                sender.input(Step0Input::ISetData(Some((ConfigModifier::Alt, \"Tab\".to_string()))));\n                            }\n                            _ => {}\n                        }\n                    }\n                },\n                adw::ActionRow {\n                    set_title: \"Disabled\",\n                    set_activatable: true,\n                },\n                adw::ActionRow {\n                    set_title: \"Super\",\n                    set_activatable: true,\n                },\n                adw::ActionRow {\n                    set_title: \"Super + Tab\",\n                    set_activatable: true,\n                },\n                adw::ActionRow {\n                    set_title: \"Ctrl\",\n                    set_activatable: true,\n                },\n                adw::ActionRow {\n                    set_title: \"Ctrl + Tab\",\n                    set_activatable: true,\n                },\n                adw::ActionRow {\n                    set_title: \"Alt\",\n                    set_activatable: true,\n                },\n                adw::ActionRow {\n                    set_title: \"Alt + Tab\",\n                    set_activatable: true,\n                },\n                #[local_ref]\n                button -> adw::ButtonRow {\n                    connect_activated[sender] => move |b| {\n                        trace!(\"Generate: step0_keyboard_button toggled\");\n                        sender.input(Step0Input::OpenKeyboardShortcut(b.widget_ref().clone()));\n                    }\n                },\n            },\n            gtk::Picture {\n                set_file: Some(&gio::File::for_path(init.system_data_dir.join(\"00_overview.png\"))),\n                set_css_classes: &[\"theme-image\"],\n                set_vexpand: true,\n                set_hexpand: false,\n                set_valign: Align::Fill,\n                set_halign: Align::Center,\n            },\n            gtk::Label::new(Some(\"similar to gnome's overview\\nShows all apps in a miniature view, allows to switch using arrow keys or tab.\")) {\n                set_css_classes: &[\"title-4\"],\n                set_justify: Justification::Center,\n                set_vexpand: true,\n                set_valign: Align::End,\n            },\n        }\n    }\n\n    fn init(\n        init: Self::Init,\n        root: Self::Root,\n        sender: ComponentSender<Self>,\n    ) -> ComponentParts<Self> {\n        let ins = sender.input_sender().clone();\n        let keyboard_shortcut = KeyboardShortcut::builder()\n            .launch(KeyboardShortcutInit {\n                label: Some(\"Custom\".to_string()),\n                icon: None,\n                init: None,\n            })\n            .connect_receiver(move |_send, out| match out {\n                KeyboardShortcutOutput::SetKey(r#mod, key) => {\n                    // updates the label\n                    ins.emit(Step0Input::ISetData(Some((r#mod, key))));\n                }\n                _ => {}\n            });\n\n        let list_box = gtk::ListBox::default();\n        let button = adw::ButtonRow::default();\n        let model = Self {\n            keyboard_shortcut,\n            button: button.clone(),\n            list_box: list_box.clone(),\n        };\n\n        let widgets = view_output!();\n        ComponentParts { model, widgets }\n    }\n\n    fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>) {\n        trace!(\"launcher::step0:update: {message:?}\");\n        match message {\n            Step0Input::ISetData(data) => {\n                sender.input(Step0Input::SetData(data.clone()));\n                sender.output_sender().emit(data);\n            }\n            Step0Input::SetData(data) => {\n                self.list_box\n                    .select_row_index(match data.as_ref().map(|(a, b)| (a, b.as_str())) {\n                        None => 0,\n                        Some((ConfigModifier::Super, \"Super_L\")) => 1,\n                        Some((ConfigModifier::Super, \"Tab\")) => 2,\n                        Some((ConfigModifier::Ctrl, \"Ctrl_L\")) => 3,\n                        Some((ConfigModifier::Ctrl, \"Tab\")) => 4,\n                        Some((ConfigModifier::Alt, \"Alt_L\")) => 5,\n                        Some((ConfigModifier::Alt, \"Tab\")) => 6,\n                        _ => 7,\n                    });\n                self.button.set_title(&format!(\n                    \"Custom: {}\",\n                    if data.is_some()\n                        && data != Some((ConfigModifier::Super, \"Super_L\".to_string()))\n                        && data != Some((ConfigModifier::Super, \"Tab\".to_string()))\n                        && data != Some((ConfigModifier::Ctrl, \"Ctrl_L\".to_string()))\n                        && data != Some((ConfigModifier::Ctrl, \"Tab\".to_string()))\n                        && data != Some((ConfigModifier::Alt, \"Alt_L\".to_string()))\n                        && data != Some((ConfigModifier::Alt, \"Tab\".to_string()))\n                    {\n                        data.as_ref()\n                            .map(|(r#mod, key)| mod_key_to_string(*r#mod, key))\n                            .unwrap_or_default()\n                    } else {\n                        String::new()\n                    }\n                ));\n            }\n            Step0Input::OpenKeyboardShortcut(widget) => {\n                self.keyboard_shortcut\n                    .emit(KeyboardShortcutInput::ShowKeyboardShortcutDialog(\n                        None,\n                        Some(widget),\n                    ));\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "crates/config-edit-lib/src/components/generate/step1.rs",
    "content": "use relm4::adw::prelude::*;\nuse relm4::gtk::{Align, Justification, SelectionMode};\nuse relm4::{ComponentParts, ComponentSender, RelmWidgetExt, SimpleComponent};\nuse relm4::{adw, gtk};\nuse tracing::trace;\n\n#[derive(Debug, Copy, Clone)]\npub struct LauncherPlugins {\n    pub launch_applications: bool,\n    pub run_commands_in_background: bool,\n    pub run_commands_in_terminal: bool,\n    pub search_the_web: bool,\n    pub calculate_math_expressions: bool,\n    pub run_actions: bool,\n}\n\nimpl Default for LauncherPlugins {\n    fn default() -> Self {\n        Self {\n            launch_applications: true,\n            run_commands_in_background: false,\n            run_commands_in_terminal: true,\n            search_the_web: false,\n            calculate_math_expressions: true,\n            run_actions: true,\n        }\n    }\n}\n\n#[derive(Debug)]\npub struct Step1 {\n    launch_apps: adw::SwitchRow,\n    run_commands_in_background: adw::SwitchRow,\n    run_commands_in_terminal: adw::SwitchRow,\n    search_the_web: adw::SwitchRow,\n    calculate_math_expressions: adw::SwitchRow,\n    run_actions: adw::SwitchRow,\n}\n\n#[derive(Debug)]\npub enum Step1Input {\n    // external set method\n    SetData(LauncherPlugins),\n    // internal set method\n    _Update,\n}\n\n#[derive(Debug)]\npub struct Step1Init {}\n\n#[allow(unused_assignments)]\n#[relm4::component(pub)]\nimpl SimpleComponent for Step1 {\n    type Init = Step1Init;\n    type Input = Step1Input;\n    type Output = LauncherPlugins;\n\n    view! {\n        gtk::Box {\n            set_orientation: gtk::Orientation::Vertical,\n            set_hexpand: true,\n            set_spacing: 40,\n            gtk::Label::new(Some(\"Launcher Plugins\")) {\n                set_css_classes: &[\"title-1\"],\n                set_align: Align::Center,\n                set_justify: Justification::Center,\n            },\n            gtk::ListBox {\n                set_halign: Align::Center,\n                set_valign: Align::Start,\n                set_hexpand: true,\n                set_selection_mode: SelectionMode::None,\n                set_css_classes: &[\"items-list\", \"boxed-list\", \"generate-min-width\"],\n                #[local_ref]\n                launch_apps -> adw::SwitchRow {\n                    set_title: \"Launch Applications\",\n                    set_subtitle: \"Launch .desktop files, sorted based on recent usage\",\n                    connect_active_notify => Step1Input::_Update,\n                },\n                #[local_ref]\n                run_commands_in_background -> adw::SwitchRow {\n                    set_title: \"Run Commands in Background\",\n                    set_subtitle: \"Run shell commands without opening a terminal\",\n                    connect_active_notify => Step1Input::_Update,\n                },\n                #[local_ref]\n                run_commands_in_terminal -> adw::SwitchRow {\n                    set_title: \"Run Commands in Terminal\",\n                    set_subtitle: \"Run command in a new terminal\",\n                    connect_active_notify => Step1Input::_Update,\n                },\n                #[local_ref]\n                search_the_web -> adw::SwitchRow {\n                    set_title: \"Search the Web\",\n                    set_subtitle: \"Open browser searching for text with user defined search engine\",\n                    connect_active_notify => Step1Input::_Update,\n                },\n                #[local_ref]\n                calculate_math_expressions -> adw::SwitchRow {\n                    set_title: \"Calculate Math expressions\",\n                    set_subtitle: \"Calculate valid Expressions using Rink\",\n                    connect_active_notify => Step1Input::_Update,\n                },\n                #[local_ref]\n                run_actions -> adw::SwitchRow {\n                    set_title: \"Run Actions (shutdown, sleep, custom, etc.)\",\n                    set_subtitle: \"Run predefined / user defined actions\",\n                    connect_active_notify => Step1Input::_Update,\n                },\n            },\n            gtk::Label::new(Some(\"\")) {\n                set_css_classes: &[\"title-4\"],\n                set_justify: Justification::Center,\n                set_vexpand: true,\n                set_valign: Align::End,\n            },\n        }\n    }\n\n    fn init(\n        _init: Self::Init,\n        root: Self::Root,\n        sender: ComponentSender<Self>,\n    ) -> ComponentParts<Self> {\n        let launch_apps = adw::SwitchRow::default();\n        let run_commands_in_background = adw::SwitchRow::default();\n        let run_commands_in_terminal = adw::SwitchRow::default();\n        let search_the_web = adw::SwitchRow::default();\n        let calculate_math_expressions = adw::SwitchRow::default();\n        let run_actions = adw::SwitchRow::default();\n\n        let model = Self {\n            launch_apps: launch_apps.clone(),\n            run_commands_in_background: run_commands_in_background.clone(),\n            run_commands_in_terminal: run_commands_in_terminal.clone(),\n            search_the_web: search_the_web.clone(),\n            calculate_math_expressions: calculate_math_expressions.clone(),\n            run_actions: run_actions.clone(),\n        };\n\n        let widgets = view_output!();\n        ComponentParts { model, widgets }\n    }\n\n    fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>) {\n        trace!(\"launcher::step1:update: {message:?}\");\n        match message {\n            Step1Input::_Update => {\n                let data = self.get_data();\n                sender.output_sender().emit(data);\n            }\n            Step1Input::SetData(data) => {\n                self.launch_apps.set_active(data.launch_applications);\n                self.run_commands_in_background\n                    .set_active(data.run_commands_in_background);\n                self.run_commands_in_terminal\n                    .set_active(data.run_commands_in_terminal);\n                self.search_the_web.set_active(data.search_the_web);\n                self.calculate_math_expressions\n                    .set_active(data.calculate_math_expressions);\n                self.run_actions.set_active(data.run_actions);\n            }\n        }\n    }\n}\n\nimpl Step1 {\n    fn get_data(&self) -> LauncherPlugins {\n        LauncherPlugins {\n            launch_applications: self.launch_apps.is_active(),\n            run_commands_in_background: self.run_commands_in_background.is_active(),\n            run_commands_in_terminal: self.run_commands_in_terminal.is_active(),\n            search_the_web: self.search_the_web.is_active(),\n            calculate_math_expressions: self.calculate_math_expressions.is_active(),\n            run_actions: self.run_actions.is_active(),\n        }\n    }\n}\n"
  },
  {
    "path": "crates/config-edit-lib/src/components/generate/step2.rs",
    "content": "use crate::util::{SelectRow, SetTextIfDifferent};\nuse core_lib::util::find_command;\nuse relm4::adw::prelude::*;\nuse relm4::gtk::{Align, Justification, SelectionMode};\nuse relm4::{ComponentParts, ComponentSender, RelmWidgetExt, SimpleComponent};\nuse relm4::{adw, gtk};\nuse std::path::PathBuf;\nuse std::sync::OnceLock;\nuse tracing::trace;\n\n#[derive(Debug)]\npub struct Step2 {\n    list_box: gtk::ListBox,\n    entry: adw::EntryRow,\n}\n\n#[derive(Debug)]\npub enum Step2Input {\n    // external set method\n    SetData(Option<String>),\n    // internal set method\n    ISetData(Option<String>),\n}\n\n#[derive(Debug)]\npub struct Step2Init {}\n\n#[allow(unused_assignments)]\n#[relm4::component(pub)]\nimpl SimpleComponent for Step2 {\n    type Init = Step2Init;\n    type Input = Step2Input;\n    type Output = Option<String>;\n\n    view! {\n        gtk::Box {\n            set_orientation: gtk::Orientation::Vertical,\n            set_hexpand: true,\n            set_spacing: 40,\n            gtk::Label::new(Some(\"Default Terminal\")) {\n                set_css_classes: &[\"title-1\"],\n                set_align: Align::Center,\n                set_justify: Justification::Center,\n            },\n            #[local_ref]\n            list_box -> gtk::ListBox {\n                set_halign: Align::Center,\n                set_valign: Align::Start,\n                set_hexpand: true,\n                set_selection_mode: SelectionMode::Single,\n                set_css_classes: &[\"boxed-list\", \"generate-min-width\"],\n                connect_row_activated[sender] => move |_, row| {\n                    if let Some(wdg) = row.downcast_ref::<adw::ActionRow>() {\n                        let title = wdg.title().to_string();\n                        trace!(\"press title: {title}\");\n                        match &*title {\n                            \"Autodetect\" => {\n                                sender.input(Step2Input::ISetData(None));\n                            }\n                            \"Alacritty\" => {\n                                sender.input(Step2Input::ISetData(Some(\"alacritty\".to_string())));\n                            }\n                            \"Kitty\" => {\n                                sender.input(Step2Input::ISetData(Some(\"kitty\".to_string())));\n                            }\n                            \"Wezterm\" => {\n                                sender.input(Step2Input::ISetData(Some(\"wezterm\".to_string())));\n                            }\n                            \"Foot\" => {\n                                sender.input(Step2Input::ISetData(Some(\"foot\".to_string())));\n                            }\n                            \"QTerminal\" => {\n                                sender.input(Step2Input::ISetData(Some(\"qterminal\".to_string())));\n                            }\n                            \"Lilyterm\" => {\n                                sender.input(Step2Input::ISetData(Some(\"lilyterm\".to_string())));\n                            }\n                            \"Tilix\" => {\n                                sender.input(Step2Input::ISetData(Some(\"tilix\".to_string())));\n                            }\n                            \"Terminix\" => {\n                                sender.input(Step2Input::ISetData(Some(\"terminix\".to_string())));\n                            }\n                            \"Konsole\" => {\n                                sender.input(Step2Input::ISetData(Some(\"konsole\".to_string())));\n                            }\n                            _ => {}\n                        }\n                    }\n                },\n                adw::ActionRow {\n                    set_title: \"Autodetect\",\n                    set_activatable: true,\n                    set_subtitle: \"autodetect from list of known terminals\"\n                },\n                adw::ActionRow {\n                    set_title: \"Alacritty\",\n                    set_activatable: true,\n                    set_css_classes: if get_terminals().iter().any(|(name, _)| *name == \"alacritty\") {&[]} else {&[\"gray-label\"]},\n                    set_subtitle: &get_terminals().iter().find(|(name, _)| *name == \"alacritty\").map(|(_, path)| path.display().to_string()).unwrap_or_default()\n                },\n                adw::ActionRow {\n                    set_title: \"Kitty\",\n                    set_activatable: true,\n                    set_css_classes: if get_terminals().iter().any(|(name, _)| *name == \"kitty\") {&[]} else {&[\"gray-label\"]},\n                    set_subtitle: &get_terminals().iter().find(|(name, _)| *name == \"kitty\").map(|(_, path)| path.display().to_string()).unwrap_or_default()\n                },\n                adw::ActionRow {\n                    set_title: \"Wezterm\",\n                    set_activatable: true,\n                    set_css_classes: if get_terminals().iter().any(|(name, _)| *name == \"wezterm\") {&[]} else {&[\"gray-label\"]},\n                    set_subtitle: &get_terminals().iter().find(|(name, _)| *name == \"wezterm\").map(|(_, path)| path.display().to_string()).unwrap_or_default()\n                },\n                adw::ActionRow {\n                    set_title: \"Foot\",\n                    set_activatable: true,\n                    set_css_classes: if get_terminals().iter().any(|(name, _)| *name == \"foot\") {&[]} else {&[\"gray-label\"]},\n                    set_subtitle: &get_terminals().iter().find(|(name, _)| *name == \"foot\").map(|(_, path)| path.display().to_string()).unwrap_or_default()\n                },\n                adw::ActionRow {\n                    set_title: \"QTerminal\",\n                    set_activatable: true,\n                    set_css_classes: if get_terminals().iter().any(|(name, _)| *name == \"qterminal\") {&[]} else {&[\"gray-label\"]},\n                    set_subtitle: &get_terminals().iter().find(|(name, _)| *name == \"qterminal\").map(|(_, path)| path.display().to_string()).unwrap_or_default()\n                },\n                adw::ActionRow {\n                    set_title: \"Lilyterm\",\n                    set_activatable: true,\n                    set_css_classes: if get_terminals().iter().any(|(name, _)| *name == \"lilyterm\") {&[]} else {&[\"gray-label\"]},\n                    set_subtitle: &get_terminals().iter().find(|(name, _)| *name == \"lilyterm\").map(|(_, path)| path.display().to_string()).unwrap_or_default()\n                },\n                adw::ActionRow {\n                    set_title: \"Tilix\",\n                    set_activatable: true,\n                    set_css_classes: if get_terminals().iter().any(|(name, _)| *name == \"tilix\") {&[]} else {&[\"gray-label\"]},\n                    set_subtitle: &get_terminals().iter().find(|(name, _)| *name == \"tilix\").map(|(_, path)| path.display().to_string()).unwrap_or_default()\n                },\n                adw::ActionRow {\n                    set_title: \"Terminix\",\n                    set_activatable: true,\n                    set_css_classes: if get_terminals().iter().any(|(name, _)| *name == \"terminix\") {&[]} else {&[\"gray-label\"]},\n                    set_subtitle: &get_terminals().iter().find(|(name, _)| *name == \"terminix\").map(|(_, path)| path.display().to_string()).unwrap_or_default()\n                },\n                adw::ActionRow {\n                    set_title: \"Konsole\",\n                    set_activatable: true,\n                    set_css_classes: if get_terminals().iter().any(|(name, _)| *name == \"konsole\") {&[]} else {&[\"gray-label\"]},\n                    set_subtitle: &get_terminals().iter().find(|(name, _)| *name == \"konsole\").map(|(_, path)| path.display().to_string()).unwrap_or_default()\n                },\n                #[local_ref]\n                entry -> adw::EntryRow {\n                    set_title: \"Custom\",\n                    connect_changed[sender] => move |e| { sender.input(Step2Input::ISetData(Some(e.text().into())))} @h_20,\n                    set_input_purpose: gtk::InputPurpose::FreeForm,\n                },\n            },\n            gtk::Label::new(Some(\"used to open terminal applications like htop\")) {\n                set_css_classes: &[\"title-4\"],\n                set_justify: Justification::Center,\n                set_vexpand: true,\n                set_valign: Align::End,\n            },\n        }\n    }\n\n    fn init(\n        _init: Self::Init,\n        root: Self::Root,\n        sender: ComponentSender<Self>,\n    ) -> ComponentParts<Self> {\n        let list_box = gtk::ListBox::default();\n        let entry = adw::EntryRow::default();\n        let model = Self {\n            list_box: list_box.clone(),\n            entry: entry.clone(),\n        };\n\n        let widgets = view_output!();\n        ComponentParts { model, widgets }\n    }\n\n    fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>) {\n        trace!(\"launcher::step2:update: {message:?}\");\n        match message {\n            Step2Input::ISetData(data) => {\n                sender.input(Step2Input::SetData(data.clone()));\n                sender.output_sender().emit(data);\n            }\n            Step2Input::SetData(data) => {\n                self.list_box.select_row_index(match data.as_deref() {\n                    None => 0,\n                    Some(\"alacritty\") => 1,\n                    Some(\"kitty\") => 2,\n                    Some(\"wezterm\") => 3,\n                    Some(\"foot\") => 4,\n                    Some(\"qterminal\") => 5,\n                    Some(\"lilyterm\") => 6,\n                    Some(\"tilix\") => 7,\n                    Some(\"terminix\") => 8,\n                    Some(\"konsole\") => 9,\n                    _ => 10,\n                });\n                self.entry.set_text_if_different(\n                    if data.is_some()\n                        && data != Some(\"alacritty\".to_string())\n                        && data != Some(\"kitty\".to_string())\n                        && data != Some(\"wezterm\".to_string())\n                        && data != Some(\"foot\".to_string())\n                        && data != Some(\"qterminal\".to_string())\n                        && data != Some(\"lilyterm\".to_string())\n                        && data != Some(\"tilix\".to_string())\n                        && data != Some(\"terminix\".to_string())\n                        && data != Some(\"konsole\".to_string())\n                    {\n                        data.as_ref().map_or(\"\", |v| v)\n                    } else {\n                        \"\"\n                    },\n                );\n            }\n        }\n    }\n}\n\nconst TERMINALS: &[&str] = &[\n    \"alacritty\",\n    \"kitty\",\n    \"wezterm\",\n    \"foot\",\n    \"qterminal\",\n    \"lilyterm\",\n    \"tilix\",\n    \"terminix\",\n    \"konsole\",\n];\n\nfn get_terminals() -> &'static Vec<(&'static str, PathBuf)> {\n    static TERMS: OnceLock<Vec<(&'static str, PathBuf)>> = OnceLock::new();\n    TERMS.get_or_init(|| {\n        let mut out = vec![];\n        for terminal in TERMINALS {\n            trace!(\"terminal: {terminal}\");\n            if let Some(path) = find_command(terminal) {\n                out.push((*terminal, path));\n            }\n        }\n        out\n    })\n}\n"
  },
  {
    "path": "crates/config-edit-lib/src/components/generate/step3.rs",
    "content": "use relm4::adw::prelude::*;\nuse relm4::gtk::{Align, Justification, SelectionMode};\nuse relm4::{ComponentParts, ComponentSender, RelmWidgetExt, SimpleComponent};\nuse relm4::{adw, gtk};\nuse tracing::trace;\n\n#[derive(Debug, Copy, Clone)]\npub struct SearchEngines {\n    pub google: bool,\n    pub startpage: bool,\n    pub duckduckgo: bool,\n    pub bing: bool,\n    pub wikipedia: bool,\n    pub chatgpt: bool,\n    pub youtube: bool,\n    pub reddit: bool,\n}\n\nimpl Default for SearchEngines {\n    fn default() -> Self {\n        Self {\n            google: false,\n            startpage: true,\n            duckduckgo: false,\n            bing: false,\n            wikipedia: true,\n            chatgpt: false,\n            youtube: false,\n            reddit: false,\n        }\n    }\n}\n\n#[derive(Debug)]\npub struct Step3 {\n    pub google: adw::SwitchRow,\n    pub startpage: adw::SwitchRow,\n    pub duckduckgo: adw::SwitchRow,\n    pub bing: adw::SwitchRow,\n    pub wikipedia: adw::SwitchRow,\n    pub chatgpt: adw::SwitchRow,\n    pub youtube: adw::SwitchRow,\n    pub reddit: adw::SwitchRow,\n}\n\n#[derive(Debug)]\npub enum Step3Input {\n    // external set method\n    SetData(SearchEngines),\n    // internal set method\n    _Update,\n}\n\n#[derive(Debug)]\npub struct Step3Init {}\n\n#[allow(unused_assignments)]\n#[relm4::component(pub)]\nimpl SimpleComponent for Step3 {\n    type Init = Step3Init;\n    type Input = Step3Input;\n    type Output = SearchEngines;\n\n    view! {\n        gtk::Box {\n            set_orientation: gtk::Orientation::Vertical,\n            set_hexpand: true,\n            set_spacing: 40,\n            gtk::Label::new(Some(\"Launcher Websearch Plugin\")) {\n                set_css_classes: &[\"title-1\"],\n                set_align: Align::Center,\n                set_justify: Justification::Center,\n            },\n            gtk::ListBox {\n                set_halign: Align::Center,\n                set_valign: Align::Start,\n                set_hexpand: true,\n                set_selection_mode: SelectionMode::None,\n                set_css_classes: &[\"items-list\", \"boxed-list\", \"generate-min-width\"],\n                #[local_ref]\n                google -> adw::SwitchRow {\n                    set_title: \"Google\",\n                    set_subtitle: \"https://www.google.com/search?q={}\",\n                    connect_active_notify => Step3Input::_Update,\n                },\n                #[local_ref]\n                startpage -> adw::SwitchRow {\n                    set_title: \"Startpage\",\n                    set_subtitle: \"https://www.startpage.com/sp/search?query={}\",\n                    connect_active_notify => Step3Input::_Update,\n                },\n                #[local_ref]\n                duckduckgo -> adw::SwitchRow {\n                    set_title: \"DuckDuckGo\",\n                    set_subtitle: \"https://duckduckgo.com/?q={}\",\n                    connect_active_notify => Step3Input::_Update,\n                },\n                #[local_ref]\n                bing -> adw::SwitchRow {\n                    set_title: \"Bing\",\n                    set_subtitle: \"https://www.bing.com/search?q={}\",\n                    connect_active_notify => Step3Input::_Update,\n                },\n                #[local_ref]\n                wikipedia -> adw::SwitchRow {\n                    set_title: \"Wikipedia\",\n                    set_subtitle: \"https://en.wikipedia.org/wiki/Special:Search?search={}\",\n                    connect_active_notify => Step3Input::_Update,\n                },\n                #[local_ref]\n                chatgpt -> adw::SwitchRow {\n                    set_title: \"ChatGpt\",\n                    set_subtitle: \"https://chatgpt.com/?q={}\",\n                    connect_active_notify => Step3Input::_Update,\n                },\n                #[local_ref]\n                youtube -> adw::SwitchRow {\n                    set_title: \"YouTube\",\n                    set_subtitle: \"https://www.youtube.com/results?search_query={}\",\n                    connect_active_notify => Step3Input::_Update,\n                },\n                #[local_ref]\n                reddit -> adw::SwitchRow {\n                    set_title: \"Reddit\",\n                    set_subtitle: \"https://www.reddit.com/search?q={}\",\n                    connect_active_notify => Step3Input::_Update,\n                },\n            },\n            gtk::Label::new(Some(\"Search engines used for the websearch plugin in the launcher\\n{} are replaced with the search string\")) {\n                set_css_classes: &[\"title-4\"],\n                set_justify: Justification::Center,\n                set_vexpand: true,\n                set_valign: Align::End,\n            },\n        }\n    }\n\n    fn init(\n        _init: Self::Init,\n        root: Self::Root,\n        sender: ComponentSender<Self>,\n    ) -> ComponentParts<Self> {\n        let google = adw::SwitchRow::default();\n        let startpage = adw::SwitchRow::default();\n        let duckduckgo = adw::SwitchRow::default();\n        let bing = adw::SwitchRow::default();\n        let wikipedia = adw::SwitchRow::default();\n        let chatgpt = adw::SwitchRow::default();\n        let youtube = adw::SwitchRow::default();\n        let reddit = adw::SwitchRow::default();\n\n        let model = Self {\n            google: google.clone(),\n            startpage: startpage.clone(),\n            duckduckgo: duckduckgo.clone(),\n            bing: bing.clone(),\n            wikipedia: wikipedia.clone(),\n            chatgpt: chatgpt.clone(),\n            youtube: youtube.clone(),\n            reddit: reddit.clone(),\n        };\n\n        let widgets = view_output!();\n        ComponentParts { model, widgets }\n    }\n\n    fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>) {\n        trace!(\"launcher::step3:update: {message:?}\");\n        match message {\n            Step3Input::_Update => {\n                let data = self.get_data();\n                sender.output_sender().emit(data);\n            }\n            Step3Input::SetData(data) => {\n                self.google.set_active(data.google);\n                self.startpage.set_active(data.startpage);\n                self.duckduckgo.set_active(data.duckduckgo);\n                self.bing.set_active(data.bing);\n                self.wikipedia.set_active(data.wikipedia);\n                self.chatgpt.set_active(data.chatgpt);\n                self.youtube.set_active(data.youtube);\n                self.reddit.set_active(data.reddit);\n            }\n        }\n    }\n}\n\nimpl Step3 {\n    fn get_data(&self) -> SearchEngines {\n        SearchEngines {\n            google: self.google.is_active(),\n            startpage: self.startpage.is_active(),\n            duckduckgo: self.duckduckgo.is_active(),\n            bing: self.bing.is_active(),\n            wikipedia: self.wikipedia.is_active(),\n            chatgpt: self.chatgpt.is_active(),\n            youtube: self.youtube.is_active(),\n            reddit: self.reddit.is_active(),\n        }\n    }\n}\n"
  },
  {
    "path": "crates/config-edit-lib/src/components/generate/step4.rs",
    "content": "use crate::components::shortcut_dialog::{\n    KeyboardShortcut, KeyboardShortcutInit, KeyboardShortcutInput, KeyboardShortcutOutput,\n};\nuse crate::structs::ConfigModifier;\nuse crate::util::{SelectRow, mod_key_to_string};\nuse relm4::adw::prelude::*;\nuse relm4::gtk::{Align, Justification, SelectionMode, gio};\nuse relm4::{\n    Component, ComponentController, ComponentParts, ComponentSender, Controller, RelmWidgetExt,\n    SimpleComponent, WidgetRef,\n};\nuse relm4::{adw, gtk};\nuse std::path::Path;\nuse tracing::trace;\n\n#[derive(Debug)]\npub struct Step4 {\n    keyboard_shortcut: Controller<KeyboardShortcut>,\n    list_box: gtk::ListBox,\n    button: adw::ButtonRow,\n}\n\n#[derive(Debug)]\npub enum Step4Input {\n    // external set method\n    SetData(Option<(ConfigModifier, String)>),\n    // internal set method\n    ISetData(Option<(ConfigModifier, String)>),\n    OpenKeyboardShortcut(gtk::Widget),\n}\n\n#[derive(Debug)]\npub struct Step4Init {\n    pub system_data_dir: Box<Path>,\n}\n\n#[allow(unused_assignments)]\n#[relm4::component(pub)]\nimpl SimpleComponent for Step4 {\n    type Init = Step4Init;\n    type Input = Step4Input;\n\n    type Output = Option<(ConfigModifier, String)>;\n\n    view! {\n        #[root]\n        gtk::Box {\n            set_orientation: gtk::Orientation::Vertical,\n            set_hexpand: true,\n            set_spacing: 20,\n            gtk::Label::new(Some(\"Key combination to open the overview and launcher\")) {\n                set_css_classes: &[\"title-1\"],\n                set_align: Align::Center,\n                set_justify: Justification::Center,\n            },\n            #[local_ref]\n            list_box -> gtk::ListBox {\n                set_halign: Align::Center,\n                set_valign: Align::Start,\n                set_hexpand: true,\n                set_selection_mode: SelectionMode::Single,\n                set_css_classes: &[\"boxed-list\", \"generate-min-width\"],\n                connect_row_activated[sender] => move |_, row| {\n                    if let Some(wdg) = row.downcast_ref::<adw::ActionRow>() {\n                        let title = wdg.title().to_string();\n                        trace!(\"press title: {title}\");\n                        match &*title {\n                            \"Disabled\" => {\n                                sender.input(Step4Input::ISetData(None));\n                            }\n                            \"Super\" => {\n                                sender.input(Step4Input::ISetData(Some((ConfigModifier::Super, \"Super_L\".to_string()))));\n                            }\n                            \"Super + Tab\" => {\n                                sender.input(Step4Input::ISetData(Some((ConfigModifier::Super, \"Tab\".to_string()))));\n                            }\n                            \"Ctrl\" => {\n                                sender.input(Step4Input::ISetData(Some((ConfigModifier::Ctrl, \"Ctrl_L\".to_string()))));\n                            }\n                            \"Ctrl + Tab\" => {\n                                sender.input(Step4Input::ISetData(Some((ConfigModifier::Ctrl, \"Tab\".to_string()))));\n                            }\n                            \"Alt\" => {\n                                sender.input(Step4Input::ISetData(Some((ConfigModifier::Alt, \"Alt_L\".to_string()))));\n                            }\n                            \"Alt + Tab\" => {\n                                sender.input(Step4Input::ISetData(Some((ConfigModifier::Alt, \"Tab\".to_string()))));\n                            }\n                            _ => {}\n                        }\n                    }\n                },\n                adw::ActionRow {\n                    set_title: \"Disabled\",\n                    set_activatable: true,\n                },\n                adw::ActionRow {\n                    set_title: \"Super\",\n                    set_activatable: true,\n                },\n                adw::ActionRow {\n                    set_title: \"Super + Tab\",\n                    set_activatable: true,\n                },\n                adw::ActionRow {\n                    set_title: \"Ctrl\",\n                    set_activatable: true,\n                },\n                adw::ActionRow {\n                    set_title: \"Ctrl + Tab\",\n                    set_activatable: true,\n                },\n                adw::ActionRow {\n                    set_title: \"Alt\",\n                    set_activatable: true,\n                },\n                adw::ActionRow {\n                    set_title: \"Alt + Tab\",\n                    set_activatable: true,\n                },\n                #[local_ref]\n                button -> adw::ButtonRow {\n                    connect_activated[sender] => move |b| {\n                        trace!(\"Generate: step0_keyboard_button toggled\");\n                        sender.input(Step4Input::OpenKeyboardShortcut(b.widget_ref().clone()));\n                    }\n                },\n            },\n            gtk::Picture {\n                set_file: Some(&gio::File::for_path(init.system_data_dir.join(\"04_switch.png\"))),\n                set_css_classes: &[\"theme-image\"],\n                set_vexpand: true,\n                set_hexpand: false,\n                set_valign: Align::Fill,\n                set_halign: Align::Center,\n            },\n            gtk::Label::new(Some(\"Shows windows in a list sorted by recently accessed. Navigate using tab.\\nPlease use a different keyboard Shortcut than the Overview modifier\")) {\n                set_css_classes: &[\"title-4\"],\n                set_justify: Justification::Center,\n                set_vexpand: true,\n                set_valign: Align::End,\n            },\n        }\n    }\n\n    fn init(\n        init: Self::Init,\n        root: Self::Root,\n        sender: ComponentSender<Self>,\n    ) -> ComponentParts<Self> {\n        let ins = sender.input_sender().clone();\n        let keyboard_shortcut = KeyboardShortcut::builder()\n            .launch(KeyboardShortcutInit {\n                label: Some(\"Custom\".to_string()),\n                icon: None,\n                init: None,\n            })\n            .connect_receiver(move |_send, out| match out {\n                KeyboardShortcutOutput::SetKey(r#mod, key) => {\n                    // updates the label\n                    ins.emit(Step4Input::ISetData(Some((r#mod, key))));\n                }\n                _ => {}\n            });\n\n        let list_box = gtk::ListBox::default();\n        let button = adw::ButtonRow::default();\n        let model = Self {\n            keyboard_shortcut,\n            button: button.clone(),\n            list_box: list_box.clone(),\n        };\n\n        let widgets = view_output!();\n        ComponentParts { model, widgets }\n    }\n\n    fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>) {\n        trace!(\"launcher::step4:update: {message:?}\");\n        match message {\n            Step4Input::ISetData(data) => {\n                sender.input(Step4Input::SetData(data.clone()));\n                sender.output_sender().emit(data);\n            }\n            Step4Input::SetData(data) => {\n                self.list_box\n                    .select_row_index(match data.as_ref().map(|(a, b)| (a, b.as_str())) {\n                        None => 0,\n                        Some((ConfigModifier::Super, \"Super_L\")) => 1,\n                        Some((ConfigModifier::Super, \"Tab\")) => 2,\n                        Some((ConfigModifier::Ctrl, \"Ctrl_L\")) => 3,\n                        Some((ConfigModifier::Ctrl, \"Tab\")) => 4,\n                        Some((ConfigModifier::Alt, \"Alt_L\")) => 5,\n                        Some((ConfigModifier::Alt, \"Tab\")) => 6,\n                        _ => 7,\n                    });\n                self.button.set_title(&format!(\n                    \"Custom: {}\",\n                    if data.is_some()\n                        && data != Some((ConfigModifier::Super, \"Super_L\".to_string()))\n                        && data != Some((ConfigModifier::Super, \"Tab\".to_string()))\n                        && data != Some((ConfigModifier::Ctrl, \"Ctrl_L\".to_string()))\n                        && data != Some((ConfigModifier::Ctrl, \"Tab\".to_string()))\n                        && data != Some((ConfigModifier::Alt, \"Alt_L\".to_string()))\n                        && data != Some((ConfigModifier::Alt, \"Tab\".to_string()))\n                    {\n                        data.as_ref()\n                            .map(|(r#mod, key)| mod_key_to_string(*r#mod, key))\n                            .unwrap_or_default()\n                    } else {\n                        String::new()\n                    }\n                ));\n            }\n            Step4Input::OpenKeyboardShortcut(widget) => {\n                self.keyboard_shortcut\n                    .emit(KeyboardShortcutInput::ShowKeyboardShortcutDialog(\n                        None,\n                        Some(widget),\n                    ));\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "crates/config-edit-lib/src/components/launcher.rs",
    "content": "use crate::components::launcher_plugins::{\n    LauncherPlugins, LauncherPluginsInit, LauncherPluginsInput, LauncherPluginsOutput,\n};\nuse crate::structs::ConfigModifier;\nuse crate::util::{SetCursor, SetTextIfDifferent};\nuse relm4::ComponentController;\nuse relm4::adw::prelude::*;\nuse relm4::{\n    Component, ComponentParts, ComponentSender, Controller, RelmWidgetExt, SimpleComponent,\n};\nuse relm4::{adw, gtk};\nuse tracing::trace;\n\n#[derive(Debug)]\npub struct Launcher {\n    config: crate::Launcher,\n    prev_config: crate::Launcher,\n    plugins: Controller<LauncherPlugins>,\n}\n\n#[derive(Debug)]\npub enum LauncherInput {\n    Set(crate::Launcher),\n    SetPrev(crate::Launcher),\n    Reset,\n}\n\n#[derive(Debug)]\npub struct LauncherInit {\n    pub config: crate::Launcher,\n}\n\n#[derive(Debug)]\npub enum LauncherOutput {\n    Modifier(ConfigModifier),\n    Width(u32),\n    MaxItems(u8),\n    DefaultTerminal(Option<String>),\n    LauncherPlugins(LauncherPluginsOutput),\n}\n\n#[relm4::component(pub)]\nimpl SimpleComponent for Launcher {\n    type Init = LauncherInit;\n    type Input = LauncherInput;\n    type Output = LauncherOutput;\n\n    view! {\n        #[root]\n        gtk::Box {\n            set_orientation: gtk::Orientation::Horizontal,\n            set_margin_all: 10,\n            adw::ExpanderRow {\n                set_title_selectable: true,\n                set_show_enable_switch: false,\n                set_hexpand: true,\n                set_css_classes: &[\"enable-frame\"],\n                set_title: \"Launcher\",\n                set_expanded: true,\n                add_row = &gtk::Box {\n                    set_orientation: gtk::Orientation::Horizontal,\n                    set_css_classes: &[\"frame-row\"],\n                    set_spacing: 30,\n                    gtk::Box {\n                        set_orientation: gtk::Orientation::Horizontal,\n                        set_spacing: 10,\n                        gtk::Label {\n                            #[watch]\n                            set_css_classes: if model.config.launch_modifier == model.prev_config.launch_modifier { &[] } else { &[\"blue-label\"]  },\n                            set_label: \"Modifier\",\n                        },\n                        gtk::Image::from_icon_name(\"dialog-information-symbolic\") {\n                            set_cursor_by_name: \"help\",\n                            set_tooltip_text: Some(\"The modifier used to select items in the launcher, pressing `<Mod> + 1` to open second entry, `<Mod> + t` to run in terminal, etc.\")\n                        },\n                        gtk::DropDown::from_strings(ConfigModifier::strings()) {\n                            #[watch]\n                            #[block_signal(h_1)]\n                            set_selected: model.config.launch_modifier.into(),\n                            connect_selected_notify[sender] => move |e| {sender.output_sender().emit(LauncherOutput::Modifier(e.selected().try_into().expect(\"invalid modifier\")));} @ h_1,\n                            set_hexpand: true,\n                        }\n                    },\n                    gtk::Box {\n                        set_orientation: gtk::Orientation::Horizontal,\n                        set_spacing: 10,\n                        gtk::Label {\n                            #[watch]\n                            set_css_classes: if model.config.width == model.prev_config.width { &[] } else { &[\"blue-label\"]  },\n                            set_label: \"Width\",\n                        },\n                        gtk::Image::from_icon_name(\"dialog-information-symbolic\") {\n                            set_cursor_by_name: \"help\",\n                            set_tooltip_text: Some(\"The width of the launcher in pixels.\")\n                        },\n                        gtk::SpinButton {\n                            set_adjustment: &gtk::Adjustment::new(0.0, 0.0, 2000.0, 50.0, 100.0, 0.0),\n                            set_hexpand: true,\n                            set_digits: 0,\n                            #[watch]\n                            #[block_signal(h_2)]\n                            set_value: f64::from(model.config.width),\n                            connect_value_changed[sender] => move |e| { sender.output_sender().emit(LauncherOutput::Width(e.value() as u32)); } @h_2,\n                        }\n                    },\n                    gtk::Box {\n                        set_orientation: gtk::Orientation::Horizontal,\n                        set_spacing: 10,\n                        gtk::Label {\n                            #[watch]\n                            set_css_classes: if model.config.max_items == model.prev_config.max_items { &[] } else { &[\"blue-label\"]  },\n                            set_label: \"Max items\",\n                        },\n                        gtk::Image::from_icon_name(\"dialog-information-symbolic\") {\n                            set_cursor_by_name: \"help\",\n                            set_tooltip_text: Some(\"Sets the maximum number of items to show in the launcher.\")\n                        },\n                        gtk::SpinButton {\n                            set_adjustment: &gtk::Adjustment::new(0.0, 0.0, 10.0, 1.0, 2.0, 0.0),\n                            set_hexpand: true,\n                            set_digits: 0,\n                            #[watch]\n                            #[block_signal(h_3)]\n                            set_value: f64::from(model.config.max_items),\n                            connect_value_changed[sender] => move |e| { sender.output_sender().emit(LauncherOutput::MaxItems(e.value() as u8)); } @h_3,\n                        }\n                    }\n                },\n                add_row = &gtk::Box {\n                    set_orientation: gtk::Orientation::Horizontal,\n                    set_css_classes: &[\"frame-row\"],\n                    set_spacing: 30,\n                    gtk::Box {\n                        set_orientation: gtk::Orientation::Horizontal,\n                        set_spacing: 10,\n                        gtk::Label {\n                            #[watch]\n                            set_css_classes: if model.config.default_terminal == model.prev_config.default_terminal { &[] } else { &[\"blue-label\"]  },\n                            set_label: \"Autodetect Terminal\",\n                        },\n                        gtk::Switch {\n                            #[watch]\n                            #[block_signal(h_4)]\n                            set_active: model.config.default_terminal.is_none(),\n                            set_valign: gtk::Align::Center,\n                            connect_active_notify[sender] => move |e| { sender.output_sender().emit(LauncherOutput::DefaultTerminal(if e.is_active() { None } else { Some(String::new()) })); } @h_4,\n                        },\n                        gtk::Image::from_icon_name(\"dialog-information-symbolic\") {\n                            set_cursor_by_name: \"help\",\n                            set_tooltip_text: Some(\"name/path of the default terminal to use. This value is optional, if unset a list of default terminals is used to find a default terminal. Will be used to launch terminal apps and for the terminal plugin.\")\n                        },\n                        gtk::Entry {\n                            #[watch]\n                            set_sensitive: model.config.default_terminal.is_some(),\n                            #[watch]\n                            #[block_signal(h_5)]\n                            set_text_if_different: &model.config.default_terminal.as_ref().unwrap_or(&String::new()),\n                            connect_changed[sender] => move |e| { sender.output_sender().emit(LauncherOutput::DefaultTerminal(Some(e.text().into())))} @h_5,\n                            set_input_purpose: gtk::InputPurpose::FreeForm,\n                            set_placeholder_text: Some(\"kitty\"),\n                            set_hexpand: true,\n                        }\n                    },\n                },\n                add_row = model.plugins.widget(),\n            }\n        }\n    }\n\n    #[allow(clippy::cast_sign_loss, clippy::cast_precision_loss)]\n    fn init(\n        init: Self::Init,\n        root: Self::Root,\n        sender: ComponentSender<Self>,\n    ) -> ComponentParts<Self> {\n        let launcher_plugins = LauncherPlugins::builder()\n            .launch(LauncherPluginsInit {\n                config: init.config.plugins.clone(),\n            })\n            .forward(sender.output_sender(), LauncherOutput::LauncherPlugins);\n\n        let model = Self {\n            config: init.config.clone(),\n            prev_config: init.config,\n            plugins: launcher_plugins,\n        };\n        let widgets = view_output!();\n        ComponentParts { model, widgets }\n    }\n\n    fn update(&mut self, message: Self::Input, _sender: ComponentSender<Self>) {\n        trace!(\"launcher::update: {message:?}\");\n        match message {\n            LauncherInput::Set(config) => {\n                self.config = config;\n                self.plugins\n                    .emit(LauncherPluginsInput::Set(self.config.plugins.clone()));\n            }\n            LauncherInput::SetPrev(config) => {\n                self.prev_config = config;\n                self.plugins.emit(LauncherPluginsInput::SetPrev(\n                    self.prev_config.plugins.clone(),\n                ));\n            }\n            LauncherInput::Reset => {\n                self.config = self.prev_config.clone();\n                self.plugins.emit(LauncherPluginsInput::Reset);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "crates/config-edit-lib/src/components/launcher_plugins/actions.rs",
    "content": "use crate::util::SetCursor;\nuse config_lib::actions::ToAction;\nuse config_lib::{ActionsPluginAction, ActionsPluginActionCustom};\nuse relm4::adw::prelude::*;\nuse relm4::gtk::{Align, SelectionMode};\nuse relm4::prelude::*;\nuse relm4::{ComponentParts, ComponentSender, SimpleComponent};\nuse relm4_components::alert::{Alert, AlertMsg, AlertResponse, AlertSettings};\nuse std::path::Path;\n\n#[allow(clippy::struct_field_names)]\n#[derive(Debug)]\npub struct Actions {\n    config: crate::ActionsPluginConfig,\n    prev_config: crate::ActionsPluginConfig,\n    actions: FactoryVecDeque<Action>,\n\n    create: adw::ButtonRow,\n    create_dialog: Controller<Alert>,\n    pub names: adw::EntryRow,\n    pub details: adw::EntryRow,\n    pub command: adw::EntryRow,\n    pub icon: adw::EntryRow,\n}\n\n#[derive(Debug)]\npub enum ActionsInput {\n    Set(crate::ActionsPluginConfig),\n    SetPrev(crate::ActionsPluginConfig),\n    Reset,\n    Action(ActionOutput),\n    OpenCreateNew,\n    Create,\n    Ignore,\n}\n\n#[derive(Debug)]\npub struct ActionsInit {\n    pub config: crate::ActionsPluginConfig,\n}\n\n#[derive(Debug)]\npub enum ActionsOutput {\n    Enabled(bool),\n    Actions(Vec<ActionsPluginAction>),\n}\n\n#[allow(unused_assignments)]\n#[relm4::component(pub)]\nimpl SimpleComponent for Actions {\n    type Init = ActionsInit;\n    type Input = ActionsInput;\n    type Output = ActionsOutput;\n\n    view! {\n        #[root]\n        adw::ExpanderRow {\n            set_title_selectable: true,\n            set_show_enable_switch: true,\n            set_hexpand: true,\n            set_css_classes: &[\"enable-frame\"],\n            add_prefix = &gtk::Box {\n                set_orientation: gtk::Orientation::Horizontal,\n                set_halign: Align::Fill,\n                set_valign: Align::Center,\n                set_spacing: 15,\n                gtk::Label {\n                    set_label: \"Actions\",\n                },\n                gtk::Image::from_icon_name(\"dialog-information-symbolic\") {\n                    set_cursor_by_name: \"help\",\n                    set_tooltip_text: Some(\"Runs the specified action like reboot, hibernate, etc. Custom actions can also be specified.\")\n                },\n            },\n            #[watch]\n            #[block_signal(h)]\n            set_enable_expansion: model.config.enabled,\n            connect_enable_expansion_notify[sender] => move |e| {sender.output_sender().emit(ActionsOutput::Enabled(e.enables_expansion()));} @h,\n            #[watch]\n            set_expanded: model.config.enabled,\n\n            #[local_ref]\n            add_row = actions -> gtk::ListBox {\n                set_halign: Align::Fill,\n                set_valign: Align::Start,\n                set_expand: true,\n                set_selection_mode: SelectionMode::None,\n                set_css_classes: &[\"items-list\", \"boxed-list\"],\n\n                #[local_ref]\n                create -> adw::ButtonRow {\n                    set_title: \"Create new\",\n                    connect_activated[sender] => move |_b| {\n                        sender.input(ActionsInput::OpenCreateNew);\n                    }\n                },\n            }\n        }\n    }\n\n    #[allow(unused_assignments)]\n    fn init(\n        init: Self::Init,\n        root: Self::Root,\n        sender: ComponentSender<Self>,\n    ) -> ComponentParts<Self> {\n        let list = gtk::ListBox::builder()\n            .selection_mode(SelectionMode::None)\n            .vexpand(true)\n            .hexpand(true)\n            .css_classes(vec![\"boxed-list\"])\n            .build();\n\n        let names = adw::EntryRow::builder()\n            .title(\"Names (separate with ,)\")\n            .build();\n        list.append(&names);\n        let details = adw::EntryRow::builder().title(\"Description\").build();\n        list.append(&details);\n        let command = adw::EntryRow::builder().title(\"Shell command\").build();\n        list.append(&command);\n        let icon = adw::EntryRow::builder().title(\"Icon name\").build();\n        list.append(&icon);\n\n        let create_dialog = Alert::builder()\n            .transient_for(&root)\n            .launch(AlertSettings {\n                text: Some(\"Create New Search Engine\".to_string()),\n                secondary_text: None,\n                confirm_label: Some(String::from(\"Create\")),\n                cancel_label: Some(String::from(\"Cancel\")),\n                option_label: None,\n                is_modal: true,\n                destructive_accept: false,\n                extra_child: Some(list.into()),\n            })\n            .forward(sender.input_sender(), |res| match res {\n                AlertResponse::Confirm => ActionsInput::Create,\n                AlertResponse::Option | AlertResponse::Cancel => ActionsInput::Ignore,\n            });\n\n        let mut actions = FactoryVecDeque::builder()\n            .launch(gtk::ListBox::builder().build())\n            .forward(sender.input_sender(), ActionsInput::Action);\n\n        let mut l = actions.guard();\n        for engine in &init.config.actions {\n            l.push_back(engine.clone());\n        }\n        drop(l);\n\n        let create = adw::ButtonRow::default();\n\n        let model = Self {\n            config: init.config.clone(),\n            prev_config: init.config,\n            actions,\n            create,\n            create_dialog,\n            names,\n            details,\n            command,\n            icon,\n        };\n\n        let actions = model.actions.widget();\n        let create = &model.create;\n        let widgets = view_output!();\n        ComponentParts { model, widgets }\n    }\n\n    fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>) {\n        match message {\n            ActionsInput::Ignore => {}\n            ActionsInput::Set(config) => {\n                self.config = config;\n                let mut l = self.actions.guard();\n                l.clear();\n                for engine in &self.config.actions {\n                    l.push_back(engine.clone());\n                }\n                drop(l);\n            }\n            ActionsInput::SetPrev(config) => {\n                self.prev_config = config;\n            }\n            ActionsInput::Reset => {\n                sender.input(ActionsInput::Set(self.prev_config.clone()));\n            }\n            ActionsInput::Action(msg) => match msg {\n                ActionOutput::Delete(command) => {\n                    #[allow(clippy::iter_overeager_cloned)]\n                    let actions = self\n                        .config\n                        .actions\n                        .iter()\n                        .cloned()\n                        .filter(|e| e.clone().to_action().command != command)\n                        .collect::<Vec<_>>();\n                    sender.output_sender().emit(ActionsOutput::Actions(actions));\n                }\n            },\n            ActionsInput::OpenCreateNew => {\n                self.create_dialog\n                    .widget()\n                    .set_transient_for(self.create.toplevel_window().as_ref());\n                self.create_dialog.emit(AlertMsg::Show);\n                self.create_dialog.widgets().gtk_window_12.set_modal(true); // TODO remove if https://github.com/Relm4/Relm4/issues/837 fixed\n            }\n            ActionsInput::Create => {\n                let names = self\n                    .names\n                    .text()\n                    .split(',')\n                    .map(|s| s.trim().to_string().into_boxed_str())\n                    .collect::<Vec<_>>();\n                let details = self.details.text().to_string().into_boxed_str();\n                let command = self.details.text().to_string().into_boxed_str();\n                let icon = Box::from(Path::new(&self.icon.text()));\n                self.create_dialog.emit(AlertMsg::Hide);\n                self.names.set_text(\"\");\n                self.details.set_text(\"\");\n                self.command.set_text(\"\");\n                self.icon.set_text(\"\");\n\n                let mut actions = self.config.actions.clone();\n                actions.push(ActionsPluginAction::Custom(ActionsPluginActionCustom {\n                    names,\n                    details,\n                    command,\n                    icon,\n                }));\n                sender.output_sender().emit(ActionsOutput::Actions(actions));\n            }\n        }\n    }\n}\n\n/// ------------------------------------------------------------------------------------------------\n\n#[derive(Debug)]\nstruct Action {\n    action: config_lib::ActionsPluginActionCustom,\n}\n\n#[derive(Debug)]\nenum ActionInput {\n    Update,\n}\n\n#[derive(Debug)]\npub enum ActionOutput {\n    Delete(Box<str>),\n}\n\n#[allow(unused_assignments)]\n#[relm4::factory]\nimpl FactoryComponent for Action {\n    type Init = ActionsPluginAction;\n    type Input = ActionInput;\n    type Output = ActionOutput;\n    type CommandOutput = ();\n    type ParentWidget = gtk::ListBox;\n\n    view! {\n        adw::ActionRow {\n            set_title: &self.action.names.join(\", \"),\n            set_subtitle: &self.action.details,\n            #[name = \"image\"]\n            add_prefix = &gtk::Image {},\n            add_suffix = &gtk::Button::from_icon_name(\"delete-symbolic\") {\n                connect_clicked[sender, command = self.action.command.clone()] => move |_| sender.output_sender().emit(ActionOutput::Delete(command.clone())),\n            }\n        }\n    }\n\n    fn init_model(init: Self::Init, _index: &DynamicIndex, sender: FactorySender<Self>) -> Self {\n        sender.input(ActionInput::Update);\n        Self {\n            action: init.to_action(),\n        }\n    }\n\n    fn update_with_view(\n        &mut self,\n        widgets: &mut Self::Widgets,\n        _message: Self::Input,\n        _sender: FactorySender<Self>,\n    ) {\n        let icon_path = self.action.icon.clone();\n        if icon_path.is_absolute() {\n            widgets.image.set_from_file(Some(Path::new(&*icon_path)));\n        } else {\n            widgets\n                .image\n                .set_icon_name(icon_path.file_name().and_then(|name| name.to_str()));\n        }\n    }\n\n    fn update(&mut self, msg: Self::Input, _sender: FactorySender<Self>) {\n        match msg {\n            ActionInput::Update => {}\n        }\n    }\n}\n"
  },
  {
    "path": "crates/config-edit-lib/src/components/launcher_plugins/applications.rs",
    "content": "use crate::util::SetCursor;\nuse relm4::adw::gtk::{Adjustment, Align};\nuse relm4::adw::prelude::*;\nuse relm4::{ComponentParts, ComponentSender, SimpleComponent};\nuse relm4::{adw, gtk};\nuse tracing::trace;\n\n#[derive(Debug)]\npub struct Applications {\n    config: crate::ApplicationsPluginConfig,\n    prev_config: crate::ApplicationsPluginConfig,\n}\n\n#[derive(Debug)]\npub enum ApplicationsInput {\n    Set(crate::ApplicationsPluginConfig),\n    SetPrev(crate::ApplicationsPluginConfig),\n    Reset,\n}\n\n#[derive(Debug)]\npub struct ApplicationsInit {\n    pub config: crate::ApplicationsPluginConfig,\n}\n\n#[derive(Debug)]\npub enum ApplicationsOutput {\n    Enabled(bool),\n    ShowExecs(bool),\n    ShowActions(bool),\n    CacheWeeks(u8),\n}\n\n#[relm4::component(pub)]\nimpl SimpleComponent for Applications {\n    type Init = ApplicationsInit;\n    type Input = ApplicationsInput;\n    type Output = ApplicationsOutput;\n\n    view! {\n        #[root]\n        adw::ExpanderRow {\n            set_title_selectable: true,\n            set_show_enable_switch: true,\n            set_hexpand: true,\n            set_css_classes: &[\"enable-frame\"],\n            add_prefix = &gtk::Box {\n                set_orientation: gtk::Orientation::Horizontal,\n                set_halign: Align::Fill,\n                set_valign: Align::Center,\n                set_spacing: 15,\n                gtk::Label {\n                    set_label: \"Applications\",\n                },\n                gtk::Image::from_icon_name(\"dialog-information-symbolic\") {\n                    set_cursor_by_name: \"help\",\n                    set_tooltip_text: Some(\"Launch .desktop files, sorted based on recent usage\")\n                },\n            },\n            #[watch]\n            #[block_signal(h)]\n            set_enable_expansion: model.config.enabled,\n            connect_enable_expansion_notify[sender] => move |e| {sender.output_sender().emit(ApplicationsOutput::Enabled(e.enables_expansion()));} @h,\n            #[watch]\n            set_expanded: model.config.enabled,\n            add_row = &gtk::Box {\n                set_orientation: gtk::Orientation::Horizontal,\n                set_css_classes: &[\"frame-row\"],\n                set_spacing: 30,\n                gtk::Box {\n                    set_orientation: gtk::Orientation::Horizontal,\n                    set_spacing: 10,\n                    gtk::Label {\n                        #[watch]\n                        set_css_classes: if model.config.run_cache_weeks == model.prev_config.run_cache_weeks { &[] } else { &[\"blue-label\"]  },\n                        set_label: \"Run cache period (weeks)\",\n                    },\n                    gtk::Image::from_icon_name(\"dialog-information-symbolic\") {\n                        set_cursor_by_name: \"help\",\n                        set_tooltip_text: Some(\"Number of weeks to retain run history; used to rank applications by usage.\")\n                    },\n                    gtk::SpinButton {\n                        set_adjustment: &Adjustment::new(0.0, 0.0, 52.0, 1.0, 8.0, 0.0),\n                        set_digits: 0,\n                        set_hexpand: true,\n                        #[watch]\n                        #[block_signal(h_1)]\n                        set_value: f64::from(model.config.run_cache_weeks),\n                        connect_value_changed[sender] => move |e| { sender.output_sender().emit(ApplicationsOutput::CacheWeeks(e.value() as u8)); } @h_1,\n                    }\n                },\n                gtk::Box {\n                    set_orientation: gtk::Orientation::Horizontal,\n                    set_spacing: 10,\n                    gtk::Label {\n                        #[watch]\n                        set_css_classes: if model.config.show_execs == model.prev_config.show_execs { &[] } else { &[\"blue-label\"]  },\n                        set_label: \"Show Execs\",\n                    },\n                    gtk::Image::from_icon_name(\"dialog-information-symbolic\") {\n                        set_cursor_by_name: \"help\",\n                        set_tooltip_text: Some(\"Show the exec line from the Desktop file. In the case of Flatpaks and PWAs these get shortened to the name of the app.\")\n                    },\n                    gtk::Switch {\n                        set_valign: Align::Center,\n                        #[watch]\n                        #[block_signal(h_2)]\n                        set_active: model.config.show_execs,\n                        connect_active_notify[sender] => move |e| { sender.output_sender().emit(ApplicationsOutput::ShowExecs(e.is_active())) } @h_2,\n                    },\n                },\n                gtk::Box {\n                    set_orientation: gtk::Orientation::Horizontal,\n                    set_spacing: 10,\n                    gtk::Label {\n                        #[watch]\n                        set_css_classes: if model.config.show_actions_submenu == model.prev_config.show_actions_submenu { &[] } else { &[\"blue-label\"]  },\n                        set_label: \"Show Actions\",\n                    },\n                    gtk::Image::from_icon_name(\"dialog-information-symbolic\") {\n                        set_cursor_by_name: \"help\",\n                        set_tooltip_text: Some(\"Show a dropdown menu with all the desktop actions specified in the `.desktop` files of the applications, like `new private window`, etc.\")\n                    },\n                    gtk::Switch {\n                        set_valign: Align::Center,\n                        #[watch]\n                        #[block_signal(h_3)]\n                        set_active: model.config.show_actions_submenu,\n                        connect_active_notify[sender] => move |e| { sender.output_sender().emit(ApplicationsOutput::ShowActions(e.is_active())); } @h_3,\n                    },\n                },\n            }\n        }\n    }\n\n    #[allow(clippy::cast_sign_loss)]\n    fn init(\n        init: Self::Init,\n        root: Self::Root,\n        sender: ComponentSender<Self>,\n    ) -> ComponentParts<Self> {\n        let model = Self {\n            config: init.config.clone(),\n            prev_config: init.config,\n        };\n\n        let widgets = view_output!();\n        ComponentParts { model, widgets }\n    }\n\n    fn update(&mut self, message: Self::Input, _sender: ComponentSender<Self>) {\n        trace!(\"launcher_plugins::application::update: {message:?}\");\n        match message {\n            ApplicationsInput::Set(config) => {\n                self.config = config;\n            }\n            ApplicationsInput::SetPrev(config) => {\n                self.prev_config = config;\n            }\n            ApplicationsInput::Reset => {\n                self.config = self.prev_config.clone();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "crates/config-edit-lib/src/components/launcher_plugins/main.rs",
    "content": "use crate::components::launcher_plugins::actions::{\n    Actions, ActionsInit, ActionsInput, ActionsOutput,\n};\nuse crate::components::launcher_plugins::applications::{\n    Applications, ApplicationsInit, ApplicationsInput, ApplicationsOutput,\n};\nuse crate::components::launcher_plugins::simple::{\n    SimplePlugin, SimplePluginInit, SimplePluginInput, SimplePluginOutput,\n};\nuse crate::components::launcher_plugins::websearch::{\n    WebSearch, WebSearchInit, WebSearchInput, WebSearchOutput,\n};\nuse relm4::ComponentController;\nuse relm4::adw::prelude::*;\nuse relm4::{\n    Component, ComponentParts, ComponentSender, Controller, RelmWidgetExt, SimpleComponent,\n};\nuse relm4::{adw, gtk};\nuse tracing::trace;\n\n#[derive(Debug)]\npub struct LauncherPlugins {\n    config: crate::Plugins,\n    prev_config: crate::Plugins,\n    applications: Controller<Applications>,\n    run_in_terminal: Controller<SimplePlugin>,\n    run_in_shell: Controller<SimplePlugin>,\n    calculator: Controller<SimplePlugin>,\n    file_path: Controller<SimplePlugin>,\n    web_search: Controller<WebSearch>,\n    actions: Controller<Actions>,\n}\n\n#[derive(Debug)]\npub enum LauncherPluginsInput {\n    Set(crate::Plugins),\n    SetPrev(crate::Plugins),\n    Reset,\n}\n\n#[derive(Debug)]\npub struct LauncherPluginsInit {\n    pub config: crate::Plugins,\n}\n\n#[derive(Debug)]\npub enum LauncherPluginsOutput {\n    Applications(ApplicationsOutput),\n    Terminal(SimplePluginOutput),\n    Shell(SimplePluginOutput),\n    Calculator(SimplePluginOutput),\n    FilePath(SimplePluginOutput),\n    WebSearch(WebSearchOutput),\n    Actions(ActionsOutput),\n}\n\n#[relm4::component(pub)]\nimpl SimpleComponent for LauncherPlugins {\n    type Init = LauncherPluginsInit;\n    type Input = LauncherPluginsInput;\n    type Output = LauncherPluginsOutput;\n\n    view! {\n        #[root]\n        adw::ExpanderRow {\n            set_title_selectable: true,\n            set_show_enable_switch: false,\n            set_hexpand: true,\n            set_css_classes: &[\"enable-frame\"],\n            set_title: \"Plugins\",\n            set_enable_expansion: true,\n            set_expanded: true,\n            add_row = &gtk::Box {\n                set_margin_all: 10,\n                set_orientation: gtk::Orientation::Horizontal,\n                set_spacing: 20,\n                model.applications.widget(),\n            },\n            add_row = &gtk::Box {\n                set_margin_all: 10,\n                set_orientation: gtk::Orientation::Horizontal,\n                set_spacing: 20,\n                model.run_in_terminal.widget(),\n                model.run_in_shell.widget(),\n            },\n            add_row = &gtk::Box {\n                set_margin_all: 10,\n                set_orientation: gtk::Orientation::Horizontal,\n                set_spacing: 20,\n                model.calculator.widget(),\n                model.file_path.widget(),\n            },\n            add_row = &gtk::Box {\n                set_margin_all: 10,\n                set_orientation: gtk::Orientation::Horizontal,\n                set_spacing: 20,\n                model.web_search.widget(),\n                model.actions.widget(),\n            }\n        }\n    }\n\n    fn init(\n        init: Self::Init,\n        root: Self::Root,\n        sender: ComponentSender<Self>,\n    ) -> ComponentParts<Self> {\n        let launcher_plugins = Applications::builder()\n            .launch(ApplicationsInit {\n                config: init.config.applications.clone(),\n            })\n            .forward(sender.output_sender(), LauncherPluginsOutput::Applications);\n        let run_in_terminal = SimplePlugin::builder()\n            .launch(SimplePluginInit {\n                config: init.config.terminal.clone(),\n                name: \"Run in Terminal\",\n                description: \"Open a terminal and run the typed command in it. The terminal is defined in the `default_terminal` config option.\",\n            })\n            .forward(sender.output_sender(), LauncherPluginsOutput::Terminal);\n        let run_in_shell = SimplePlugin::builder()\n            .launch(SimplePluginInit {\n                config: init.config.shell.clone(),\n                name: \"Run in Shell\",\n                description: \"Run the typed command in a shell (in the background).\",\n            })\n            .forward(sender.output_sender(), LauncherPluginsOutput::Shell);\n        let calculator = SimplePlugin::builder()\n            .launch(SimplePluginInit {\n                config: init.config.calc.clone(),\n                name: \"Calculator\",\n                description: \"Calculates any mathematical expression typed into the launcher.\",\n            })\n            .forward(sender.output_sender(), LauncherPluginsOutput::Calculator);\n        let file_path = SimplePlugin::builder()\n            .launch(SimplePluginInit {\n                config: init.config.path.clone(),\n                name: \"Open Filepath\",\n                description: \"Opens the typed path in the default file manager.\",\n            })\n            .forward(sender.output_sender(), LauncherPluginsOutput::FilePath);\n        let web_search = WebSearch::builder()\n            .launch(WebSearchInit {\n                config: init.config.websearch.clone(),\n            })\n            .forward(sender.output_sender(), LauncherPluginsOutput::WebSearch);\n        let actions = Actions::builder()\n            .launch(ActionsInit {\n                config: init.config.actions.clone(),\n            })\n            .forward(sender.output_sender(), LauncherPluginsOutput::Actions);\n\n        let model = Self {\n            config: init.config.clone(),\n            prev_config: init.config,\n            applications: launcher_plugins,\n            run_in_terminal,\n            run_in_shell,\n            calculator,\n            file_path,\n            web_search,\n            actions,\n        };\n\n        let widgets = view_output!();\n        ComponentParts { model, widgets }\n    }\n\n    fn update(&mut self, message: Self::Input, _sender: ComponentSender<Self>) {\n        trace!(\"launcher_plugins::main::update: {message:?}\");\n        match message {\n            LauncherPluginsInput::Set(config) => {\n                self.config = config;\n                self.applications\n                    .emit(ApplicationsInput::Set(self.config.applications.clone()));\n                self.run_in_terminal\n                    .emit(SimplePluginInput::Set(self.config.terminal.clone()));\n                self.run_in_shell\n                    .emit(SimplePluginInput::Set(self.config.shell.clone()));\n                self.calculator\n                    .emit(SimplePluginInput::Set(self.config.calc.clone()));\n                self.file_path\n                    .emit(SimplePluginInput::Set(self.config.path.clone()));\n                self.web_search\n                    .emit(WebSearchInput::Set(self.config.websearch.clone()));\n                self.actions\n                    .emit(ActionsInput::Set(self.config.actions.clone()));\n            }\n            LauncherPluginsInput::SetPrev(config) => {\n                self.prev_config = config;\n                self.applications.emit(ApplicationsInput::SetPrev(\n                    self.prev_config.applications.clone(),\n                ));\n                self.run_in_terminal.emit(SimplePluginInput::SetPrev(\n                    self.prev_config.terminal.clone(),\n                ));\n                self.run_in_shell\n                    .emit(SimplePluginInput::SetPrev(self.prev_config.shell.clone()));\n                self.calculator\n                    .emit(SimplePluginInput::SetPrev(self.prev_config.calc.clone()));\n                self.file_path\n                    .emit(SimplePluginInput::SetPrev(self.prev_config.path.clone()));\n                self.web_search\n                    .emit(WebSearchInput::SetPrev(self.prev_config.websearch.clone()));\n                self.actions\n                    .emit(ActionsInput::SetPrev(self.prev_config.actions.clone()));\n            }\n            LauncherPluginsInput::Reset => {\n                self.config = self.prev_config.clone();\n                self.applications.emit(ApplicationsInput::Reset);\n                self.run_in_terminal.emit(SimplePluginInput::Reset);\n                self.run_in_shell.emit(SimplePluginInput::Reset);\n                self.calculator.emit(SimplePluginInput::Reset);\n                self.file_path.emit(SimplePluginInput::Reset);\n                self.web_search.emit(WebSearchInput::Reset);\n                self.actions.emit(ActionsInput::Reset);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "crates/config-edit-lib/src/components/launcher_plugins/mod.rs",
    "content": "pub mod actions;\npub mod applications;\nmod main;\npub mod simple;\npub mod websearch;\n\npub use main::*;\n"
  },
  {
    "path": "crates/config-edit-lib/src/components/launcher_plugins/simple.rs",
    "content": "use crate::util::SetCursor;\nuse relm4::adw::prelude::*;\nuse relm4::gtk::Align;\nuse relm4::{ComponentParts, ComponentSender, SimpleComponent};\nuse relm4::{adw, gtk};\nuse tracing::trace;\n\n#[derive(Debug)]\npub struct SimplePlugin {\n    config: crate::EmptyConfig,\n    prev_config: crate::EmptyConfig,\n}\n\n#[derive(Debug)]\npub enum SimplePluginInput {\n    Set(crate::EmptyConfig),\n    SetPrev(crate::EmptyConfig),\n    Reset,\n}\n\n#[derive(Debug)]\npub struct SimplePluginInit {\n    pub name: &'static str,\n    pub description: &'static str,\n    pub config: crate::EmptyConfig,\n}\n\n#[derive(Debug)]\npub enum SimplePluginOutput {\n    Enabled(bool),\n}\n\n#[relm4::component(pub)]\nimpl SimpleComponent for SimplePlugin {\n    type Init = SimplePluginInit;\n    type Input = SimplePluginInput;\n    type Output = SimplePluginOutput;\n\n    view! {\n        #[root]\n        adw::ExpanderRow {\n            set_title_selectable: true,\n            set_show_enable_switch: true,\n            set_hexpand: true,\n            set_css_classes: &[\"enable-frame\"],\n            add_prefix = &gtk::Box {\n                set_orientation: gtk::Orientation::Horizontal,\n                set_halign: Align::Fill,\n                set_valign: Align::Center,\n                set_spacing: 15,\n                gtk::Label {\n                    set_label: init.name,\n                },\n                gtk::Image::from_icon_name(\"dialog-information-symbolic\") {\n                    set_cursor_by_name: \"help\",\n                    set_tooltip_text: Some(init.description)\n                },\n            },\n            #[watch]\n            #[block_signal(h)]\n            set_enable_expansion: model.config.enabled,\n            connect_enable_expansion_notify[sender] => move |e| {sender.output_sender().emit(SimplePluginOutput::Enabled(e.enables_expansion()))} @h,\n            #[watch]\n            set_expanded: model.config.enabled,\n        }\n    }\n\n    fn init(\n        init: Self::Init,\n        root: Self::Root,\n        sender: ComponentSender<Self>,\n    ) -> ComponentParts<Self> {\n        let model = Self {\n            config: init.config.clone(),\n            prev_config: init.config,\n        };\n\n        let widgets = view_output!();\n        ComponentParts { model, widgets }\n    }\n\n    fn update(&mut self, message: Self::Input, _sender: ComponentSender<Self>) {\n        trace!(\"launcher_plugins::simple::update: {message:?}\");\n        match message {\n            SimplePluginInput::Set(config) => {\n                self.config = config;\n            }\n            SimplePluginInput::SetPrev(config) => {\n                self.prev_config = config;\n            }\n            SimplePluginInput::Reset => {\n                self.config = self.prev_config.clone();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "crates/config-edit-lib/src/components/launcher_plugins/websearch.rs",
    "content": "use crate::util::SetCursor;\nuse config_lib::SearchEngine;\nuse relm4::adw::prelude::*;\nuse relm4::gtk::{Align, SelectionMode};\nuse relm4::prelude::*;\nuse relm4::{ComponentParts, ComponentSender, SimpleComponent};\nuse relm4::{adw, gtk};\nuse relm4_components::alert::{Alert, AlertMsg, AlertResponse, AlertSettings};\nuse tracing::trace;\n\n#[derive(Debug)]\npub struct WebSearch {\n    config: crate::WebSearchConfig,\n    prev_config: crate::WebSearchConfig,\n    engines: FactoryVecDeque<Search>,\n\n    create: adw::ButtonRow,\n    create_dialog: Controller<Alert>,\n    name: adw::EntryRow,\n    url: adw::EntryRow,\n    key: adw::EntryRow,\n}\n\n#[derive(Debug)]\npub enum WebSearchInput {\n    Set(crate::WebSearchConfig),\n    SetPrev(crate::WebSearchConfig),\n    Reset,\n    Engine(SearchOutput),\n    OpenCreateNew,\n    Create,\n    Ignore,\n}\n\n#[derive(Debug)]\npub struct WebSearchInit {\n    pub config: crate::WebSearchConfig,\n}\n\n#[derive(Debug)]\npub enum WebSearchOutput {\n    Enabled(bool),\n    Engines(Vec<SearchEngine>),\n}\n\n#[allow(unused_assignments)]\n#[relm4::component(pub)]\nimpl SimpleComponent for WebSearch {\n    type Init = WebSearchInit;\n    type Input = WebSearchInput;\n    type Output = WebSearchOutput;\n\n    view! {\n        #[root]\n        adw::ExpanderRow {\n            set_title_selectable: true,\n            set_show_enable_switch: true,\n            set_hexpand: true,\n            set_css_classes: &[\"enable-frame\"],\n            add_prefix = &gtk::Box {\n                set_orientation: gtk::Orientation::Horizontal,\n                set_halign: Align::Fill,\n                set_valign: Align::Center,\n                set_spacing: 15,\n                gtk::Label {\n                    set_label: \"Web search\",\n                },\n                gtk::Image::from_icon_name(\"dialog-information-symbolic\") {\n                    set_cursor_by_name: \"help\",\n                    set_tooltip_text: Some(\"Allows searching for the typed query in a web browser.\")\n                },\n            },\n            #[watch]\n            #[block_signal(h)]\n            set_enable_expansion: model.config.enabled,\n            connect_enable_expansion_notify[sender] => move |e| {sender.output_sender().emit(WebSearchOutput::Enabled(e.enables_expansion()));} @h,\n            #[watch]\n            set_expanded: model.config.enabled,\n\n            #[local_ref]\n            add_row = engines -> gtk::ListBox {\n                set_halign: Align::Fill,\n                set_valign: Align::Start,\n                set_expand: true,\n                set_selection_mode: SelectionMode::None,\n                set_css_classes: &[\"items-list\", \"boxed-list\"],\n                #[local_ref]\n                create -> adw::ButtonRow {\n                    set_title: \"Create new\",\n                    connect_activated[sender] => move |_b| {\n                        sender.input(WebSearchInput::OpenCreateNew);\n                    }\n                },\n            }\n        }\n    }\n\n    fn init(\n        init: Self::Init,\n        root: Self::Root,\n        sender: ComponentSender<Self>,\n    ) -> ComponentParts<Self> {\n        let list = gtk::ListBox::builder()\n            .selection_mode(SelectionMode::None)\n            .vexpand(true)\n            .hexpand(true)\n            .css_classes(vec![\"boxed-list\"])\n            .build();\n\n        let name = adw::EntryRow::builder().title(\"Name\").build();\n        list.append(&name);\n        let url = adw::EntryRow::builder().title(\"Url containing {}\").build();\n        list.append(&url);\n        let key = adw::EntryRow::builder()\n            .title(\"Key (single character)\")\n            .build();\n        list.append(&key);\n\n        let create_dialog = Alert::builder()\n            .transient_for(&root)\n            .launch(AlertSettings {\n                text: Some(\"Create New Search Engine\".to_string()),\n                secondary_text: None,\n                confirm_label: Some(String::from(\"Create\")),\n                cancel_label: Some(String::from(\"Cancel\")),\n                option_label: None,\n                is_modal: true,\n                destructive_accept: false,\n                extra_child: Some(list.into()),\n            })\n            .forward(sender.input_sender(), |res| match res {\n                AlertResponse::Confirm => WebSearchInput::Create,\n                AlertResponse::Option | AlertResponse::Cancel => WebSearchInput::Ignore,\n            });\n\n        let mut engines = FactoryVecDeque::builder()\n            .launch(gtk::ListBox::builder().build())\n            .forward(sender.input_sender(), WebSearchInput::Engine);\n\n        let mut l = engines.guard();\n        for engine in &init.config.engines {\n            l.push_back(engine.clone());\n        }\n        drop(l);\n\n        let create = adw::ButtonRow::default();\n\n        let model = Self {\n            config: init.config.clone(),\n            prev_config: init.config,\n            engines,\n            create,\n            create_dialog,\n            name,\n            url,\n            key,\n        };\n\n        let engines = model.engines.widget();\n        let create = &model.create;\n        let widgets = view_output!();\n        ComponentParts { model, widgets }\n    }\n\n    fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>) {\n        trace!(\"launcher_plugins::websearch::update: {message:?}\");\n        match message {\n            WebSearchInput::Ignore => {}\n            WebSearchInput::Set(config) => {\n                self.config = config;\n                let mut l = self.engines.guard();\n                l.clear();\n                for engine in &self.config.engines {\n                    l.push_back(engine.clone());\n                }\n                drop(l);\n            }\n            WebSearchInput::SetPrev(config) => {\n                self.prev_config = config;\n            }\n            WebSearchInput::Reset => {\n                sender.input(WebSearchInput::Set(self.prev_config.clone()));\n            }\n            WebSearchInput::Engine(msg) => match msg {\n                SearchOutput::Delete(ch) => {\n                    let engines = self\n                        .config\n                        .engines\n                        .iter()\n                        .filter(|e| e.key != ch)\n                        .cloned()\n                        .collect();\n                    sender\n                        .output_sender()\n                        .emit(WebSearchOutput::Engines(engines));\n                }\n            },\n            WebSearchInput::OpenCreateNew => {\n                self.create_dialog\n                    .widget()\n                    .set_transient_for(self.create.toplevel_window().as_ref());\n                self.create_dialog.emit(AlertMsg::Show);\n                self.create_dialog.widgets().gtk_window_12.set_modal(true); // TODO remove if https://github.com/Relm4/Relm4/issues/837 fixed\n            }\n            WebSearchInput::Create => {\n                let name = self.name.text().to_string().into_boxed_str();\n                let url = self.url.text().to_string().into_boxed_str();\n                let key = self.key.text().to_string().chars().next().unwrap_or('x');\n                self.create_dialog.emit(AlertMsg::Hide);\n                self.name.set_text(\"\");\n                self.url.set_text(\"\");\n                self.key.set_text(\"\");\n\n                let mut engines = self.config.engines.clone();\n                engines.push(SearchEngine { name, url, key });\n                sender\n                    .output_sender()\n                    .emit(WebSearchOutput::Engines(engines));\n            }\n        }\n    }\n}\n\n/// ------------------------------------------------------------------------------------------------\n\n#[derive(Debug)]\nstruct Search {\n    engine: SearchEngine,\n}\n\n#[derive(Debug)]\nenum SearchInput {}\n\n#[derive(Debug)]\npub enum SearchOutput {\n    Delete(char),\n}\n\n#[relm4::factory]\nimpl FactoryComponent for Search {\n    type Init = SearchEngine;\n    type Input = SearchInput;\n    type Output = SearchOutput;\n    type CommandOutput = ();\n    type ParentWidget = gtk::ListBox;\n\n    view! {\n        adw::ActionRow {\n            set_title: &format!(\"{} ({})\", self.engine.name, self.engine.key),\n            set_subtitle: &self.engine.url,\n            add_suffix = &gtk::Button::from_icon_name(\"delete-symbolic\") {\n                connect_clicked[sender, key = self.engine.key] => move |_| sender.output_sender().emit(SearchOutput::Delete(key)),\n            }\n        }\n    }\n\n    fn init_model(init: Self::Init, _index: &DynamicIndex, _sender: FactorySender<Self>) -> Self {\n        Self { engine: init }\n    }\n\n    fn update(&mut self, msg: Self::Input, _sender: FactorySender<Self>) {\n        match msg {};\n    }\n}\n"
  },
  {
    "path": "crates/config-edit-lib/src/components/mod.rs",
    "content": "pub mod changes;\nmod footer;\nmod generate;\nmod launcher;\nmod launcher_plugins;\npub mod nix_preview;\npub mod root;\nmod shortcut_dialog;\nmod switch;\nmod theme;\npub mod windows;\npub mod windows_overview;\n"
  },
  {
    "path": "crates/config-edit-lib/src/components/nix_preview.rs",
    "content": "use relm4::adw::prelude::*;\nuse relm4::gtk;\nuse relm4::{ComponentParts, ComponentSender, RelmWidgetExt, SimpleComponent};\n\n#[derive(Debug)]\npub struct NixPreview {}\n\n#[derive(Debug)]\npub enum NixPreviewInput {}\n\n#[derive(Debug)]\npub struct NixPreviewInit {}\n\n#[derive(Debug)]\npub enum NixPreviewOutput {}\n\n#[relm4::component(pub)]\nimpl SimpleComponent for NixPreview {\n    type Init = NixPreviewInit;\n    type Input = NixPreviewInput;\n    type Output = NixPreviewOutput;\n\n    view! {\n        gtk::Box {\n            set_orientation: gtk::Orientation::Vertical,\n            set_margin_all: 10,\n            gtk::Label {\n                set_label:  \"Nix preview (TODO)\"\n            }\n        }\n    }\n\n    fn init(\n        _init: Self::Init,\n        root: Self::Root,\n        _sender: ComponentSender<Self>,\n    ) -> ComponentParts<Self> {\n        let model = Self {};\n\n        let widgets = view_output!();\n        ComponentParts { model, widgets }\n    }\n\n    fn update(&mut self, _message: Self::Input, _sender: ComponentSender<Self>) {}\n}\n"
  },
  {
    "path": "crates/config-edit-lib/src/components/root.rs",
    "content": "use crate::components::changes::{\n    Changes, ChangesInit, ChangesInput, ChangesOutput, generate_items,\n};\nuse crate::components::footer::{Footer, FooterInit, FooterInput, FooterOutput};\nuse crate::components::generate::{Generate, GenerateInit, GenerateInput, GenerateOutput};\nuse crate::components::launcher::{Launcher, LauncherInit, LauncherInput, LauncherOutput};\nuse crate::components::launcher_plugins::LauncherPluginsOutput;\nuse crate::components::launcher_plugins::actions::ActionsOutput;\nuse crate::components::launcher_plugins::applications::ApplicationsOutput;\nuse crate::components::launcher_plugins::simple::SimplePluginOutput;\nuse crate::components::launcher_plugins::websearch::WebSearchOutput;\nuse crate::components::nix_preview::{NixPreview, NixPreviewInit};\nuse crate::components::switch::SwitchOutput;\nuse crate::components::theme::{Style, StyleInit, StyleInput, StyleOutput};\nuse crate::components::windows::{Windows, WindowsInit, WindowsInput, WindowsOutput};\nuse crate::components::windows_overview::WindowsOverviewOutput;\nuse crate::structs;\nuse crate::util::default_config;\nuse relm4::ComponentController;\nuse relm4::abstractions::Toaster;\nuse relm4::adw::gtk::{Align, SelectionMode};\nuse relm4::adw::prelude::*;\nuse relm4::gtk::glib;\nuse relm4::{Component, ComponentParts, ComponentSender, Controller, SimpleComponent};\nuse relm4::{adw, gtk};\nuse relm4_components::alert::{Alert, AlertMsg, AlertResponse, AlertSettings};\nuse std::path::Path;\nuse tracing::{debug, error, info, trace, warn};\n\n#[derive(Debug)]\npub struct Root {\n    config_file: Box<Path>,\n    css_file: Box<Path>,\n\n    is_generate_mode: bool,\n\n    config: crate::Config,\n    prev_config: crate::Config,\n\n    footer: Controller<Footer>,\n    alert_dialog: Controller<Alert>,\n    launcher: Controller<Launcher>,\n    windows: Controller<Windows>,\n    changes: Controller<Changes>,\n    nix_preview: Controller<NixPreview>,\n    style: Controller<Style>,\n    generate: Controller<Generate>,\n\n    view_stack: adw::ViewStack,\n    alert_dialog_changes_list: gtk::ListBox,\n    toaster: Toaster,\n}\n\n#[derive(Debug)]\npub enum RootInput {\n    Ignore,\n    Reload(bool),\n    CloseRequest,\n    Close,\n    Save(bool),\n    Regenerate,\n    AbortGenerate,\n\n    Reset,\n    SetConfig(crate::Config),\n    SetPrevConfig(crate::Config),\n\n    Windows(WindowsOutput),\n    Launcher(LauncherOutput),\n    Style(StyleOutput),\n    Changes(ChangesOutput),\n    Generate(GenerateOutput),\n}\n\n#[derive(Debug)]\npub struct RootInit {\n    pub config_file: Box<Path>,\n    pub system_data_dir: Box<Path>,\n    pub css_file: Box<Path>,\n    pub generate: bool,\n}\n\n#[derive(Debug)]\npub enum RootOutput {}\n\n#[allow(unused_assignments)]\n#[relm4::component(pub)]\nimpl SimpleComponent for Root {\n    type Init = RootInit;\n    type Input = RootInput;\n    type Output = RootOutput;\n\n    view! {\n        #[root]\n        adw::ApplicationWindow {\n            set_title: Some(\"Hyprshell Config Editor\"),\n            set_default_width: 900,\n            set_default_height: 700,\n            set_resizable: true,\n            #[wrap(Some)]\n            #[local_ref]\n            set_content = toast_overlay -> adw::ToastOverlay {\n                set_vexpand: true,\n                adw::ToolbarView {\n                    set_top_bar_style: adw::ToolbarStyle::Raised,\n                    set_bottom_bar_style: adw::ToolbarStyle::Flat,\n                    set_reveal_bottom_bars: true,\n                    set_reveal_top_bars: true,\n                    #[local_ref]\n                    add_bottom_bar = footer -> gtk::ActionBar {},\n                    #[wrap(Some)]\n                    set_content = &adw::Clamp {\n                        set_maximum_size: 1400,\n                        gtk::ScrolledWindow {\n                            #[local_ref]\n                            view_stack -> adw::ViewStack {\n                                set_enable_transitions: true,\n                                set_hhomogeneous: false,\n                                set_vhomogeneous: false,\n                                set_transition_duration: 150,\n                            }\n                        }\n                    },\n                    add_top_bar = &adw::HeaderBar {\n                        set_show_end_title_buttons: true,\n                        set_show_start_title_buttons: true,\n                        set_show_back_button: true,\n                        #[wrap(Some)]\n                        set_title_widget: view_stack_switcher = &adw::ViewSwitcherBar {\n                            #[watch]\n                            set_visible: !model.is_generate_mode,\n                            set_reveal: true,\n                        },\n                        pack_start = &gtk::Button {\n                            set_label: \"Generate new\",\n                            set_css_classes: &[\"pill\", \"warning\"],\n                            #[watch]\n                            set_visible: !model.is_generate_mode,\n                            connect_clicked[sender] => move |_| {\n                                sender.input(RootInput::Regenerate);\n                            }\n                        }\n                    }\n                },\n            },\n            connect_close_request[sender] => move |_| {\n                sender.input(RootInput::CloseRequest);\n                glib::Propagation::Stop\n            }\n        }\n    }\n\n    #[allow(clippy::too_many_lines)]\n    fn init(\n        init: Self::Init,\n        root: Self::Root,\n        sender: ComponentSender<Self>,\n    ) -> ComponentParts<Self> {\n        let config = default_config();\n        let config = structs::Config::from(config);\n\n        let footer: Controller<Footer> = Footer::builder()\n            .launch(FooterInit {\n                config_file: init.config_file.clone(),\n            })\n            .forward(sender.input_sender(), |msg| match msg {\n                FooterOutput::Reset => RootInput::Reset,\n                FooterOutput::Close => RootInput::CloseRequest,\n                FooterOutput::Save => RootInput::Save(false),\n                FooterOutput::Reload => RootInput::Reload(false),\n                FooterOutput::Abort => RootInput::AbortGenerate,\n            });\n\n        let changes_list = gtk::ListBox::builder()\n            .css_classes([\"items-list\", \"boxed-list\"])\n            .selection_mode(SelectionMode::None)\n            .show_separators(false)\n            .halign(Align::Center)\n            .valign(Align::Start)\n            .hexpand(true)\n            .build();\n        let alert_dialog = Alert::builder()\n            .transient_for(&root)\n            .launch(AlertSettings {\n                text: Some(\"Do you want to close before saving?\".to_string()),\n                secondary_text: None,\n                // secondary_text: Some(String::from(\"All unsaved changes will be lost\")),\n                confirm_label: Some(String::from(\"Close without saving\")),\n                cancel_label: Some(String::from(\"Cancel\")),\n                option_label: Some(String::from(\"Save and quit\")),\n                is_modal: true,\n                destructive_accept: true,\n                extra_child: Some(changes_list.clone().into()),\n            })\n            .forward(sender.input_sender(), |res| match res {\n                AlertResponse::Confirm => RootInput::Close,\n                AlertResponse::Option => RootInput::Save(true),\n                AlertResponse::Cancel => RootInput::Ignore,\n            });\n        let style = Style::builder()\n            .launch(StyleInit {\n                system_data_dir: init.system_data_dir.clone(),\n                css_file: init.css_file.clone(),\n            })\n            .forward(sender.input_sender(), RootInput::Style);\n        let windows = Windows::builder()\n            .launch(WindowsInit {\n                config: config.windows.clone(),\n            })\n            .forward(sender.input_sender(), RootInput::Windows);\n        let launcher = Launcher::builder()\n            .launch(LauncherInit {\n                config: config.windows.overview.launcher.clone(),\n            })\n            .forward(sender.input_sender(), RootInput::Launcher);\n        let nix_preview = NixPreview::builder().launch(NixPreviewInit {}).detach();\n        let changes = Changes::builder()\n            .launch(ChangesInit {\n                config: config.clone(),\n            })\n            .forward(sender.input_sender(), RootInput::Changes);\n        let generate = Generate::builder()\n            .launch(GenerateInit {\n                system_data_dir: init.system_data_dir,\n            })\n            .forward(sender.input_sender(), RootInput::Generate);\n\n        let view_stack = adw::ViewStack::builder().build();\n        let toaster = Toaster::default();\n        let model = Self {\n            config_file: init.config_file,\n            css_file: init.css_file.clone(),\n            config: config.clone(),\n            prev_config: config.clone(),\n            is_generate_mode: init.generate,\n            generate,\n            footer,\n            windows,\n            launcher,\n            changes,\n            style,\n            nix_preview,\n            alert_dialog,\n            toaster,\n            alert_dialog_changes_list: changes_list,\n            view_stack,\n        };\n        let toast_overlay = model.toaster.overlay_widget();\n        let footer = model.footer.widget();\n        let view_stack = &model.view_stack;\n        let widgets = view_output!();\n\n        model.view_stack.add_titled_with_icon(\n            model.style.widget(),\n            Some(\"style\"),\n            \"Style\",\n            \"viewimage\",\n        );\n        model.view_stack.add_titled_with_icon(\n            model.changes.widget(),\n            None,\n            \"Changes\",\n            \"document-edit-symbolic\",\n        );\n        model.view_stack.add_titled_with_icon(\n            model.nix_preview.widget(),\n            None,\n            \"Nix Preview\",\n            \"preview\",\n        );\n        model.view_stack.add_titled_with_icon(\n            model.windows.widget(),\n            Some(\"overview\"),\n            \"Windows\",\n            \"configure\",\n        );\n        if config.windows.overview.enabled {\n            model.view_stack.add_titled_with_icon(\n                model.launcher.widget(),\n                Some(\"launcher\"),\n                \"Launcher\",\n                \"configure\",\n            );\n        }\n        model.view_stack.set_visible_child_name(\"overview\");\n        widgets\n            .view_stack_switcher\n            .set_stack(Some(&model.view_stack));\n        sender.input(RootInput::Reload(true));\n        ComponentParts { model, widgets }\n    }\n\n    #[allow(clippy::too_many_lines)]\n    fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>) {\n        trace!(\"root::update: {message:?}\");\n        match message {\n            RootInput::Ignore => (),\n            RootInput::CloseRequest => {\n                debug!(\"close request\");\n                let changes = generate_items(\n                    &self.alert_dialog_changes_list,\n                    &self.config,\n                    &self.prev_config,\n                );\n                if changes {\n                    self.alert_dialog.emit(AlertMsg::Show);\n                    self.alert_dialog.widgets().gtk_window_12.set_modal(true); // TODO remove if https://github.com/Relm4/Relm4/issues/837 fixed\n                } else {\n                    sender.input(RootInput::Close);\n                }\n            }\n            RootInput::Close => {\n                relm4::main_application().quit();\n            }\n            RootInput::Regenerate => {\n                self.is_generate_mode = true;\n                self.footer.emit(FooterInput::GenerateMode(true));\n                self.generate.emit(GenerateInput::Start);\n                trace!(\"Adding generate tab\");\n                if self.view_stack.child_by_name(\"generate\").is_none() {\n                    self.view_stack.add_titled_with_icon(\n                        self.generate.widget(),\n                        Some(\"generate\"),\n                        \"Generate\",\n                        \"configure\",\n                    );\n                } else {\n                    trace!(\"Generate tab already exists\");\n                }\n                self.view_stack.set_visible_child_name(\"generate\");\n            }\n            RootInput::AbortGenerate => {\n                self.is_generate_mode = false;\n                self.footer.emit(FooterInput::GenerateMode(false));\n                if let Some(ch) = self.view_stack.child_by_name(\"generate\") {\n                    self.view_stack.remove(&ch);\n                }\n                self.view_stack.set_visible_child_name(\"overview\");\n            }\n            RootInput::Generate(msg) => match msg {\n                GenerateOutput::Finish(out) => {\n                    self.is_generate_mode = false;\n                    sender.input(RootInput::SetConfig(out));\n                    self.footer.emit(FooterInput::GenerateMode(false));\n                    if let Some(ch) = self.view_stack.child_by_name(\"generate\") {\n                        self.view_stack.remove(&ch);\n                    }\n                    self.view_stack.set_visible_child_name(\"overview\");\n                    sender.input(RootInput::Save(false));\n                }\n            },\n            RootInput::Reload(initial) => {\n                if self.config_file.exists() {\n                    match config_lib::load_and_migrate_config(&self.config_file, true) {\n                        Ok(c) => {\n                            let config = structs::Config::from(c);\n                            sender.input(RootInput::SetConfig(config.clone()));\n                            sender.input(RootInput::SetPrevConfig(config));\n                        }\n                        Err(err) => {\n                            warn!(\"Failed to load config: {err:#}\");\n                            self.toaster.add_toast(\n                                adw::Toast::builder()\n                                    .title(err.to_string())\n                                    .timeout(0)\n                                    .build(),\n                            );\n                        }\n                    }\n                } else if initial {\n                    warn!(\"Config file doesnt exist\");\n                    sender.input(RootInput::Regenerate);\n                } else {\n                    warn!(\"Config file was deleted\");\n                    let button = adw::Toast::builder()\n                        .title(\"Config file missing\")\n                        .button_label(\"Generate new\")\n                        .timeout(0)\n                        .build();\n                    let s = sender.clone();\n                    button.connect_button_clicked(move |_| {\n                        s.input(RootInput::Regenerate);\n                    });\n                    self.toaster.add_toast(button);\n\n                    let config = default_config();\n                    let config = structs::Config::from(config);\n                    sender.input(RootInput::SetConfig(config.clone()));\n                    sender.input(RootInput::SetPrevConfig(config));\n                }\n            }\n            RootInput::SetConfig(config) => {\n                self.config = config;\n\n                if self.config.windows.overview.enabled {\n                    trace!(\"Adding launcher tab\");\n                    if self.view_stack.child_by_name(\"launcher\").is_none() {\n                        self.view_stack.add_titled_with_icon(\n                            self.launcher.widget(),\n                            Some(\"launcher\"),\n                            \"Launcher\",\n                            \"configure\",\n                        );\n                    } else {\n                        trace!(\"Launcher tab already exists\");\n                    }\n                } else if let Some(ch) = self.view_stack.child_by_name(\"launcher\") {\n                    self.view_stack.remove(&ch);\n                }\n\n                self.windows\n                    .emit(WindowsInput::Set(self.config.windows.clone()));\n                self.launcher.emit(LauncherInput::Set(\n                    self.config.windows.overview.launcher.clone(),\n                ));\n                self.changes\n                    .emit(ChangesInput::SetConfig(self.config.clone()));\n            }\n            RootInput::SetPrevConfig(config) => {\n                self.prev_config = config;\n\n                self.windows\n                    .emit(WindowsInput::SetPrev(self.prev_config.windows.clone()));\n                self.launcher.emit(LauncherInput::SetPrev(\n                    self.prev_config.windows.overview.launcher.clone(),\n                ));\n                self.changes\n                    .emit(ChangesInput::SetPrevConfig(self.prev_config.clone()));\n            }\n            RootInput::Save(close) => {\n                match config_lib::write_config(\n                    &self.config_file,\n                    &(self.config.clone().into()),\n                    true,\n                ) {\n                    Ok(()) => {\n                        info!(\"Saved config to {}\", self.config_file.display());\n                        self.toaster.add_toast(\n                            adw::Toast::builder()\n                                .title(\"Saved\".to_string())\n                                .timeout(2)\n                                .build(),\n                        );\n                    }\n                    Err(err) => {\n                        error!(\"Failed to save config: {err:#}\");\n                        self.toaster.add_toast(\n                            adw::Toast::builder()\n                                .title(err.to_string())\n                                .timeout(0)\n                                .build(),\n                        );\n                    }\n                }\n\n                if close {\n                    sender.input(RootInput::Close);\n                }\n                sender.input(RootInput::SetPrevConfig(self.config.clone()));\n            }\n            RootInput::Reset => {\n                self.config = self.prev_config.clone();\n\n                self.windows.emit(WindowsInput::Reset);\n                self.launcher.emit(LauncherInput::Reset);\n                self.changes\n                    .emit(ChangesInput::SetConfig(self.config.clone()));\n            }\n            RootInput::Changes(msg) => match msg {\n                ChangesOutput::ChangesExist(changes_exist) => {\n                    self.footer.emit(FooterInput::ChangesExist(changes_exist));\n                }\n            },\n            RootInput::Style(msg) => match msg {\n                StyleOutput::Apply((name, content)) => {\n                    match std::fs::write(&self.css_file, content) {\n                        Ok(()) => {\n                            info!(\"Saved css from {name} to {}\", self.css_file.display());\n                            self.toaster.add_toast(\n                                adw::Toast::builder()\n                                    .title(\"Saved\".to_string())\n                                    .timeout(2)\n                                    .build(),\n                            );\n                        }\n                        Err(err) => {\n                            error!(\"Failed to save css from {name}: {err:#}\");\n                            self.toaster.add_toast(\n                                adw::Toast::builder()\n                                    .title(err.to_string())\n                                    .timeout(0)\n                                    .build(),\n                            );\n                        }\n                    }\n                    self.style.emit(StyleInput::Reload);\n                }\n            },\n            RootInput::Launcher(msg) => {\n                let r#ref = &mut self.config.windows.overview.launcher;\n                match msg {\n                    LauncherOutput::Modifier(modifier) => {\n                        r#ref.launch_modifier = modifier;\n                    }\n                    LauncherOutput::Width(width) => {\n                        r#ref.width = width;\n                    }\n                    LauncherOutput::MaxItems(max_items) => {\n                        r#ref.max_items = max_items;\n                    }\n                    LauncherOutput::DefaultTerminal(default_terminal) => match default_terminal {\n                        None => {\n                            r#ref.default_terminal = None;\n                        }\n                        Some(val) => {\n                            r#ref.default_terminal = Some(val);\n                        }\n                    },\n                    LauncherOutput::LauncherPlugins(msg) => match msg {\n                        LauncherPluginsOutput::Applications(msg) => match msg {\n                            ApplicationsOutput::Enabled(enabled) => {\n                                r#ref.plugins.applications.enabled = enabled;\n                            }\n                            ApplicationsOutput::ShowExecs(enabled) => {\n                                r#ref.plugins.applications.show_execs = enabled;\n                            }\n                            ApplicationsOutput::ShowActions(enabled) => {\n                                r#ref.plugins.applications.show_actions_submenu = enabled;\n                            }\n                            ApplicationsOutput::CacheWeeks(weeks) => {\n                                r#ref.plugins.applications.run_cache_weeks = weeks;\n                            }\n                        },\n                        LauncherPluginsOutput::Terminal(msg) => match msg {\n                            SimplePluginOutput::Enabled(enabled) => {\n                                r#ref.plugins.terminal.enabled = enabled;\n                            }\n                        },\n                        LauncherPluginsOutput::Shell(msg) => match msg {\n                            SimplePluginOutput::Enabled(enabled) => {\n                                r#ref.plugins.shell.enabled = enabled;\n                            }\n                        },\n                        LauncherPluginsOutput::Calculator(msg) => match msg {\n                            SimplePluginOutput::Enabled(enabled) => {\n                                r#ref.plugins.calc.enabled = enabled;\n                            }\n                        },\n                        LauncherPluginsOutput::FilePath(msg) => match msg {\n                            SimplePluginOutput::Enabled(enabled) => {\n                                r#ref.plugins.path.enabled = enabled;\n                            }\n                        },\n                        LauncherPluginsOutput::WebSearch(msg) => match msg {\n                            WebSearchOutput::Enabled(enabled) => {\n                                r#ref.plugins.websearch.enabled = enabled;\n                            }\n                            WebSearchOutput::Engines(engines) => {\n                                r#ref.plugins.websearch.engines = engines;\n                            }\n                        },\n                        LauncherPluginsOutput::Actions(msg) => match msg {\n                            ActionsOutput::Enabled(enabled) => {\n                                r#ref.plugins.actions.enabled = enabled;\n                            }\n                            ActionsOutput::Actions(actions) => {\n                                r#ref.plugins.actions.actions = actions;\n                            }\n                        },\n                    },\n                }\n                // propagate event back\n                sender.input(RootInput::SetConfig(self.config.clone()));\n            }\n            RootInput::Windows(msg) => {\n                let r#ref = &mut self.config.windows;\n                match msg {\n                    WindowsOutput::Enabled(enabled) => {\n                        r#ref.enabled = enabled;\n                    }\n                    WindowsOutput::Scale(scale) => {\n                        r#ref.scale = scale;\n                    }\n                    WindowsOutput::ItemsPerRow(items_per_row) => {\n                        r#ref.items_per_row = items_per_row;\n                    }\n                    WindowsOutput::Overview(msg) => match msg {\n                        WindowsOverviewOutput::Enabled(enabled) => {\n                            r#ref.overview.enabled = enabled;\n                        }\n                        WindowsOverviewOutput::Key(key) => r#ref.overview.key = key,\n                        WindowsOverviewOutput::Modifier(modifier) => {\n                            r#ref.overview.modifier = modifier;\n                        }\n                        WindowsOverviewOutput::FilterSameClass(enabled) => {\n                            r#ref.overview.same_class = enabled;\n                        }\n                        WindowsOverviewOutput::FilterWorkspace(enabled) => {\n                            r#ref.overview.current_workspace = enabled;\n                            // current monitor and current workspace are incompatible\n                            if enabled {\n                                r#ref.overview.current_monitor = false;\n                            }\n                        }\n                        WindowsOverviewOutput::FilterMonitor(enabled) => {\n                            r#ref.overview.current_monitor = enabled;\n                            // current monitor and current workspace are incompatible\n                            if enabled {\n                                r#ref.overview.current_workspace = false;\n                            }\n                        }\n                        WindowsOverviewOutput::ExcludeSpecialWorkspaces(\n                            exclude_special_workspaces,\n                        ) => {\n                            r#ref.overview.exclude_special_workspaces = exclude_special_workspaces;\n                        }\n                    },\n                    WindowsOutput::Switch(msg) => match msg {\n                        SwitchOutput::Enabled(enabled) => {\n                            r#ref.switch.enabled = enabled;\n                        }\n                        SwitchOutput::Key(key) => r#ref.switch.key = key,\n                        SwitchOutput::Modifier(modifier) => {\n                            r#ref.switch.modifier = modifier;\n                        }\n                        SwitchOutput::FilterSameClass(enabled) => {\n                            r#ref.switch.same_class = enabled;\n                        }\n                        SwitchOutput::FilterWorkspace(enabled) => {\n                            r#ref.switch.current_workspace = enabled;\n                            // current monitor and current workspace are incompatible\n                            if enabled {\n                                r#ref.switch.current_monitor = false;\n                            }\n                        }\n                        SwitchOutput::FilterMonitor(enabled) => {\n                            r#ref.switch.current_monitor = enabled;\n                            // current monitor and current workspace are incompatible\n                            if enabled {\n                                r#ref.switch.current_workspace = false;\n                            }\n                        }\n                        SwitchOutput::SwitchWorkspaces(enabled) => {\n                            r#ref.switch.switch_workspaces = enabled;\n                        }\n                        SwitchOutput::ExcludeSpecialWorkspaces(exclude_special_workspaces) => {\n                            r#ref.switch.exclude_special_workspaces = exclude_special_workspaces;\n                        }\n                    },\n                    WindowsOutput::Switch2(msg) => match msg {\n                        SwitchOutput::Enabled(enabled) => {\n                            r#ref.switch_2.enabled = enabled;\n                        }\n                        SwitchOutput::Key(key) => r#ref.switch_2.key = key,\n                        SwitchOutput::Modifier(modifier) => {\n                            r#ref.switch_2.modifier = modifier;\n                        }\n                        SwitchOutput::FilterSameClass(enabled) => {\n                            r#ref.switch_2.same_class = enabled;\n                        }\n                        SwitchOutput::FilterWorkspace(enabled) => {\n                            r#ref.switch_2.current_workspace = enabled;\n                            // current monitor and current workspace are incompatible\n                            if enabled {\n                                r#ref.switch_2.current_monitor = false;\n                            }\n                        }\n                        SwitchOutput::FilterMonitor(enabled) => {\n                            r#ref.switch_2.current_monitor = enabled;\n                            // current monitor and current workspace are incompatible\n                            if enabled {\n                                r#ref.switch_2.current_workspace = false;\n                            }\n                        }\n                        SwitchOutput::SwitchWorkspaces(enabled) => {\n                            r#ref.switch_2.switch_workspaces = enabled;\n                        }\n                        SwitchOutput::ExcludeSpecialWorkspaces(exclude_special_workspaces) => {\n                            r#ref.switch_2.exclude_special_workspaces = exclude_special_workspaces;\n                        }\n                    },\n                }\n                // propagate event back\n                sender.input(RootInput::SetConfig(self.config.clone()));\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "crates/config-edit-lib/src/components/shortcut_dialog.rs",
    "content": "use crate::structs::ConfigModifier;\nuse crate::util::{handle_key, mod_key_to_string};\nuse relm4::adw::prelude::*;\nuse relm4::gtk;\nuse relm4::gtk::{EventControllerKey, Label};\nuse relm4::{\n    Component, ComponentController, ComponentParts, ComponentSender, Controller, RelmWidgetExt,\n    SimpleComponent,\n};\nuse relm4_components::alert::{Alert, AlertMsg, AlertResponse, AlertSettings};\nuse tracing::{debug, trace, warn};\n\n#[derive(Debug)]\npub struct KeyboardShortcut {\n    key: Option<String>,\n    modifier: Option<ConfigModifier>,\n    is_visible: bool,\n    dialog: Controller<Alert>,\n    entry: Label,\n    label: Option<String>,\n}\n\n#[derive(Debug)]\npub enum KeyboardShortcutInput {\n    UpdateKey(String),\n    UpdateModifier(ConfigModifier),\n    HideKeyboardShortcut(bool),\n    ShowKeyboardShortcutDialog(Option<(ConfigModifier, String)>, Option<gtk::Widget>),\n    #[allow(dead_code)]\n    SetLabelText(Option<String>),\n}\n\n#[derive(Debug)]\npub struct KeyboardShortcutInit {\n    pub label: Option<String>,\n    pub icon: Option<String>,\n    pub init: Option<(ConfigModifier, String)>,\n}\n\n#[derive(Debug)]\npub enum KeyboardShortcutOutput {\n    SetKey(ConfigModifier, String),\n    Abort,\n    OpenRequest,\n}\n\n#[relm4::component(pub)]\nimpl SimpleComponent for KeyboardShortcut {\n    type Init = KeyboardShortcutInit;\n    type Input = KeyboardShortcutInput;\n    type Output = KeyboardShortcutOutput;\n\n    view! {\n        #[root]\n        gtk::Button {\n            set_icon_name?: &init.icon,\n            #[watch]\n            set_label?: &model.label,\n            #[watch]\n            set_css_classes: if model.is_visible { &[\"active\"] } else { &[\"not-active\"] },\n            connect_clicked[sender] => move |_b| { sender.output_sender().emit(KeyboardShortcutOutput::OpenRequest); },\n        },\n    }\n\n    fn init(\n        init: Self::Init,\n        root: Self::Root,\n        sender: ComponentSender<Self>,\n    ) -> ComponentParts<Self> {\n        use relm4::ComponentController;\n\n        let entry = Label::new(None);\n        let dialog = Alert::builder()\n            .transient_for(&root)\n            .launch(AlertSettings {\n                text: Some(\"Press Keyboard shortcut\".to_string()),\n                secondary_text: None,\n                confirm_label: Some(\"Use\".to_string()),\n                cancel_label: Some(\"Cancel\".to_string()),\n                option_label: None,\n                is_modal: true,\n                destructive_accept: false,\n                extra_child: Some(entry.clone().into()),\n            })\n            .forward(sender.input_sender(), |res| match res {\n                AlertResponse::Confirm => KeyboardShortcutInput::HideKeyboardShortcut(true),\n                AlertResponse::Cancel => KeyboardShortcutInput::HideKeyboardShortcut(false),\n                AlertResponse::Option => unreachable!(\"no option button in alert dialog\"),\n            });\n\n        // Attach an EventControllerKey to the alert dialog's window to print raw key events.\n        let key_controller = EventControllerKey::new();\n        let entry_2 = entry.clone();\n        let window = dialog.widgets().gtk_window_12.clone();\n        let send = sender.clone();\n        key_controller.connect_key_pressed(move |_, val, _, state| {\n            debug!(\"Raw key event - val: {}, state: {:?}\", val, state);\n            match handle_key(val, state) {\n                Some((key, r#mod, label)) => {\n                    entry.set_text(&label);\n                    send.input(KeyboardShortcutInput::UpdateKey(key));\n                    send.input(KeyboardShortcutInput::UpdateModifier(r#mod));\n                }\n                None => {\n                    entry.set_text(\"---\");\n                }\n            }\n            gtk::glib::Propagation::Stop\n        });\n        window.add_controller(key_controller);\n\n        let model = Self {\n            key: init.init.as_ref().map(|(_, key)| key.clone()),\n            modifier: init.init.map(|(r#mod, _)| r#mod),\n            is_visible: false,\n            entry: entry_2,\n            dialog,\n            label: init.label.clone(),\n        };\n\n        let widgets = view_output!();\n        ComponentParts { model, widgets }\n    }\n\n    fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>) {\n        trace!(\"shortcut_dialog::update: {message:?}\");\n        match message {\n            KeyboardShortcutInput::UpdateKey(key) => {\n                self.key = Some(key);\n            }\n            KeyboardShortcutInput::UpdateModifier(modifier) => {\n                self.modifier = Some(modifier);\n            }\n            KeyboardShortcutInput::HideKeyboardShortcut(confirm) => {\n                self.is_visible = false;\n                if confirm {\n                    if let (Some(key), Some(r#mod)) = (&self.key, &self.modifier) {\n                        sender\n                            .output_sender()\n                            .emit(KeyboardShortcutOutput::SetKey(*r#mod, key.clone()));\n                    } else {\n                        warn!(\"Tried to hide keyboard shortcut dialog without key or modifier\");\n                        sender.output_sender().emit(KeyboardShortcutOutput::Abort);\n                    }\n                } else {\n                    sender.output_sender().emit(KeyboardShortcutOutput::Abort);\n                }\n            }\n            KeyboardShortcutInput::ShowKeyboardShortcutDialog(initial, root) => {\n                if let Some(root) = root {\n                    self.dialog\n                        .widget()\n                        .set_transient_for(root.toplevel_window().as_ref());\n                }\n                self.is_visible = true;\n                self.key = initial.as_ref().map(|(_, key)| key.clone());\n                self.modifier = initial.map(|(r#mod, _)| r#mod);\n                if let (Some(r#mod), Some(key)) = (&self.modifier, &self.key) {\n                    self.entry.set_text(&mod_key_to_string(*r#mod, key));\n                } else {\n                    self.entry.set_text(\"\");\n                }\n                self.dialog.emit(AlertMsg::Show);\n                self.dialog.widgets().gtk_window_12.set_modal(true); // TODO remove if https://github.com/Relm4/Relm4/issues/837 fixed\n            }\n            KeyboardShortcutInput::SetLabelText(text) => {\n                self.label = text;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "crates/config-edit-lib/src/components/switch.rs",
    "content": "use crate::components::shortcut_dialog::{\n    KeyboardShortcut, KeyboardShortcutInit, KeyboardShortcutInput, KeyboardShortcutOutput,\n};\nuse crate::flags_csv;\nuse crate::structs::ConfigModifier;\nuse crate::util::{SetCursor, SetTextIfDifferent, mod_key_to_accelerator};\nuse relm4::adw::gtk::Align;\nuse relm4::adw::prelude::*;\nuse relm4::{Component, ComponentController, Controller};\nuse relm4::{ComponentParts, ComponentSender, SimpleComponent};\nuse relm4::{adw, gtk};\nuse tracing::trace;\n\n#[derive(Debug)]\npub struct Switch {\n    config: crate::Switch,\n    prev_config: crate::Switch,\n    name: &'static str,\n    keyboard_shortcut: Controller<KeyboardShortcut>,\n}\n\n#[derive(Debug)]\npub enum SwitchInput {\n    SetSwitch(crate::Switch),\n    SetPrevSwitch(crate::Switch),\n    ResetSwitch,\n    OpenKeyboardShortcut,\n}\n\n#[derive(Debug)]\npub struct SwitchInit {\n    pub config: crate::Switch,\n    pub name: &'static str,\n}\n\n#[derive(Debug)]\npub enum SwitchOutput {\n    Enabled(bool),\n    Key(String),\n    Modifier(ConfigModifier),\n    FilterSameClass(bool),\n    FilterWorkspace(bool),\n    FilterMonitor(bool),\n    SwitchWorkspaces(bool),\n    ExcludeSpecialWorkspaces(String),\n}\n\n#[relm4::component(pub)]\nimpl SimpleComponent for Switch {\n    type Init = SwitchInit;\n    type Input = SwitchInput;\n    type Output = SwitchOutput;\n\n    view! {\n        #[root]\n        adw::ExpanderRow {\n            set_title_selectable: true,\n            set_show_enable_switch: true,\n            set_hexpand: true,\n            set_css_classes: &[\"enable-frame\"],\n            add_prefix = &gtk::Box {\n                set_orientation: gtk::Orientation::Horizontal,\n                set_halign: Align::Fill,\n                set_valign: Align::Center,\n                set_spacing: 25,\n                gtk::Label {\n                    set_label: model.name,\n                },\n                model.keyboard_shortcut.widget().clone() -> gtk::Button {\n                    #[watch]\n                    set_sensitive: model.config.enabled,\n                },\n                _adw::ShortcutLabel::new(&mod_key_to_accelerator(model.config.modifier, &model.config.key)) {\n                    #[watch]\n                    set_accelerator: &mod_key_to_accelerator(model.config.modifier, &model.config.key),\n                    #[watch]\n                    set_css_classes: if model.config.enabled {\n                        if mod_key_to_accelerator(model.config.modifier, &model.config.key) == mod_key_to_accelerator(model.prev_config.modifier, &model.prev_config.key)\n                            { &[] }\n                        else\n                            { &[\"blue-label\"] }\n                    } else {\n                        &[\"gray-label\"]\n                    },\n                },\n            },\n            #[watch]\n            #[block_signal(h)]\n            set_enable_expansion: model.config.enabled,\n            connect_enable_expansion_notify[sender] => move |e| {sender.output_sender().emit(SwitchOutput::Enabled(e.enables_expansion()));} @h,\n            #[watch]\n            set_expanded: model.config.enabled,\n            add_row = &gtk::Box {\n                set_orientation: gtk::Orientation::Horizontal,\n                set_css_classes: &[\"frame-row\"],\n                set_spacing: 30,\n                gtk::Box {\n                    set_orientation: gtk::Orientation::Horizontal,\n                    set_spacing: 10,\n                    gtk::Label {\n                        #[watch]\n                        set_css_classes: if model.config.same_class == model.prev_config.same_class &&\n                            model.config.current_workspace == model.prev_config.current_workspace &&\n                            model.config.current_monitor == model.prev_config.current_monitor { &[] } else { &[\"blue-label\"]  },\n                        set_label: \"Filter\",\n                    },\n                    gtk::Image::from_icon_name(\"dialog-information-symbolic\") {\n                        set_cursor_by_name: \"help\",\n                        set_tooltip_text: Some(\"Filter the shown windows by the provided filters\")\n                    },\n                    adw::ExpanderRow {\n                        #[watch]\n                        set_title: &flags_csv!(model.config,same_class,current_monitor,current_workspace),\n                        set_hexpand: true,\n                        set_title_lines: 2,\n                        set_css_classes: &[\"item-expander\"],\n                        add_row = &adw::SwitchRow {\n                            #[watch]\n                            #[block_signal(h_2)]\n                            set_active: model.config.same_class,\n                            connect_active_notify[sender] => move |c| { sender.output_sender().emit(SwitchOutput::FilterSameClass(c.is_active())); } @h_2,\n                            set_title: \"Same class\",\n                        },\n                        add_row = &adw::SwitchRow {\n                            #[watch]\n                            #[block_signal(h_3)]\n                            set_active: model.config.current_workspace,\n                            connect_active_notify[sender] => move |c| { sender.output_sender().emit(SwitchOutput::FilterWorkspace(c.is_active())); } @h_3,\n                            set_title: \"Current workspace\",\n                        },\n                        add_row = &adw::SwitchRow {\n                            #[watch]\n                            #[block_signal(h_4)]\n                            set_active: model.config.current_monitor,\n                            connect_active_notify[sender] => move |c| { sender.output_sender().emit(SwitchOutput::FilterMonitor(c.is_active())); } @h_4,\n                            set_title: \"Current monitor\",\n                        }\n                    }\n                },\n                gtk::Box {\n                    set_orientation: gtk::Orientation::Horizontal,\n                    set_spacing: 10,\n                    gtk::Label {\n                        set_label: \"Switch Workspaces\",\n                        #[watch]\n                        set_css_classes: if model.config.switch_workspaces == model.prev_config.switch_workspaces { &[] } else { &[\"blue-label\"] },\n                    },\n                    gtk::Image::from_icon_name(\"dialog-information-symbolic\") {\n                        set_cursor_by_name: \"help\",\n                        set_tooltip_text: Some(\"Switch between workspaces in the Switch mode instead of windows\")\n                    },\n                    gtk::Switch {\n                        #[watch]\n                        #[block_signal(h_5)]\n                        set_active: model.config.switch_workspaces,\n                        connect_active_notify[sender] => move |e| { sender.output_sender().emit(SwitchOutput::SwitchWorkspaces(e.is_active())); } @h_5,\n                        set_valign: Align::Center,\n                    },\n                },\n                gtk::Box {\n                    set_orientation: gtk::Orientation::Horizontal,\n                    set_spacing: 10,\n                    gtk::Label {\n                        set_label: \"Exclude special workspaces (TODO)\",\n                        #[watch]\n                        set_css_classes: if model.config.exclude_special_workspaces == model.prev_config.exclude_special_workspaces { &[] } else { &[\"blue-label\"] },\n                    },\n                    gtk::Image::from_icon_name(\"dialog-information-symbolic\") {\n                        set_cursor_by_name: \"help\",\n                        set_tooltip_text: Some(\"Exclude special workspaces by regex \\n(hyprctl workspaces -j | jq \\\".[].name\\\")\")\n                    },\n                    gtk::Entry {\n                        set_input_purpose: gtk::InputPurpose::FreeForm,\n                        set_placeholder_text: Some(\"special:(monitor|second)\"),\n                        set_hexpand: true,\n                        set_valign: Align::Center,\n                        #[watch]\n                        #[block_signal(h_6)]\n                        set_text_if_different: &model.config.exclude_special_workspaces,\n                        connect_changed[sender] => move |e| { sender.output_sender().emit(SwitchOutput::ExcludeSpecialWorkspaces(e.text().into())) } @h_6,\n                    }\n                },\n            }\n        }\n    }\n\n    fn init(\n        init: Self::Init,\n        root: Self::Root,\n        sender: ComponentSender<Self>,\n    ) -> ComponentParts<Self> {\n        let outs = sender.output_sender().clone();\n        let ins = sender.input_sender().clone();\n        let keyboard_shortcut = KeyboardShortcut::builder()\n            .launch(KeyboardShortcutInit {\n                label: None,\n                icon: Some(\"keyboard-layout\".to_string()),\n                init: Some((init.config.modifier, init.config.key.clone())),\n            })\n            .connect_receiver(move |_, out| {\n                #[allow(clippy::match_wildcard_for_single_variants)]\n                match out {\n                    KeyboardShortcutOutput::SetKey(r#mod, key) => {\n                        outs.emit(SwitchOutput::Key(key));\n                        outs.emit(SwitchOutput::Modifier(r#mod));\n                    }\n                    KeyboardShortcutOutput::OpenRequest => {\n                        ins.emit(SwitchInput::OpenKeyboardShortcut);\n                    }\n                    _ => {}\n                }\n            });\n\n        let model = Self {\n            name: init.name,\n            config: init.config.clone(),\n            prev_config: init.config,\n            keyboard_shortcut,\n        };\n        let widgets = view_output!();\n        ComponentParts { model, widgets }\n    }\n\n    fn update(&mut self, message: Self::Input, _sender: ComponentSender<Self>) {\n        trace!(\"switch::update: {message:?}\");\n        match message {\n            SwitchInput::SetSwitch(config) => {\n                self.config = config;\n            }\n            SwitchInput::SetPrevSwitch(config) => {\n                self.prev_config = config;\n            }\n            SwitchInput::ResetSwitch => {\n                self.config = self.prev_config.clone();\n            }\n            SwitchInput::OpenKeyboardShortcut => {\n                self.keyboard_shortcut\n                    .emit(KeyboardShortcutInput::ShowKeyboardShortcutDialog(\n                        Some((self.config.modifier, self.config.key.clone())),\n                        None,\n                    ));\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "crates/config-edit-lib/src/components/theme.rs",
    "content": "use crate::util::{ScrollToPosition, SetCursor};\nuse config_lib::style::Theme;\nuse relm4::abstractions::Toaster;\nuse relm4::adw::gtk::Orientation;\nuse relm4::adw::prelude::*;\nuse relm4::gtk::{Align, Justification, gio};\nuse relm4::prelude::*;\nuse relm4::{ComponentParts, ComponentSender, RelmWidgetExt, SimpleComponent};\nuse relm4::{adw, gtk};\nuse std::fs;\nuse std::path::Path;\nuse tracing::{debug, trace, warn};\n\n#[derive(Debug)]\nstruct ThemeCarousel {\n    theme: Theme,\n}\n\n#[derive(Debug, Clone, Copy)]\nenum ThemeCarouselInput {}\n\n#[derive(Debug)]\nenum ThemeCarouselOutput {\n    Apply((String, String)),\n}\n\n#[relm4::factory]\nimpl FactoryComponent for ThemeCarousel {\n    type Init = Theme;\n    type Input = ThemeCarouselInput;\n    type Output = ThemeCarouselOutput;\n    type CommandOutput = ();\n    type ParentWidget = adw::Carousel;\n\n    view! {\n        gtk::Box {\n            // set_description: Some(&self.theme.path.display().to_string()),\n            set_orientation: Orientation::Vertical,\n            set_css_classes: &[\"theme\"],\n            set_halign: Align::Fill,\n            set_valign: Align::Fill,\n            gtk::Box {\n                set_halign: Align::Fill,\n                set_margin_bottom: 15,\n                set_homogeneous: true,\n                gtk::Image::from_icon_name(\"file-system-manager\") {\n                    set_tooltip_text: Some(&self.theme.path.display().to_string()),\n                    set_cursor_by_name: \"help\",\n                    set_pixel_size: 22,\n                    set_halign: Align::Start,\n                },\n                gtk::Label {\n                    set_text: &self.theme.data.name,\n                    set_css_classes: &[\"title-2\"],\n                },\n                gtk::Box {\n                    set_halign: Align::End,\n                    set_spacing: 15,\n                    if self.theme.is_current {\n                        gtk::Image::from_icon_name(\"checkmark\") {\n                            set_tooltip_text: Some(\"Current theme\"),\n                            set_pixel_size: 22\n                        }\n                    } else {\n                        gtk::Box {\n                        }\n                    },\n                    if self.theme.data.experimental {\n                        gtk::Image::from_icon_name(\"dialog-warning-symbolic\") {\n                            set_tooltip_text: Some(\"Experimental theme\"),\n                            set_pixel_size: 22\n                        }\n                    } else {\n                        gtk::Box {\n                        }\n                    },\n                    gtk::Button {\n                        set_label: \"Apply\",\n                        set_css_classes: &[\"suggested-action\", \"pill\"],\n                        connect_clicked[sender, style = self.theme.style.clone(), name = self.theme.name.clone()] => move |_| sender.output_sender().emit(ThemeCarouselOutput::Apply((name.clone(), style.clone()))),\n                    }\n                },\n            },\n            gtk::Label {\n                set_text: &self.theme.data.description,\n                set_halign: Align::Center,\n                set_justify: Justification::Center,\n                set_margin_bottom: 10,\n            },\n            gtk::Picture {\n                set_file:  self.theme.image_path.as_ref().map(gio::File::for_path).as_ref(),\n                set_css_classes: &[\"theme-image\"],\n                set_vexpand: true,\n                set_hexpand: false,\n                set_valign: Align::Fill,\n                set_halign: Align::Center,\n            }\n        }\n    }\n\n    fn init_model(init: Self::Init, _index: &DynamicIndex, _sender: FactorySender<Self>) -> Self {\n        Self { theme: init }\n    }\n\n    fn update(&mut self, msg: Self::Input, _sender: FactorySender<Self>) {\n        match msg {};\n    }\n}\n\n#[derive(Debug)]\npub struct Style {\n    err: Option<String>,\n    themes_list: FactoryVecDeque<ThemeCarousel>,\n    toaster: Toaster,\n    system_data_dir: Box<Path>,\n    css_file: Box<Path>,\n    initial_position: Option<usize>,\n}\n\n#[derive(Debug)]\npub enum StyleInput {\n    Reload,\n}\n\n#[derive(Debug)]\npub struct StyleInit {\n    pub system_data_dir: Box<Path>,\n    pub css_file: Box<Path>,\n}\n\n#[derive(Debug)]\npub enum StyleOutput {\n    Apply((String, String)),\n}\n\n#[allow(unused_assignments)]\n#[relm4::component(pub)]\nimpl SimpleComponent for Style {\n    type Init = StyleInit;\n    type Input = StyleInput;\n    type Output = StyleOutput;\n\n    view! {\n        #[root]\n        gtk::Box {\n            set_orientation: Orientation::Vertical,\n            set_margin_all: 10,\n            gtk::Label {\n                #[watch]\n                set_visible: model.err.is_some(),\n                #[watch]\n                set_text: match &model.err {\n                    Some(err) => err,\n                    None => \"\",\n                }\n            },\n            #[local_ref]\n            toast_overlay -> adw::ToastOverlay {\n                set_vexpand: true,\n                gtk::Box {\n                    set_orientation: Orientation::Vertical,\n                    set_spacing: 10,\n                    #[local_ref]\n                    themes_carousel -> adw::Carousel {\n                        set_orientation: Orientation::Horizontal,\n                        set_spacing: 5,\n                        set_css_classes: &[\"theme-carousel\"],\n                        set_vexpand: true,\n                        set_vexpand_set: true,\n                        connect_realize[refc = model.initial_position.clone()] => move |s| {\n                            if let Some(pos) = refc {\n                                debug!(\"Scroll to position: {:?}\", pos);\n                                s.scroll_to_pos(pos, false);\n                            }\n                        }\n                    },\n                    adw::CarouselIndicatorDots {\n                        set_carousel: Some(themes_carousel),\n                    }\n                }\n            }\n        }\n    }\n\n    fn init(\n        init: Self::Init,\n        root: Self::Root,\n        sender: ComponentSender<Self>,\n    ) -> ComponentParts<Self> {\n        let mut themes_list = FactoryVecDeque::builder()\n            .launch(adw::Carousel::builder().build())\n            .forward(sender.output_sender(), |output| match output {\n                ThemeCarouselOutput::Apply(content) => StyleOutput::Apply(content),\n            });\n\n        let model = match load_themes(&init.system_data_dir, &init.css_file) {\n            Ok((themes, errors)) => {\n                let mut v = themes_list.guard();\n                let mut index = 0;\n                for (idx, theme) in themes.into_iter().enumerate() {\n                    if theme.is_current {\n                        index = idx;\n                    }\n                    v.push_back(theme);\n                }\n                drop(v);\n                let toaster = Toaster::default();\n                for err in errors {\n                    toaster.add_toast(adw::Toast::builder().title(err).timeout(0).build());\n                }\n                Self {\n                    toaster,\n                    err: None,\n                    themes_list,\n                    css_file: init.css_file,\n                    system_data_dir: init.system_data_dir,\n                    initial_position: Some(index),\n                }\n            }\n            Err(err) => {\n                warn!(\"Failed to load themes: {err}\");\n                Self {\n                    toaster: Toaster::default(),\n                    err: Some(err),\n                    themes_list,\n                    css_file: init.css_file,\n                    system_data_dir: init.system_data_dir,\n                    initial_position: None,\n                }\n            }\n        };\n\n        let themes_carousel = model.themes_list.widget();\n        let toast_overlay = model.toaster.overlay_widget();\n        let widgets = view_output!();\n        ComponentParts { model, widgets }\n    }\n\n    fn update(&mut self, message: Self::Input, _sender: ComponentSender<Self>) {\n        trace!(\"style::update: {message:?}\");\n        match message {\n            StyleInput::Reload => match load_themes(&self.system_data_dir, &self.css_file) {\n                Ok((themes, errors)) => {\n                    let mut v = self.themes_list.guard();\n                    let mut index = 0;\n                    v.clear();\n                    for (idx, theme) in themes.into_iter().enumerate() {\n                        if theme.is_current {\n                            index = idx;\n                        }\n                        v.push_back(theme);\n                    }\n                    drop(v);\n                    self.themes_list.widget().scroll_to_pos(index, false);\n                    for err in errors {\n                        self.toaster\n                            .add_toast(adw::Toast::builder().title(err).timeout(0).build());\n                    }\n                }\n                Err(err) => {\n                    warn!(\"Failed to load themes: {err}\");\n                    self.err = Some(err);\n                }\n            },\n        }\n    }\n}\n\nfn load_themes(\n    system_data_dir: &Path,\n    css_path: &Path,\n) -> Result<(Vec<Theme>, Vec<String>), String> {\n    let current = fs::read_to_string(css_path)\n        .inspect_err(|err| {\n            warn!(\"Failed to read css file({}): {err:?}\", css_path.display());\n        })\n        .unwrap_or_default();\n\n    let path = system_data_dir.join(\"themes\");\n    let themes = config_lib::style::load_themes(&path, &current);\n    // trace!(\"Loaded themes: {:?}\", themes);\n    match themes {\n        Ok((themes, errors)) => {\n            debug!(\"Loaded {} themes, {} errors\", themes.len(), errors.len());\n            Ok((themes, errors.iter().map(ToString::to_string).collect()))\n        }\n        Err(err) => {\n            warn!(\"Failed to load themes: {err}\");\n            Err(err.to_string())\n        }\n    }\n}\n"
  },
  {
    "path": "crates/config-edit-lib/src/components/windows.rs",
    "content": "use crate::components::switch::{Switch, SwitchInit, SwitchInput, SwitchOutput};\nuse crate::components::windows_overview::{\n    WindowsOverview, WindowsOverviewInit, WindowsOverviewInput, WindowsOverviewOutput,\n};\nuse crate::util::SetCursor;\nuse relm4::ComponentController;\nuse relm4::adw::prelude::*;\nuse relm4::{\n    Component, ComponentParts, ComponentSender, Controller, RelmWidgetExt, SimpleComponent,\n};\nuse relm4::{adw, gtk};\nuse tracing::trace;\n\n#[derive(Debug)]\npub struct Windows {\n    pub overview: Controller<WindowsOverview>,\n    pub config: crate::Windows,\n    pub prev_config: crate::Windows,\n    pub switch: Controller<Switch>,\n    pub switch_2: Controller<Switch>,\n}\n\n#[derive(Debug)]\npub enum WindowsInput {\n    Set(crate::Windows),\n    SetPrev(crate::Windows),\n    Reset,\n}\n\n#[derive(Debug)]\npub enum WindowsOutput {\n    Enabled(bool),\n    Scale(f64),\n    ItemsPerRow(u8),\n    Overview(WindowsOverviewOutput),\n    Switch(SwitchOutput),\n    Switch2(SwitchOutput),\n}\n\n#[derive(Debug)]\npub struct WindowsInit {\n    pub config: crate::Windows,\n}\n\n#[relm4::component(pub)]\nimpl SimpleComponent for Windows {\n    type Init = WindowsInit;\n    type Input = WindowsInput;\n    type Output = WindowsOutput;\n\n    view! {\n        #[root]\n        gtk::Box {\n            set_orientation: gtk::Orientation::Horizontal,\n            set_margin_all: 10,\n            adw::ExpanderRow {\n                set_title_selectable: true,\n                set_show_enable_switch: true,\n                set_hexpand: true,\n                set_css_classes: &[\"enable-frame\"],\n                set_title: \"Windows (Overview and Switch)\",\n                #[watch]\n                #[block_signal(h)]\n                set_enable_expansion: model.config.enabled,\n                connect_enable_expansion_notify[sender] => move |e| {sender.output_sender().emit(WindowsOutput::Enabled(e.enables_expansion())); } @h,\n                #[watch]\n                set_expanded: model.config.enabled,\n                add_row = &gtk::Box {\n                    set_orientation: gtk::Orientation::Horizontal,\n                    set_css_classes: &[\"frame-row\"],\n                    set_spacing: 30,\n                    gtk::Box {\n                        set_orientation: gtk::Orientation::Horizontal,\n                        set_spacing: 10,\n                        gtk::Label {\n                            #[watch]\n                            set_css_classes: if (model.config.scale - model.prev_config.scale).abs() < 0.01 { &[] } else { &[\"blue-label\"]  },\n                            set_label: \"Scale\",\n                        },\n                        gtk::Image::from_icon_name(\"dialog-information-symbolic\") {\n                            set_cursor_by_name: \"help\",\n                            set_tooltip_text: Some(\"The scale used to scale down the real dimension the windows displayed in the overview. \\nCan be set from `0.5 < X > to 15.0`\")\n                        },\n                        gtk::SpinButton {\n                            set_adjustment: &gtk::Adjustment::new(1.0, 0.5, 15.0, 0.5, 1.0, 0.0),\n                            set_hexpand: true,\n                            set_digits: 2,\n                            #[watch]\n                            #[block_signal(h_2)]\n                            set_value: model.config.scale,\n                            connect_value_changed[sender] => move |e| { sender.output_sender().emit(WindowsOutput::Scale((e.value() * 100.0).round() / 100.0)); } @h_2,\n                        }\n                    },\n                    gtk::Box {\n                        set_orientation: gtk::Orientation::Horizontal,\n                        set_spacing: 10,\n                        gtk::Label {\n                            #[watch]\n                            set_css_classes: if model.config.items_per_row == model.prev_config.items_per_row { &[] } else { &[\"blue-label\"] },\n                            set_label: \"Items per row\",\n                        },\n                        gtk::Image::from_icon_name(\"dialog-information-symbolic\") {\n                            set_cursor_by_name: \"help\",\n                            set_tooltip_text: Some(\"The number of workspaces or windows to show per row. \\nIf you have 6 workspaces open and set this to 3, you will see 2 rows of 3 workspaces\")\n                        },\n                        gtk::SpinButton {\n                            set_adjustment: &gtk::Adjustment::new(1.0, 0.0, 50.0, 1.0, 5.0, 0.0),\n                            set_hexpand: true,\n                            set_digits: 0,\n                            #[watch]\n                            #[block_signal(h_3)]\n                            set_value: f64::from(model.config.items_per_row),\n                            connect_value_changed[sender] => move |e| { sender.output_sender().emit(WindowsOutput::ItemsPerRow(e.value() as u8)) } @h_3,\n                        }\n                    }\n                },\n                add_row = model.overview.widget(),\n                add_row = model.switch.widget(),\n                add_row = model.switch_2.widget(),\n            }\n        }\n    }\n\n    #[allow(clippy::cast_sign_loss)]\n    fn init(\n        init: Self::Init,\n        root: Self::Root,\n        sender: ComponentSender<Self>,\n    ) -> ComponentParts<Self> {\n        let windows_overview = WindowsOverview::builder()\n            .launch(WindowsOverviewInit {\n                config: init.config.overview.clone(),\n            })\n            .forward(sender.output_sender(), WindowsOutput::Overview);\n        let switch = Switch::builder()\n            .launch(SwitchInit {\n                config: init.config.switch.clone(),\n                name: \"Switch\",\n            })\n            .forward(sender.output_sender(), WindowsOutput::Switch);\n        let switch_2 = Switch::builder()\n            .launch(SwitchInit {\n                config: init.config.switch_2.clone(),\n                name: \"Switch 2 (TODO)\",\n            })\n            .forward(sender.output_sender(), WindowsOutput::Switch2);\n\n        let model = Self {\n            overview: windows_overview,\n            switch,\n            switch_2,\n            config: init.config.clone(),\n            prev_config: init.config,\n        };\n\n        let widgets = view_output!();\n        ComponentParts { model, widgets }\n    }\n\n    fn update(&mut self, message: WindowsInput, _sender: ComponentSender<Self>) {\n        trace!(\"windows::update: {message:?}\");\n        match message {\n            WindowsInput::Set(config) => {\n                self.config = config;\n                self.overview.emit(WindowsOverviewInput::SetOverview(\n                    self.config.overview.clone(),\n                ));\n                self.switch\n                    .emit(SwitchInput::SetSwitch(self.config.switch.clone()));\n                self.switch_2\n                    .emit(SwitchInput::SetSwitch(self.config.switch_2.clone()));\n            }\n            WindowsInput::SetPrev(config) => {\n                self.prev_config = config;\n                self.overview.emit(WindowsOverviewInput::SetPrevOverview(\n                    self.prev_config.overview.clone(),\n                ));\n                self.switch\n                    .emit(SwitchInput::SetPrevSwitch(self.prev_config.switch.clone()));\n                self.switch_2.emit(SwitchInput::SetPrevSwitch(\n                    self.prev_config.switch_2.clone(),\n                ));\n            }\n            WindowsInput::Reset => {\n                self.config = self.prev_config.clone();\n                self.overview.emit(WindowsOverviewInput::ResetOverview);\n                self.switch.emit(SwitchInput::ResetSwitch);\n                self.switch_2.emit(SwitchInput::ResetSwitch);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "crates/config-edit-lib/src/components/windows_overview.rs",
    "content": "use crate::components::shortcut_dialog::{\n    KeyboardShortcut, KeyboardShortcutInit, KeyboardShortcutInput, KeyboardShortcutOutput,\n};\nuse crate::flags_csv;\nuse crate::structs::ConfigModifier;\nuse crate::util::{SetCursor, SetTextIfDifferent, mod_key_to_accelerator};\nuse relm4::Component;\nuse relm4::adw::prelude::*;\nuse relm4::gtk::Align;\nuse relm4::{ComponentController, Controller};\nuse relm4::{ComponentParts, ComponentSender, SimpleComponent};\nuse relm4::{adw, gtk};\nuse tracing::trace;\n\n#[derive(Debug)]\npub struct WindowsOverview {\n    config: crate::Overview,\n    prev_config: crate::Overview,\n    keyboard_shortcut: Controller<KeyboardShortcut>,\n}\n\n#[derive(Debug)]\npub enum WindowsOverviewInput {\n    SetOverview(crate::Overview),\n    SetPrevOverview(crate::Overview),\n    ResetOverview,\n    OpenKeyboardShortcut,\n}\n\n#[derive(Debug)]\npub struct WindowsOverviewInit {\n    pub config: crate::Overview,\n}\n\n#[derive(Debug)]\npub enum WindowsOverviewOutput {\n    Enabled(bool),\n    Key(String),\n    Modifier(ConfigModifier),\n    FilterSameClass(bool),\n    FilterWorkspace(bool),\n    FilterMonitor(bool),\n    ExcludeSpecialWorkspaces(String),\n}\n\n#[relm4::component(pub)]\nimpl SimpleComponent for WindowsOverview {\n    type Init = WindowsOverviewInit;\n    type Input = WindowsOverviewInput;\n    type Output = WindowsOverviewOutput;\n\n    view! {\n        #[root]\n        adw::ExpanderRow {\n            set_title_selectable: true,\n            set_show_enable_switch: true,\n            set_hexpand: true,\n            set_css_classes: &[\"enable-frame\"],\n            add_prefix = &gtk::Box {\n                set_orientation: gtk::Orientation::Horizontal,\n                set_halign: Align::Fill,\n                set_valign: Align::Center,\n                set_spacing: 25,\n                gtk::Label {\n                    set_label: \"Overview + Launcher\",\n                },\n                model.keyboard_shortcut.widget().clone() -> gtk::Button {\n                    #[watch]\n                    set_sensitive: model.config.enabled,\n                },\n                _adw::ShortcutLabel::new(&mod_key_to_accelerator(model.config.modifier, &model.config.key)) {\n                    #[watch]\n                    set_accelerator: &mod_key_to_accelerator(model.config.modifier, &model.config.key),\n                    #[watch]\n                    set_css_classes: if model.config.enabled {\n                        if mod_key_to_accelerator(model.config.modifier, &model.config.key) == mod_key_to_accelerator(model.prev_config.modifier, &model.prev_config.key)\n                            { &[] }\n                        else\n                            { &[\"blue-label\"] }\n                    } else {\n                        &[\"gray-label\"]\n                    },\n                },\n            },\n            #[watch]\n            #[block_signal(h)]\n            set_enable_expansion: model.config.enabled,\n            connect_enable_expansion_notify[sender] => move |e| {sender.output_sender().emit(WindowsOverviewOutput::Enabled(e.enables_expansion()));} @h,\n            #[watch]\n            set_expanded: model.config.enabled,\n            add_row = &gtk::Box {\n                set_orientation: gtk::Orientation::Horizontal,\n                set_css_classes: &[\"frame-row\"],\n                set_spacing: 30,\n                gtk::Box {\n                    set_orientation: gtk::Orientation::Horizontal,\n                    set_spacing: 10,\n                    gtk::Label {\n                        #[watch]\n                        set_css_classes: if model.config.same_class == model.prev_config.same_class &&\n                            model.config.current_workspace == model.prev_config.current_workspace &&\n                            model.config.current_monitor == model.prev_config.current_monitor { &[] } else { &[\"blue-label\"]  },\n                        set_label: \"Filter\",\n                    },\n                    gtk::Image::from_icon_name(\"dialog-information-symbolic\") {\n                        set_cursor_by_name: \"help\",\n                        set_tooltip_text: Some(\"Filter the shown windows by the provided filters\")\n                    },\n                    adw::ExpanderRow {\n                        #[watch]\n                        set_title: &flags_csv!(model.config,same_class,current_monitor,current_workspace),\n                        set_hexpand: true,\n                        set_title_lines: 2,\n                        set_css_classes: &[\"item-expander\"],\n                        add_row = &adw::SwitchRow {\n                            #[watch]\n                            #[block_signal(h_1)]\n                            set_active: model.config.same_class,\n                            connect_active_notify[sender] => move |c| { sender.output_sender().emit(WindowsOverviewOutput::FilterSameClass(c.is_active())); } @h_1,\n                            set_title: \"Same class\",\n                        },\n                        add_row = &adw::SwitchRow {\n                            #[watch]\n                            #[block_signal(h_2)]\n                            set_active: model.config.current_workspace,\n                            connect_active_notify[sender] => move |c| { sender.output_sender().emit(WindowsOverviewOutput::FilterWorkspace(c.is_active())); } @h_2,\n                            set_title: \"Current workspace\",\n                        },\n                        add_row = &adw::SwitchRow {\n                            #[watch]\n                            #[block_signal(h_3)]\n                            set_active: model.config.current_monitor,\n                            connect_active_notify[sender] => move |c| { sender.output_sender().emit(WindowsOverviewOutput::FilterMonitor(c.is_active())); } @h_3,\n                            set_title: \"Current monitor\",\n                        }\n                    }\n                },\n                gtk::Box {\n                    set_orientation: gtk::Orientation::Horizontal,\n                    set_spacing: 10,\n                    gtk::Label {\n                        set_label: \"Exclude special workspaces (TODO)\",\n                        #[watch]\n                        set_css_classes: if model.config.exclude_special_workspaces == model.prev_config.exclude_special_workspaces { &[] } else { &[\"blue-label\"] },\n                    },\n                    gtk::Image::from_icon_name(\"dialog-information-symbolic\") {\n                        set_cursor_by_name: \"help\",\n                        set_tooltip_text: Some(\"Exclude special workspaces by regex \\n(hyprctl workspaces -j | jq \\\".[].name\\\")\")\n                    },\n                    gtk::Entry {\n                        set_input_purpose: gtk::InputPurpose::FreeForm,\n                        set_placeholder_text: Some(\"special:(monitor|second)\"),\n                        set_hexpand: true,\n                        set_valign: Align::Center,\n                        #[watch]\n                        #[block_signal(h_4)]\n                        set_text_if_different: &model.config.exclude_special_workspaces,\n                        connect_changed[sender] => move |e| { sender.output_sender().emit(WindowsOverviewOutput::ExcludeSpecialWorkspaces(e.text().into())); } @h_4,\n                    }\n                }\n            }\n        }\n    }\n\n    fn init(\n        init: Self::Init,\n        root: Self::Root,\n        sender: ComponentSender<Self>,\n    ) -> ComponentParts<Self> {\n        let outs = sender.output_sender().clone();\n        let ins = sender.input_sender().clone();\n        let keyboard_shortcut = KeyboardShortcut::builder()\n            .launch(KeyboardShortcutInit {\n                label: None,\n                icon: Some(\"keyboard-layout\".to_string()),\n                init: Some((init.config.modifier, init.config.key.clone())),\n            })\n            .connect_receiver(move |_, out| match out {\n                KeyboardShortcutOutput::SetKey(r#mod, key) => {\n                    outs.emit(WindowsOverviewOutput::Key(key));\n                    outs.emit(WindowsOverviewOutput::Modifier(r#mod));\n                }\n                KeyboardShortcutOutput::OpenRequest => {\n                    ins.emit(WindowsOverviewInput::OpenKeyboardShortcut);\n                }\n                _ => {}\n            });\n\n        let model = Self {\n            config: init.config.clone(),\n            prev_config: init.config,\n            keyboard_shortcut,\n        };\n        let widgets = view_output!();\n        ComponentParts { model, widgets }\n    }\n\n    fn update(&mut self, message: WindowsOverviewInput, _sender: ComponentSender<Self>) {\n        trace!(\"windows_overview::update: {message:?}\");\n        match message {\n            WindowsOverviewInput::SetOverview(config) => {\n                self.config = config;\n            }\n            WindowsOverviewInput::SetPrevOverview(config) => {\n                self.prev_config = config;\n            }\n            WindowsOverviewInput::ResetOverview => {\n                self.config = self.prev_config.clone();\n            }\n            WindowsOverviewInput::OpenKeyboardShortcut => {\n                self.keyboard_shortcut\n                    .emit(KeyboardShortcutInput::ShowKeyboardShortcutDialog(\n                        Some((self.config.modifier, self.config.key.clone())),\n                        None,\n                    ));\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "crates/config-edit-lib/src/lib.rs",
    "content": "mod components;\nmod start;\nmod structs;\nmod util;\n\npub const APPLICATION_EDIT_ID: &str = \"com.github.h3rmt.hyprshell-edit\";\npub use start::start;\n\n#[allow(clippy::wildcard_imports)]\npub(crate) use structs::*;\n"
  },
  {
    "path": "crates/config-edit-lib/src/start.rs",
    "content": "use crate::APPLICATION_EDIT_ID;\nuse crate::components::root::{Root, RootInit};\nuse relm4::RelmApp;\nuse relm4::adw::gdk::Display;\nuse relm4::adw::gtk::{\n    CssProvider, STYLE_PROVIDER_PRIORITY_APPLICATION, style_context_add_provider_for_display,\n};\nuse std::path::PathBuf;\n\n/// # Panics\n/// if no display was found\npub fn start(config_file: PathBuf, css_file: PathBuf, system_data_dir: PathBuf, generate: bool) {\n    let relm = RelmApp::new(&format!(\n        \"{}{}\",\n        APPLICATION_EDIT_ID,\n        if cfg!(debug_assertions) { \"-test\" } else { \"\" }\n    ))\n    .with_args(vec![]);\n\n    let provider_app = CssProvider::new();\n    provider_app.load_from_string(include_str!(\"styles.css\"));\n    style_context_add_provider_for_display(\n        &Display::default().expect(\"Could not connect to a display.\"),\n        &provider_app,\n        STYLE_PROVIDER_PRIORITY_APPLICATION,\n    );\n\n    relm.run::<Root>(RootInit {\n        config_file: config_file.into_boxed_path(),\n        system_data_dir: system_data_dir.into_boxed_path(),\n        css_file: css_file.into_boxed_path(),\n        generate,\n    });\n}\n"
  },
  {
    "path": "crates/config-edit-lib/src/structs.rs",
    "content": "use std::fmt;\nuse std::fmt::Formatter;\n\n#[derive(Debug, Clone)]\npub struct Config {\n    pub windows: Windows,\n}\n\n#[derive(Debug, Clone)]\npub struct Windows {\n    pub enabled: bool,\n    pub scale: f64,\n    pub items_per_row: u8,\n    pub overview: Overview,\n    pub switch: Switch,\n    pub switch_2: Switch,\n}\n\n#[derive(Debug, Clone)]\npub struct Overview {\n    pub enabled: bool,\n    pub launcher: Launcher,\n    pub key: String,\n    pub modifier: ConfigModifier,\n    pub same_class: bool,\n    pub current_workspace: bool,\n    pub current_monitor: bool,\n    pub exclude_special_workspaces: String,\n}\n\n#[derive(Debug, Clone)]\npub struct Launcher {\n    pub default_terminal: Option<String>,\n    pub launch_modifier: ConfigModifier,\n    pub width: u32,\n    pub max_items: u8,\n    pub show_when_empty: bool,\n    pub plugins: Plugins,\n}\n\n#[derive(Debug, Clone)]\npub struct Plugins {\n    pub applications: ApplicationsPluginConfig,\n    pub terminal: EmptyConfig,\n    pub shell: EmptyConfig,\n    pub websearch: WebSearchConfig,\n    pub calc: EmptyConfig,\n    pub path: EmptyConfig,\n    pub actions: ActionsPluginConfig,\n}\n\n#[derive(Debug, Clone)]\npub struct WebSearchConfig {\n    pub enabled: bool,\n    pub engines: Vec<config_lib::SearchEngine>,\n}\n\n#[derive(Debug, Clone)]\npub struct EmptyConfig {\n    pub enabled: bool,\n}\n\n#[derive(Debug, Clone)]\npub struct ActionsPluginConfig {\n    pub enabled: bool,\n    pub actions: Vec<config_lib::ActionsPluginAction>,\n}\n\n#[derive(Debug, Clone)]\npub struct ApplicationsPluginConfig {\n    pub enabled: bool,\n    pub run_cache_weeks: u8,\n    pub show_execs: bool,\n    pub show_actions_submenu: bool,\n}\n\n#[derive(Debug, Clone)]\n#[allow(clippy::struct_field_names)]\npub struct Switch {\n    pub enabled: bool,\n    pub modifier: ConfigModifier,\n    pub key: String,\n    pub same_class: bool,\n    pub current_workspace: bool,\n    pub current_monitor: bool,\n    pub switch_workspaces: bool,\n    pub exclude_special_workspaces: String,\n}\n\n#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]\npub enum ConfigModifier {\n    None,\n    Alt,\n    Super,\n    Ctrl,\n}\n\n#[allow(dead_code)]\nimpl ConfigModifier {\n    pub const fn strings() -> &'static [&'static str] {\n        &[\"Alt\", \"Super\", \"Ctrl\"]\n    }\n    pub const fn strings_with_none() -> &'static [&'static str] {\n        &[\"None\", \"Alt\", \"Super\", \"Ctrl\"]\n    }\n}\n\nimpl fmt::Display for ConfigModifier {\n    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {\n        match self {\n            Self::None => write!(f, \"\"),\n            Self::Alt => write!(f, \"Alt\"),\n            Self::Super => write!(f, \"Super\"),\n            Self::Ctrl => write!(f, \"Ctrl\"),\n        }\n    }\n}\n\nimpl TryFrom<u32> for ConfigModifier {\n    type Error = ();\n    fn try_from(value: u32) -> Result<Self, Self::Error> {\n        match value {\n            0 => Ok(Self::None),\n            1 => Ok(Self::Super),\n            2 => Ok(Self::Ctrl),\n            3 => Ok(Self::Alt),\n            _ => Err(()),\n        }\n    }\n}\n\nimpl From<ConfigModifier> for u32 {\n    fn from(value: ConfigModifier) -> Self {\n        match value {\n            ConfigModifier::None => 0,\n            ConfigModifier::Super => 1,\n            ConfigModifier::Ctrl => 2,\n            ConfigModifier::Alt => 3,\n        }\n    }\n}\n\nimpl From<config_lib::Modifier> for ConfigModifier {\n    fn from(value: config_lib::Modifier) -> Self {\n        match value {\n            config_lib::Modifier::None => Self::None,\n            config_lib::Modifier::Alt => Self::Alt,\n            config_lib::Modifier::Super => Self::Super,\n            config_lib::Modifier::Ctrl => Self::Ctrl,\n        }\n    }\n}\n\nimpl From<ConfigModifier> for config_lib::Modifier {\n    fn from(value: ConfigModifier) -> Self {\n        match value {\n            ConfigModifier::None => Self::None,\n            ConfigModifier::Alt => Self::Alt,\n            ConfigModifier::Super => Self::Super,\n            ConfigModifier::Ctrl => Self::Ctrl,\n        }\n    }\n}\n\nimpl From<config_lib::Config> for Config {\n    fn from(value: config_lib::Config) -> Self {\n        Self {\n            windows: value.windows.into(),\n        }\n    }\n}\n\nimpl From<Config> for config_lib::Config {\n    fn from(value: Config) -> Self {\n        Self {\n            version: config_lib::CURRENT_CONFIG_VERSION,\n            windows: value.windows.into(),\n        }\n    }\n}\n\nimpl From<Option<config_lib::Windows>> for Windows {\n    fn from(value: Option<config_lib::Windows>) -> Self {\n        let enabled = value.is_some();\n        let v = value.unwrap_or_default();\n        Self {\n            enabled,\n            scale: v.scale,\n            items_per_row: v.items_per_row,\n            overview: v.overview.into(),\n            switch: v.switch.into(),\n            switch_2: v.switch_2.into(),\n        }\n    }\n}\n\nimpl From<Windows> for Option<config_lib::Windows> {\n    fn from(value: Windows) -> Self {\n        if value.enabled {\n            Some(config_lib::Windows {\n                scale: value.scale,\n                items_per_row: value.items_per_row,\n                overview: value.overview.into(),\n                switch: value.switch.into(),\n                switch_2: value.switch_2.into(),\n            })\n        } else {\n            None\n        }\n    }\n}\n\nimpl From<Option<config_lib::Switch>> for Switch {\n    fn from(value: Option<config_lib::Switch>) -> Self {\n        let enabled = value.is_some();\n        let v = value.unwrap_or_default();\n        Self {\n            enabled,\n            modifier: v.modifier.into(),\n            key: v.key.to_string(),\n            same_class: v.filter_by.contains(&config_lib::FilterBy::SameClass),\n            current_workspace: v\n                .filter_by\n                .contains(&config_lib::FilterBy::CurrentWorkspace),\n            current_monitor: v.filter_by.contains(&config_lib::FilterBy::CurrentMonitor),\n            switch_workspaces: v.switch_workspaces,\n            exclude_special_workspaces: v.exclude_special_workspaces.to_string(),\n        }\n    }\n}\n\nimpl From<Switch> for Option<config_lib::Switch> {\n    fn from(value: Switch) -> Self {\n        if value.enabled {\n            let mut vec = vec![];\n            if value.same_class {\n                vec.push(config_lib::FilterBy::SameClass);\n            }\n            if value.current_workspace {\n                vec.push(config_lib::FilterBy::CurrentWorkspace);\n            }\n            if value.current_monitor {\n                vec.push(config_lib::FilterBy::CurrentMonitor);\n            }\n            Some(config_lib::Switch {\n                modifier: value.modifier.into(),\n                key: Box::from(value.key),\n                filter_by: vec,\n                switch_workspaces: value.switch_workspaces,\n                exclude_special_workspaces: Box::from(value.exclude_special_workspaces),\n            })\n        } else {\n            None\n        }\n    }\n}\n\nimpl From<Option<config_lib::Overview>> for Overview {\n    fn from(value: Option<config_lib::Overview>) -> Self {\n        let enabled = value.is_some();\n        let v = value.unwrap_or_default();\n        Self {\n            enabled,\n            launcher: v.launcher.into(),\n            key: v.key.to_string(),\n            modifier: v.modifier.into(),\n            same_class: v.filter_by.contains(&config_lib::FilterBy::SameClass),\n            current_workspace: v\n                .filter_by\n                .contains(&config_lib::FilterBy::CurrentWorkspace),\n            current_monitor: v.filter_by.contains(&config_lib::FilterBy::CurrentMonitor),\n            exclude_special_workspaces: v.exclude_special_workspaces.to_string(),\n        }\n    }\n}\n\nimpl From<Overview> for Option<config_lib::Overview> {\n    fn from(value: Overview) -> Self {\n        if value.enabled {\n            let mut vec = vec![];\n            if value.same_class {\n                vec.push(config_lib::FilterBy::SameClass);\n            }\n            if value.current_workspace {\n                vec.push(config_lib::FilterBy::CurrentWorkspace);\n            }\n            if value.current_monitor {\n                vec.push(config_lib::FilterBy::CurrentMonitor);\n            }\n            Some(config_lib::Overview {\n                launcher: value.launcher.into(),\n                key: Box::from(value.key),\n                modifier: value.modifier.into(),\n                filter_by: vec,\n                hide_filtered: false,\n                exclude_special_workspaces: Box::from(value.exclude_special_workspaces),\n            })\n        } else {\n            None\n        }\n    }\n}\n\nimpl From<config_lib::Launcher> for Launcher {\n    fn from(value: config_lib::Launcher) -> Self {\n        Self {\n            default_terminal: value.default_terminal.map(|s| s.to_string()),\n            launch_modifier: value.launch_modifier.into(),\n            width: value.width,\n            max_items: value.max_items,\n            show_when_empty: value.show_when_empty,\n            plugins: value.plugins.into(),\n        }\n    }\n}\n\nimpl From<Launcher> for config_lib::Launcher {\n    fn from(value: Launcher) -> Self {\n        Self {\n            default_terminal: value.default_terminal.map(Box::from),\n            launch_modifier: value.launch_modifier.into(),\n            width: value.width,\n            max_items: value.max_items,\n            show_when_empty: value.show_when_empty,\n            plugins: value.plugins.into(),\n        }\n    }\n}\n\nimpl From<Option<config_lib::EmptyConfig>> for EmptyConfig {\n    fn from(value: Option<config_lib::EmptyConfig>) -> Self {\n        let enabled = value.is_some();\n        Self { enabled }\n    }\n}\n\nimpl From<EmptyConfig> for Option<config_lib::EmptyConfig> {\n    fn from(value: EmptyConfig) -> Self {\n        if value.enabled {\n            Some(config_lib::EmptyConfig {})\n        } else {\n            None\n        }\n    }\n}\n\nimpl From<Option<config_lib::ActionsPluginConfig>> for ActionsPluginConfig {\n    fn from(value: Option<config_lib::ActionsPluginConfig>) -> Self {\n        let enabled = value.is_some();\n        let v = value.unwrap_or_default();\n        Self {\n            enabled,\n            actions: v.actions,\n        }\n    }\n}\n\nimpl From<ActionsPluginConfig> for Option<config_lib::ActionsPluginConfig> {\n    fn from(value: ActionsPluginConfig) -> Self {\n        if value.enabled {\n            Some(config_lib::ActionsPluginConfig {\n                actions: value.actions,\n            })\n        } else {\n            None\n        }\n    }\n}\n\nimpl From<Option<config_lib::ApplicationsPluginConfig>> for ApplicationsPluginConfig {\n    fn from(value: Option<config_lib::ApplicationsPluginConfig>) -> Self {\n        let enabled = value.is_some();\n        let v = value.unwrap_or_default();\n        Self {\n            enabled,\n            run_cache_weeks: v.run_cache_weeks,\n            show_execs: v.show_execs,\n            show_actions_submenu: v.show_actions_submenu,\n        }\n    }\n}\n\nimpl From<ApplicationsPluginConfig> for Option<config_lib::ApplicationsPluginConfig> {\n    fn from(value: ApplicationsPluginConfig) -> Self {\n        if value.enabled {\n            Some(config_lib::ApplicationsPluginConfig {\n                run_cache_weeks: value.run_cache_weeks,\n                show_execs: value.show_execs,\n                show_actions_submenu: value.show_actions_submenu,\n            })\n        } else {\n            None\n        }\n    }\n}\n\nimpl From<Option<config_lib::WebSearchConfig>> for WebSearchConfig {\n    fn from(value: Option<config_lib::WebSearchConfig>) -> Self {\n        let enabled = value.is_some();\n        let v = value.unwrap_or_default();\n        Self {\n            enabled,\n            engines: v.engines,\n        }\n    }\n}\n\nimpl From<WebSearchConfig> for Option<config_lib::WebSearchConfig> {\n    fn from(value: WebSearchConfig) -> Self {\n        if value.enabled {\n            Some(config_lib::WebSearchConfig {\n                engines: value.engines,\n            })\n        } else {\n            None\n        }\n    }\n}\n\nimpl From<config_lib::Plugins> for Plugins {\n    fn from(value: config_lib::Plugins) -> Self {\n        Self {\n            applications: value.applications.into(),\n            terminal: value.terminal.into(),\n            shell: value.shell.into(),\n            websearch: value.websearch.into(),\n            calc: value.calc.into(),\n            path: value.path.into(),\n            actions: value.actions.into(),\n        }\n    }\n}\n\nimpl From<Plugins> for config_lib::Plugins {\n    fn from(value: Plugins) -> Self {\n        Self {\n            applications: value.applications.into(),\n            terminal: value.terminal.into(),\n            shell: value.shell.into(),\n            websearch: value.websearch.into(),\n            calc: value.calc.into(),\n            path: value.path.into(),\n            actions: value.actions.into(),\n        }\n    }\n}\n"
  },
  {
    "path": "crates/config-edit-lib/src/styles.css",
    "content": "/* border around full expander row + items */\n.enable-frame > box {\n    padding: 4px;\n    border-radius: 18px;\n    border: 2px solid var(--border-color);\n}\n\n/* make hover for row rounded */\n.enable-frame > box > list > .activatable:hover {\n    border-radius: 18px;\n}\n\n/* remove background for row, increase font for title */\n.enable-frame > box > list {\n    background: transparent;\n    border-radius: 12px;\n    font-size: 1.4rem;\n    font-weight: 600;\n}\n\n/* remove background that interferes with rounden borders */\n.enable-frame > box > revealer > list {\n    background: unset;\n}\n\n/* disable hover effect on expanded row */\n.enable-frame > box > revealer > list > .activatable:hover {\n    background: unset;\n}\n\n/* some padding around the grid */\n.frame-row {\n    padding: 0.7rem;\n}\n\n/* set background color for the expander to bg color */\n.item-expander > box > revealer {\n    border-radius: 8px;\n    background-color: var(--popover-bg-color);\n}\n\n/* make it look like a button */\n.item-expander > box > list > .header {\n    border-radius: 8px;\n    /* yoinked from https://gitlab.gnome.org/GNOME/libadwaita/-/blob/main/src/stylesheet/widgets/_buttons.scss */\n    /* adjusts bg color the the same as the buttons */\n    background-color: color-mix(in srgb, currentColor 10%, transparent);\n}\n\n\n.footer {\n    padding: 6px 2px;\n}\n\n/* same style as `.enable-frame > box` to make row with switch look like expander */\n.bordered {\n    padding: 18px;\n    border-radius: 18px;\n    border: 2px solid var(--border-color);\n\n    font-size: 1.4rem;\n    font-weight: 600;\n}\n\n/* disabled style if calc plugin in not available */\n.disabled {\n    opacity: 0.5;\n    background: unset;\n}\n\n.items-list {\n    padding: 8px;\n    border-radius: 12px;\n}\n\n.items-list > row {\n    padding: 6px;\n}\n\n.changes-text {\n    padding: 16px;\n    border-radius: 12px;\n    border: 2px solid var(--border-color);\n}\n\n.theme-carousel {\n    /*border: 2px solid var(--border-color);*/\n    border-radius: 24px;\n}\n\n.theme {\n    padding: 24px;\n    /*background: red;*/\n}\n\n.theme-image {\n    border-radius: 18px;\n    border: 2px solid var(--border-color);\n    min-height: 100px;\n    min-width: 100px;\n}\n\n.blue-label {\n    color: color-mix(in srgb, currentColor 40%, #13bdda 60%);\n}\n\n.gray-label {\n    color: color-mix(in srgb, currentColor 40%, #464444 60%);\n}\n\n.generate-min-width {\n    min-width: 270px;\n}\n\nbutton.active {\n    border: 2px solid #62a600;\n    box-shadow: inset 0 0 12px #62a600;\n}\n\nbutton.not-active {\n    border: 2px solid transparent;\n    box-shadow: none;\n}"
  },
  {
    "path": "crates/config-edit-lib/src/util.rs",
    "content": "use crate::structs::ConfigModifier;\nuse relm4::gtk::gdk::{Cursor, Key, ModifierType};\nuse relm4::gtk::prelude::{Cast, EditableExt, WidgetExt};\nuse relm4::{adw, gtk};\n// use relm4::tokio::time::sleep;\nuse tracing::{instrument, warn};\n\npub trait SetTextIfDifferent {\n    fn set_text_if_different(&self, text: &str);\n}\n\nimpl SetTextIfDifferent for gtk::Entry {\n    fn set_text_if_different(&self, text: &str) {\n        use relm4::adw::prelude::EditableExt;\n        if self.text() != text {\n            self.set_text(text);\n        }\n    }\n}\n\nimpl SetTextIfDifferent for adw::EntryRow {\n    fn set_text_if_different(&self, text: &str) {\n        if self.text() != text {\n            self.set_text(text);\n        }\n    }\n}\n\npub trait SetCursor {\n    fn set_cursor_by_name(&self, name: &str);\n}\n\nimpl SetCursor for gtk::Image {\n    fn set_cursor_by_name(&self, name: &str) {\n        use relm4::adw::prelude::WidgetExt;\n        self.set_cursor(Cursor::from_name(name, None).as_ref());\n    }\n}\n\npub trait ScrollToPosition {\n    fn scroll_to_pos(&self, pos: usize, animate: bool);\n}\n\nimpl ScrollToPosition for adw::Carousel {\n    fn scroll_to_pos(&self, pos: usize, animate: bool) {\n        if let Some(wdg) = self.observe_children().into_iter().flatten().nth(pos)\n            && let Ok(widget) = wdg.downcast::<gtk::Widget>()\n        {\n            let s2 = self.clone();\n            // scuffed method to select a new widget (else it doesn't work on the first render)\n            gtk::glib::idle_add_local(move || {\n                s2.scroll_to(&widget, animate);\n                #[allow(clippy::cast_sign_loss)]\n                if s2.position() as usize == pos {\n                    gtk::glib::ControlFlow::Break\n                } else {\n                    gtk::glib::ControlFlow::Continue\n                }\n            });\n        }\n    }\n}\n\n#[allow(dead_code)]\npub trait SelectRow {\n    fn select_row_index_opt(&self, index: Option<i32>);\n    fn select_row_index(&self, index: i32);\n}\n\nimpl SelectRow for gtk::ListBox {\n    fn select_row_index_opt(&self, index: Option<i32>) {\n        self.unselect_all();\n        if let Some(index) = index {\n            self.select_row_index(index);\n        }\n    }\n\n    fn select_row_index(&self, index: i32) {\n        self.unselect_all();\n        if self.selected_row() != self.row_at_index(index) {\n            if let Some(row) = self.row_at_index(index) {\n                self.select_row(Some(&row));\n            } else {\n                warn!(\"select_row_index: row not found ({index})\");\n            }\n        }\n    }\n}\n\npub fn handle_key(val: Key, state: ModifierType) -> Option<(String, ConfigModifier, String)> {\n    let key_name = val.name()?;\n    let modifier = match val {\n        Key::Alt_L | Key::Alt_R => ConfigModifier::Alt,\n        Key::Control_L | Key::Control_R => ConfigModifier::Ctrl,\n        Key::Super_L | Key::Super_R => ConfigModifier::Super,\n        _ => match state {\n            ModifierType::NO_MODIFIER_MASK => ConfigModifier::None,\n            ModifierType::ALT_MASK => ConfigModifier::Alt,\n            ModifierType::CONTROL_MASK => ConfigModifier::Ctrl,\n            ModifierType::SUPER_MASK => ConfigModifier::Super,\n            _ => return None,\n        },\n    };\n\n    let label = if modifier == ConfigModifier::None {\n        key_name.to_string()\n    } else {\n        format!(\"{modifier} + {key_name}\")\n    };\n\n    Some((key_name.to_string(), modifier, label))\n}\n\npub fn default_config() -> config_lib::Config {\n    config_lib::Config {\n        windows: Some(config_lib::Windows::default()),\n        ..Default::default()\n    }\n}\n\n#[instrument(level = \"trace\", ret(level = \"trace\"))]\npub fn mod_key_to_accelerator(modifier: ConfigModifier, key: &str) -> String {\n    // correct some keys that can sometimes have wrong capitalization\n    let key = match &*key.to_lowercase() {\n        \"super_l\" => \"Super_L\",\n        \"super_r\" => \"Super_R\",\n        \"alt_l\" => \"Alt_L\",\n        \"alt_r\" => \"Alt_R\",\n        \"control_l\" => \"Control_L\",\n        \"control_r\" => \"Control_R\",\n        _ => key,\n    };\n\n    if modifier == ConfigModifier::None {\n        key.to_string()\n    } else {\n        format!(\"<{modifier}>{key}\")\n    }\n}\n\n#[instrument(level = \"trace\", ret(level = \"trace\"))]\npub fn mod_key_to_string(modifier: ConfigModifier, key: &str) -> String {\n    if modifier == ConfigModifier::None {\n        key.to_string()\n    } else {\n        format!(\"{modifier} + {key}\")\n    }\n}\n\n#[macro_export]\nmacro_rules! flags_csv {\n    ($s:expr, $($field:ident),+ $(,)?) => {{\n        [$( (stringify!($field), $s.$field) ),+]\n            .into_iter()\n            .filter(|(_, v)| *v)\n            .map(|(k, _)| k)\n            .collect::<Vec<&str>>()\n            .join(\", \")\n    }};\n}\n"
  },
  {
    "path": "crates/config-lib/Cargo.toml",
    "content": "[package]\nname = \"hyprshell-config-lib\"\ndocumentation = \"https://docs.rs/hyprshell-config-lib\"\nversion = \"4.9.5\"\ndescription = \"A library for reading, writing and migrating configuration files for hyprshell\"\nedition.workspace = true\nlicense.workspace = true\nauthors.workspace = true\nkeywords.workspace = true\nrepository.workspace = true\nrust-version.workspace = true\n\n[dependencies]\nanyhow.workspace = true\ntracing.workspace = true\ncore-lib.workspace = true\nserde.workspace = true\nserde_json.workspace = true\n\nsmart-default = { version = \"0.7.1\" }\nron = { version = \"0.12.0\" }\ntoml = { version = \"0.9.5\" }\nserde_json5 = { version = \"0.2.1\", optional = true }\n\n[features]\njson5_config = [\"dep:serde_json5\"]\nci_no_default_config_values = []\nlauncher_calc_plugin = []\n\n[lints]\nworkspace = true\n\n[dev-dependencies]\ntest-log.workspace = true\n"
  },
  {
    "path": "crates/config-lib/src/actions.rs",
    "content": "use crate::{ActionsPluginAction, ActionsPluginActionCustom};\nuse std::path::PathBuf;\n\npub trait ToAction {\n    fn to_action(self) -> ActionsPluginActionCustom;\n}\n\nimpl ToAction for ActionsPluginAction {\n    fn to_action(self) -> ActionsPluginActionCustom {\n        match self {\n            Self::LockScreen => ActionsPluginActionCustom {\n                names: vec![Box::from(\"Lock Screen\")],\n                details: Box::from(\"Lock the screen\"),\n                command: Box::from(\"loginctl lock-session\"),\n                icon: PathBuf::from(\"system-lock-screen\").into_boxed_path(),\n            },\n            Self::Hibernate => ActionsPluginActionCustom {\n                names: vec![Box::from(\"Hibernate\")],\n                details: Box::from(\"Hibernate the computer\"),\n                command: Box::from(\"systemctl hibernate\"),\n                icon: PathBuf::from(\"system-hibernate\").into_boxed_path(),\n            },\n            Self::Logout => ActionsPluginActionCustom {\n                names: vec![Box::from(\"Log Out\"), Box::from(\"Logout\")],\n                details: Box::from(\"Log out of the session\"),\n                command: Box::from(\"loginctl terminate-session self\"),\n                icon: PathBuf::from(\"system-log-out\").into_boxed_path(),\n            },\n            Self::Reboot => ActionsPluginActionCustom {\n                names: vec![Box::from(\"Reboot\"), Box::from(\"Restart\")],\n                details: Box::from(\"Reboot the computer\"),\n                command: Box::from(\"systemctl reboot\"),\n                icon: PathBuf::from(\"system-reboot\").into_boxed_path(),\n            },\n            Self::Shutdown => ActionsPluginActionCustom {\n                names: vec![Box::from(\"Shut Down\"), Box::from(\"Power off\")],\n                details: Box::from(\"Shut down the computer\"),\n                command: Box::from(\"systemctl poweroff\"),\n                icon: PathBuf::from(\"system-shutdown\").into_boxed_path(),\n            },\n            Self::Suspend => ActionsPluginActionCustom {\n                names: vec![Box::from(\"Sleep\"), Box::from(\"Suspend\")],\n                details: Box::from(\"Put the computer to sleep\"),\n                command: Box::from(\"systemctl suspend\"),\n                icon: PathBuf::from(\"system-suspend\").into_boxed_path(),\n            },\n            Self::Custom(c) => c,\n        }\n    }\n}\n"
  },
  {
    "path": "crates/config-lib/src/check.rs",
    "content": "use crate::Config;\nuse anyhow::bail;\n\npub fn check(config: &Config) -> anyhow::Result<()> {\n    if config\n        .windows\n        .as_ref()\n        .is_some_and(|w| w.scale >= 15f64 || w.scale <= 0f64)\n    {\n        bail!(\"Scale factor must be less than 15 and greater than 0\");\n    }\n\n    if config\n        .windows\n        .as_ref()\n        .and_then(|w| w.overview.as_ref())\n        .is_some_and(|o| matches!(&*o.key, \"super\" | \"alt\" | \"control\" | \"ctrl\"))\n    {\n        bail!(\n            \"If a modifier key is used to open it must include _l or _r at the end. (e.g. super_l, alt_r, etc)\\nctrl_l / _r is NOT a valid modifier key, only control_l / _r is\"\n        );\n    }\n\n    if let Some(l) = &config\n        .windows\n        .as_ref()\n        .and_then(|w| w.overview.as_ref().map(|o| &o.launcher))\n    {\n        if let Some(dt) = &l.default_terminal\n            && dt.is_empty()\n        {\n            bail!(\"Default terminal command cannot be empty\");\n        }\n\n        let mut used: Vec<char> = vec![];\n        for engine in l\n            .plugins\n            .websearch\n            .as_ref()\n            .map_or(&vec![], |ws| &ws.engines)\n        {\n            if engine.url.is_empty() {\n                bail!(\"Search engine url cannot be empty\");\n            }\n            if engine.name.is_empty() {\n                bail!(\"Search engine name cannot be empty\");\n            }\n            if used.contains(&engine.key) {\n                bail!(\"Duplicate search engine key: {}\", engine.key);\n            }\n            used.push(engine.key);\n        }\n        if l.plugins.calc.is_some() {\n            #[cfg(not(feature = \"launcher_calc_plugin\"))]\n            {\n                bail!(\"Calc Plugin enabled but not compiled in, please enable the calc feature\");\n            }\n        }\n    }\n\n    Ok(())\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::structs::*;\n\n    fn full() -> Config {\n        Config {\n            windows: Some(Windows {\n                overview: Some(Overview::default()),\n                switch: Some(Switch::default()),\n                ..Default::default()\n            }),\n            ..Default::default()\n        }\n    }\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_valid_config() {\n        let config = full();\n        assert!(check(&config).is_ok());\n    }\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_invalid_scale() {\n        let mut config = full();\n        config\n            .windows\n            .as_mut()\n            .expect(\"config option missing\")\n            .scale = 20.0;\n        assert!(check(&config).is_err());\n        config\n            .windows\n            .as_mut()\n            .expect(\"config option missing\")\n            .scale = 0.0;\n        assert!(check(&config).is_err());\n    }\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_invalid_key() {\n        let mut config = full();\n        let overview = config\n            .windows\n            .as_mut()\n            .expect(\"config option missing\")\n            .overview\n            .as_mut()\n            .expect(\"config option missing\");\n        overview.key = Box::from(\"super\");\n        assert!(check(&config).is_err());\n    }\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_duplicate_engine_key() {\n        let mut config = full();\n        let launcher = &mut config\n            .windows\n            .as_mut()\n            .expect(\"config option missing\")\n            .overview\n            .as_mut()\n            .expect(\"config option missing\")\n            .launcher;\n        if let Some(ws) = launcher.plugins.websearch.as_mut() {\n            ws.engines.push(SearchEngine {\n                key: 'g',\n                name: Box::from(\"Google2\"),\n                url: Box::from(\"https://google2.com\"),\n            });\n        }\n        assert!(check(&config).is_err());\n    }\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_empty_engine_url() {\n        let mut config = full();\n        let launcher = &mut config\n            .windows\n            .as_mut()\n            .expect(\"config option missing\")\n            .overview\n            .as_mut()\n            .expect(\"config option missing\")\n            .launcher;\n        if let Some(ws) = launcher.plugins.websearch.as_mut() {\n            ws.engines[0].url = Box::from(\"\");\n        }\n        assert!(check(&config).is_err());\n    }\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_empty_engine_name() {\n        let mut config = full();\n        let launcher = &mut config\n            .windows\n            .as_mut()\n            .expect(\"config option missing\")\n            .overview\n            .as_mut()\n            .expect(\"config option missing\")\n            .launcher;\n        if let Some(ws) = launcher.plugins.websearch.as_mut() {\n            ws.engines[0].name = Box::from(\"\");\n        }\n        assert!(check(&config).is_err());\n    }\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_empty_terminal() {\n        let mut config = full();\n        let launcher = &mut config\n            .windows\n            .as_mut()\n            .expect(\"config option missing\")\n            .overview\n            .as_mut()\n            .expect(\"config option missing\")\n            .launcher;\n        launcher.default_terminal = Some(Box::from(\"\"));\n        assert!(check(&config).is_err());\n    }\n}\n"
  },
  {
    "path": "crates/config-lib/src/explain.rs",
    "content": "use crate::Config;\nuse std::fmt::Write;\nuse std::path::Path;\n\nconst BOLD: &str = \"\\x1b[1m\";\nconst ITALIC: &str = \"\\x1b[3m\";\nconst BLUE: &str = \"\\x1b[34m\";\nconst GREEN: &str = \"\\x1b[32m\";\nconst RESET: &str = \"\\x1b[0m\";\n\n#[must_use]\npub fn explain(config: &Config, config_file: Option<&Path>, enable_color: bool) -> String {\n    let (bold, italic, blue, green, reset) = if enable_color {\n        (BOLD, ITALIC, BLUE, GREEN, RESET)\n    } else {\n        (\"\", \"\", \"\", \"\", \"\")\n    };\n\n    let mut builder = config_file.map_or_else(String::new, |config_file| {\n        let config_file_display = config_file.display();\n        format!(\n            \"{bold}{green}Config is valid{reset} ({config_file_display})\\n{bold}Explanation{reset} ({blue}blue{reset} are keys, {bold}{blue}bold blue{reset} keys can be configured in config):{reset}\\n\",\n        )\n    });\n\n    if let Some(windows) = &config.windows {\n        if let Some(overview) = &windows.overview {\n            let _ = builder.write_str(&format!(\n                \"Use {bold}{blue}{}{reset} + {bold}{blue}{}{reset} to open the Overview. Use {blue}tab{reset} and {blue}grave{reset} / {blue}shift{reset} + {blue}tab{reset} to select a different window, press {blue}return{reset} to switch\\n\\\n                You can also use the {blue}arrow keys{reset} or {bold}{blue}{}{reset} + vim keys to navigate the workspaces. Use {blue}Esc{reset} to close the overview.\\n\",\n                overview.modifier,\n                overview.key,\n                overview.launcher.launch_modifier\n            ));\n            let _ = builder.write_str(&format!(\n                \"After opening the Overview the {bold}Launcher{reset} is available:\\n\"\n            ));\n            let mut any_plugin = false;\n            if let Some(_applications) = overview.launcher.plugins.applications.as_ref() {\n                any_plugin = true;\n                let _ = builder.write_str(&format!(\"\\t- Start typing to search through applications (sorted by how often they were opened). Press {blue}return{reset} to launch the first app, use {blue}Ctrl{reset} + {blue}1{reset}/{blue}2{reset}/{blue}3{reset}/... to open the second, third, etc.\\n\"));\n            }\n            if overview.launcher.plugins.terminal.is_some() {\n                any_plugin = true;\n                let _ = builder.write_str(&format!(\n                    \"\\t- Press {blue}Ctrl{reset} + {blue}t{reset} to run the typed command in a terminal.\\n\"\n                ));\n            }\n            if overview.launcher.plugins.shell.is_some() {\n                any_plugin = true;\n                let _ = builder.write_str(&format!(\n                    \"\\t- Press {blue}Ctrl{reset} + {blue}r{reset} to run the typed command in the background.\\n\",\n                ));\n            }\n            if let Some(engines) = &overview.launcher.plugins.websearch {\n                any_plugin = true;\n                let _ =    builder.write_str(&format!(\"\\t- Press {blue}Ctrl{reset} + {bold}{blue}<key>{reset} to search the typed text in any of the configured SearchEngines: {}.\\n\",\n                                                      engines.engines.iter().map(|e| e.name.to_string()).collect::<Vec<_>>().join(\", \")));\n            }\n            if overview.launcher.plugins.calc.is_some() {\n                any_plugin = true;\n                let _ =   builder.write_str(\n                    \"\\t- Typing a mathematical expression will calculate it and display the result in the launcher.\\n\",\n                );\n            }\n            if overview.launcher.plugins.path.is_some() {\n                any_plugin = true;\n                let _ = builder.write_str(\n                    \"\\t- Paths (starting with ~ or /) can be open in default file-manager.\\n\",\n                );\n            }\n            if overview.launcher.plugins.actions.is_some() {\n                any_plugin = true;\n                let _ = builder.write_str(\n                    \"\\t- Type Reboot/Shutdown/etc. to run corresponding commands. Type `actions` to see all available ones.\\n\",\n                );\n            }\n            if !any_plugin {\n                let _ = builder.write_str(&format!(\n                    \"{italic}\\t<No plugins enabled in launcher>{reset}\\n\"\n                ));\n            }\n        } else {\n            let _ = builder.write_str(&format!(\"{italic}<Overview disabled>{reset}\\n\"));\n        }\n        builder.push('\\n');\n\n        if let Some(switch) = &windows.switch {\n            let _ = builder.write_str(&format!(\n                \"Press {bold}{blue}{}{reset} + {blue}tab{reset} and hold {bold}{blue}{}{reset} to view recently used applications. Press {blue}tab{reset} and {blue}grave{reset} / {blue}shift{reset} + {blue}tab{reset} to select a different window, release {bold}{blue}{}{reset} to close the window.\\n\",\n                switch.modifier,\n                switch.modifier,\n                switch.modifier,\n            ));\n        } else {\n            let _ = builder.write_str(&format!(\"{italic}<Switch mode disabled>{reset}\\n\"));\n        }\n    } else {\n        let _ = builder.write_str(&format!(\"{italic}<Windows disabled>{reset}\\n\"));\n    }\n\n    builder\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::structs::*;\n    use std::path::PathBuf;\n\n    fn create_test_config() -> Config {\n        Config {\n            windows: Some(Windows {\n                overview: Some(Overview::default()),\n                switch: Some(Switch::default()),\n                ..Default::default()\n            }),\n            ..Default::default()\n        }\n    }\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_explain_with_overview() {\n        const CONFIG: &str = r\"Config is valid (/test/config.ron)\nExplanation (blue are keys, bold blue keys can be configured in config):\nUse Super + Super_L to open the Overview. Use tab and grave / shift + tab to select a different window, press return to switch\nYou can also use the arrow keys or Ctrl + vim keys to navigate the workspaces. Use Esc to close the overview.\nAfter opening the Overview the Launcher is available:\n\t- Start typing to search through applications (sorted by how often they were opened). Press return to launch the first app, use Ctrl + 1/2/3/... to open the second, third, etc.\n\t- Press Ctrl + t to run the typed command in a terminal.\n\t- Press Ctrl + <key> to search the typed text in any of the configured SearchEngines: Google, Wikipedia.\n\t- Typing a mathematical expression will calculate it and display the result in the launcher.\n\t- Paths (starting with ~ or /) can be open in default file-manager.\n\t- Type Reboot/Shutdown/etc. to run corresponding commands. Type `actions` to see all available ones.\n\nPress Alt + tab and hold Alt to view recently used applications. Press tab and grave / shift + tab to select a different window, release Alt to close the window.\n\";\n        let config = create_test_config();\n        let path = PathBuf::from(\"/test/config.ron\");\n        let result = explain(&config, Some(&path), false);\n        assert_eq!(result, CONFIG);\n    }\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_explain_without_overview() {\n        const CONFIG: &str = r\"Config is valid (/test/config.ron)\nExplanation (blue are keys, bold blue keys can be configured in config):\n<Overview disabled>\n\nPress Alt + tab and hold Alt to view recently used applications. Press tab and grave / shift + tab to select a different window, release Alt to close the window.\n\";\n        let mut config = create_test_config();\n        config\n            .windows\n            .as_mut()\n            .expect(\"config option missing\")\n            .overview = None;\n        let path = PathBuf::from(\"/test/config.ron\");\n        let result = explain(&config, Some(&path), false);\n        assert_eq!(result, CONFIG);\n    }\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_explain_without_switch() {\n        const CONFIG: &str = r\"Config is valid (/test/config.ron)\nExplanation (blue are keys, bold blue keys can be configured in config):\nUse Super + Super_L to open the Overview. Use tab and grave / shift + tab to select a different window, press return to switch\nYou can also use the arrow keys or Ctrl + vim keys to navigate the workspaces. Use Esc to close the overview.\nAfter opening the Overview the Launcher is available:\n\t- Start typing to search through applications (sorted by how often they were opened). Press return to launch the first app, use Ctrl + 1/2/3/... to open the second, third, etc.\n\t- Press Ctrl + t to run the typed command in a terminal.\n\t- Press Ctrl + <key> to search the typed text in any of the configured SearchEngines: Google, Wikipedia.\n\t- Typing a mathematical expression will calculate it and display the result in the launcher.\n\t- Paths (starting with ~ or /) can be open in default file-manager.\n\t- Type Reboot/Shutdown/etc. to run corresponding commands. Type `actions` to see all available ones.\n\n<Switch mode disabled>\n\";\n        let mut config = create_test_config();\n        config\n            .windows\n            .as_mut()\n            .expect(\"config option missing\")\n            .switch = None;\n        let path = PathBuf::from(\"/test/config.ron\");\n        let result = explain(&config, Some(&path), false);\n        assert_eq!(result, CONFIG);\n    }\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_explain_without_plugins() {\n        const CONFIG: &str = r\"Use Super + Super_L to open the Overview. Use tab and grave / shift + tab to select a different window, press return to switch\nYou can also use the arrow keys or Ctrl + vim keys to navigate the workspaces. Use Esc to close the overview.\nAfter opening the Overview the Launcher is available:\n\t<No plugins enabled in launcher>\n\nPress Alt + tab and hold Alt to view recently used applications. Press tab and grave / shift + tab to select a different window, release Alt to close the window.\n\";\n        let mut config = create_test_config();\n        config\n            .windows\n            .as_mut()\n            .expect(\"config option missing\")\n            .overview\n            .as_mut()\n            .expect(\"config option missing\")\n            .launcher\n            .plugins = Plugins {\n            applications: None,\n            terminal: None,\n            shell: None,\n            websearch: None,\n            calc: None,\n            path: None,\n            actions: None,\n        };\n        let result = explain(&config, None, false);\n        assert_eq!(result, CONFIG);\n    }\n}\n"
  },
  {
    "path": "crates/config-lib/src/lib.rs",
    "content": "pub mod actions;\nmod check;\nmod explain;\nmod load;\nmod migrate;\nmod modifier;\nmod save;\nmod structs;\npub mod style;\n\npub use check::check;\npub use explain::explain;\npub use load::load_and_migrate_config;\npub use modifier::*;\npub use save::write_config;\npub use structs::*;\n\npub const CURRENT_CONFIG_VERSION: u16 = 3;\n"
  },
  {
    "path": "crates/config-lib/src/load.rs",
    "content": "use crate::Config;\nuse crate::migrate::check_migration_needed;\nuse anyhow::{Context, bail};\nuse ron::Options;\nuse ron::extensions::Extensions;\nuse serde::de::DeserializeOwned;\nuse std::ffi::OsStr;\nuse std::path::Path;\nuse tracing::{debug, debug_span, info, trace, warn};\n\npub fn load_and_migrate_config(config_file: &Path, allow_migrate: bool) -> anyhow::Result<Config> {\n    let _span = debug_span!(\"load_config\", path =? config_file).entered();\n    if !config_file.exists() {\n        bail!(\"Config file does not exist, create it using `hyprshell config generate`\");\n    }\n\n    if check_migration_needed(config_file)\n        .inspect_err(|e| warn!(\"Failed to check if migration is needed: {e:?}\"))\n        .unwrap_or(false)\n    {\n        info!(\"Config needs migration\");\n        if !allow_migrate {\n            bail!(\"Config file needs migration, but migration is not allowed.\");\n        }\n        let migrated = crate::migrate::migrate(config_file);\n        match migrated {\n            Ok(config) => {\n                info!(\"Config migrated successfully\");\n                crate::check(&config)?;\n                return Ok(config);\n            }\n            Err(err) => {\n                bail!(\"Config migration failed: \\n{err:?}\");\n            }\n        }\n    }\n    trace!(\"No migration needed\");\n\n    let config: Config = load_config_file(config_file).with_context(|| {\n        format!(\n            \"Failed to load config from file ({})\",\n            config_file.display()\n        )\n    })?;\n    debug!(\"Loaded config\");\n\n    crate::check(&config)?;\n\n    Ok(config)\n}\n\npub fn load_config_file<T: DeserializeOwned>(config_file: &Path) -> anyhow::Result<T> {\n    let config_file_display = config_file.display();\n    match config_file.extension().and_then(OsStr::to_str) {\n        None | Some(\"ron\") => {\n            let options = Options::default()\n                .with_default_extension(Extensions::IMPLICIT_SOME)\n                .with_default_extension(Extensions::UNWRAP_NEWTYPES)\n                .with_default_extension(Extensions::UNWRAP_VARIANT_NEWTYPES);\n            let file = std::fs::File::open(config_file)\n                .with_context(|| format!(\"Failed to open RON config at ({config_file_display})\"))?;\n            options\n                .from_reader(file)\n                .with_context(|| format!(\"Failed to read RON config at ({config_file_display})\"))\n        }\n        #[cfg(not(feature = \"json5_config\"))]\n        Some(\"json\") => {\n            let file = std::fs::File::open(config_file).with_context(|| {\n                format!(\"Failed to open JSON5 config at ({config_file_display})\")\n            })?;\n            serde_json::from_reader(file)\n                .with_context(|| format!(\"Failed to read JSON5 config at ({config_file_display})\"))\n        }\n        #[cfg(feature = \"json5_config\")]\n        Some(\"json5\" | \"json\") => {\n            let file = std::fs::File::open(config_file).with_context(|| {\n                format!(\"Failed to open JSON5 config at ({config_file_display})\")\n            })?;\n            serde_json5::from_reader(file)\n                .with_context(|| format!(\"Failed to read JSON5 config at ({config_file_display})\"))\n        }\n        Some(\"toml\") => {\n            use std::io::Read;\n            let mut file = std::fs::File::open(config_file).with_context(|| {\n                format!(\"Failed to open TOML config at ({config_file_display})\")\n            })?;\n            let mut content = String::new();\n            file.read_to_string(&mut content).with_context(|| {\n                format!(\"Failed to read TOML config at ({config_file_display})\")\n            })?;\n            toml::from_str(&content).context(\"Failed to parse TOML config\")\n        }\n        Some(ext) => bail!(\n            \"Invalid config file extension: {ext} (run with -vv and check `FEATURES: ` debug log to see enabled extensions)\"\n        ),\n    }\n}\n"
  },
  {
    "path": "crates/config-lib/src/migrate/check.rs",
    "content": "use crate::CURRENT_CONFIG_VERSION;\nuse crate::load::load_config_file;\nuse anyhow::{Context, bail};\nuse serde::Deserialize;\nuse std::path::Path;\nuse tracing::debug_span;\n\n#[derive(Debug, Clone, Deserialize)]\npub(super) struct EmptyConfig {\n    pub(super) version: Option<u16>,\n}\n\npub fn check_migration_needed(config_file: &Path) -> anyhow::Result<bool> {\n    let _span = debug_span!(\"check_migration_needed\").entered();\n    let version = get_config_version(config_file).context(\"Failed to get config version\")?;\n    Ok(version != CURRENT_CONFIG_VERSION)\n}\n\npub fn get_config_version(config_file: &Path) -> anyhow::Result<u16> {\n    let _span = debug_span!(\"get_config_version\").entered();\n    if !config_file.exists() {\n        bail!(\"Config file does not exist no need to migrate\");\n    }\n\n    let config: EmptyConfig = load_config_file(config_file).with_context(|| {\n        format!(\n            \"Failed to load config from file ({})\",\n            config_file.display()\n        )\n    })?;\n    if let Some(version) = config.version {\n        Ok(version)\n    } else {\n        bail!(\"Config file does not have a version specified! please generate a new one\");\n    }\n}\n"
  },
  {
    "path": "crates/config-lib/src/migrate/m1t2/convert.rs",
    "content": "use crate::migrate::m1t2::{NEXT_CONFIG_VERSION, old_structs};\nuse crate::migrate::m2t3;\n\nimpl From<old_structs::Config> for m2t3::Config {\n    fn from(value: old_structs::Config) -> Self {\n        Self {\n            layerrules: value.layerrules,\n            kill_bind: value.kill_bind,\n            windows: value.windows.map(old_structs::Windows::into),\n            version: Some(NEXT_CONFIG_VERSION),\n        }\n    }\n}\n\nimpl From<old_structs::Windows> for m2t3::Windows {\n    fn from(value: old_structs::Windows) -> Self {\n        Self {\n            scale: value.scale,\n            items_per_row: value.items_per_row,\n            switch: value.switch.map(old_structs::Switch::into),\n            overview: value.overview.map(old_structs::Overview::into),\n        }\n    }\n}\n\nimpl From<old_structs::Overview> for m2t3::Overview {\n    fn from(value: old_structs::Overview) -> Self {\n        Self {\n            key: value.key,\n            modifier: value.modifier.into(),\n            filter_by: value.filter_by,\n            hide_filtered: value.hide_filtered,\n            launcher: value.launcher.into(),\n        }\n    }\n}\n\nimpl From<old_structs::Switch> for crate::Switch {\n    fn from(value: old_structs::Switch) -> Self {\n        Self {\n            filter_by: value.filter_by,\n            modifier: value.modifier.into(),\n            key: \"tab\".into(),\n            switch_workspaces: value.show_workspaces,\n            exclude_special_workspaces: \"\".into(),\n        }\n    }\n}\n\nimpl From<old_structs::Launcher> for m2t3::Launcher {\n    fn from(value: old_structs::Launcher) -> Self {\n        let mut plugins = value.plugins;\n        if let Some(a) = &mut plugins.applications {\n            a.show_actions_submenu = true;\n        }\n        plugins.path = Some(crate::EmptyConfig::default());\n        Self {\n            default_terminal: value.default_terminal,\n            launch_modifier: value.launch_modifier.into(),\n            width: value.width,\n            show_when_empty: value.show_when_empty,\n            max_items: value.max_items,\n            plugins,\n        }\n    }\n}\n\nimpl From<old_structs::Modifier> for crate::Modifier {\n    fn from(value: old_structs::Modifier) -> Self {\n        match value {\n            old_structs::Modifier::Alt => Self::Alt,\n            old_structs::Modifier::Ctrl => Self::Ctrl,\n            old_structs::Modifier::Shift | old_structs::Modifier::Super => Self::Super, // Shift removed\n        }\n    }\n}\n"
  },
  {
    "path": "crates/config-lib/src/migrate/m1t2/mod.rs",
    "content": "pub const PREV_CONFIG_VERSION: u16 = 1;\npub const NEXT_CONFIG_VERSION: u16 = 2;\nmod convert;\nmod old_structs;\n\npub use old_structs::*;\n"
  },
  {
    "path": "crates/config-lib/src/migrate/m1t2/old_structs.rs",
    "content": "use crate::migrate::m2t3;\nuse serde::Deserialize;\nuse smart_default::SmartDefault;\nuse std::fmt::Display;\n\n#[derive(SmartDefault, Debug, Clone, Deserialize)]\n#[serde(default, deny_unknown_fields)]\npub struct Config {\n    #[default = true]\n    pub(super) layerrules: bool,\n    #[default = \"ctrl+shift+alt, h\"]\n    pub(super) kill_bind: String,\n    #[default(None)]\n    pub(super) windows: Option<Windows>,\n    #[allow(dead_code)]\n    pub(super) version: u16,\n}\n\n#[derive(SmartDefault, Debug, Clone, Deserialize)]\n#[serde(default, deny_unknown_fields)]\npub(super) struct Windows {\n    #[default = 8.5]\n    pub(super) scale: f64,\n    #[default = 5]\n    pub(super) items_per_row: u8,\n    #[default(None)]\n    pub(super) overview: Option<Overview>,\n    #[default(None)]\n    pub(super) switch: Option<Switch>,\n}\n\n#[derive(SmartDefault, Debug, Clone, Deserialize)]\n#[serde(default, deny_unknown_fields)]\npub(super) struct Switch {\n    #[default(Modifier::Alt)]\n    pub modifier: Modifier,\n    #[default(Vec::new())]\n    pub filter_by: Vec<crate::FilterBy>,\n    #[default = false]\n    pub show_workspaces: bool,\n}\n\n#[derive(SmartDefault, Debug, Clone, Deserialize)]\n#[serde(default, deny_unknown_fields)]\npub(super) struct Overview {\n    pub(super) launcher: Launcher,\n    #[default = \"super_l\"]\n    pub(super) key: Box<str>,\n    #[default(Modifier::Super)]\n    pub(super) modifier: Modifier,\n    #[default(Vec::new())]\n    pub(super) filter_by: Vec<crate::FilterBy>,\n    #[default = false]\n    pub(super) hide_filtered: bool,\n    #[allow(dead_code)]\n    #[default = true]\n    pub(super) strip_html_from_workspace_title: bool,\n}\n\n#[derive(SmartDefault, Debug, Clone, Deserialize)]\n#[serde(default, deny_unknown_fields)]\npub(super) struct Launcher {\n    #[default(None)]\n    pub(super) default_terminal: Option<Box<str>>,\n    #[default(Modifier::Ctrl)]\n    pub(super) launch_modifier: Modifier,\n    #[default = 650]\n    pub(super) width: u32,\n    #[default = 5]\n    pub(super) max_items: u8,\n    #[default = true]\n    pub(super) show_when_empty: bool,\n    #[allow(dead_code)]\n    #[default = 400]\n    pub(super) animate_launch_ms: u64,\n    #[default(m2t3::Plugins{\n        applications: Some(crate::ApplicationsPluginConfig::default()),\n        terminal: Some(crate::EmptyConfig::default()),\n        shell: None,\n        websearch: Some(crate::WebSearchConfig::default()),\n        calc: Some(crate::EmptyConfig::default()),\n        path: Some(crate::EmptyConfig::default()),\n    })]\n    pub(super) plugins: m2t3::Plugins,\n}\n\n#[derive(Debug, Clone, Copy, Eq, PartialEq, Deserialize)]\n#[serde(rename_all = \"snake_case\")]\npub(super) enum Modifier {\n    Alt,\n    Ctrl,\n    Super,\n    Shift,\n}\n\nimpl Display for Modifier {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            Self::Alt => write!(f, \"alt\"),\n            Self::Ctrl => write!(f, \"ctrl\"),\n            Self::Super => write!(f, \"super\"),\n            Self::Shift => write!(f, \"shift\"),\n        }\n    }\n}\n"
  },
  {
    "path": "crates/config-lib/src/migrate/m2t3/convert.rs",
    "content": "use crate::migrate::m2t3::{NEXT_CONFIG_VERSION, old_structs};\n\nimpl From<old_structs::Config> for crate::Config {\n    fn from(value: old_structs::Config) -> Self {\n        Self {\n            windows: value.windows.map(old_structs::Windows::into),\n            version: NEXT_CONFIG_VERSION,\n        }\n    }\n}\n\nimpl From<old_structs::Windows> for crate::Windows {\n    fn from(value: old_structs::Windows) -> Self {\n        Self {\n            scale: value.scale,\n            items_per_row: value.items_per_row,\n            switch: value.switch,\n            switch_2: None,\n            overview: value.overview.map(old_structs::Overview::into),\n        }\n    }\n}\n\nimpl From<old_structs::Overview> for crate::Overview {\n    fn from(value: old_structs::Overview) -> Self {\n        Self {\n            key: value.key,\n            modifier: value.modifier,\n            filter_by: value.filter_by,\n            hide_filtered: value.hide_filtered,\n            launcher: value.launcher.into(),\n            exclude_special_workspaces: \"\".into(),\n        }\n    }\n}\n\nimpl From<old_structs::Launcher> for crate::Launcher {\n    fn from(value: old_structs::Launcher) -> Self {\n        Self {\n            default_terminal: value.default_terminal,\n            launch_modifier: value.launch_modifier,\n            width: value.width,\n            show_when_empty: value.show_when_empty,\n            max_items: value.max_items,\n            plugins: value.plugins.into(),\n        }\n    }\n}\n\nimpl From<old_structs::Plugins> for crate::Plugins {\n    fn from(value: old_structs::Plugins) -> Self {\n        Self {\n            applications: value.applications,\n            terminal: value.terminal,\n            shell: value.shell,\n            websearch: value.websearch,\n            calc: value.calc,\n            path: value.path,\n            actions: Some(crate::ActionsPluginConfig::default()),\n        }\n    }\n}\n"
  },
  {
    "path": "crates/config-lib/src/migrate/m2t3/mod.rs",
    "content": "pub const PREV_CONFIG_VERSION: u16 = 2;\npub const NEXT_CONFIG_VERSION: u16 = 3;\nmod convert;\nmod old_structs;\n\npub use old_structs::*;\n"
  },
  {
    "path": "crates/config-lib/src/migrate/m2t3/old_structs.rs",
    "content": "use serde::Deserialize;\nuse smart_default::SmartDefault;\n\n#[derive(SmartDefault, Debug, Clone, Deserialize)]\n#[serde(default, deny_unknown_fields)]\npub struct Config {\n    #[default = true]\n    pub layerrules: bool,\n    #[default = \"ctrl+shift+alt, h\"]\n    pub kill_bind: String,\n    #[default(None)]\n    pub windows: Option<Windows>,\n    #[allow(dead_code)]\n    pub version: Option<u16>,\n}\n\n#[derive(SmartDefault, Debug, Clone, PartialEq, Deserialize)]\n#[serde(default, deny_unknown_fields)]\npub struct Windows {\n    #[default = 8.5]\n    pub scale: f64,\n    #[default = 5]\n    pub items_per_row: u8,\n    #[default(None)]\n    pub overview: Option<Overview>,\n    #[default(None)]\n    pub switch: Option<crate::Switch>,\n}\n\n#[derive(SmartDefault, Debug, Clone, PartialEq, Eq, Deserialize)]\n#[serde(default, deny_unknown_fields)]\npub struct Overview {\n    pub launcher: Launcher,\n    #[default = \"super_l\"]\n    pub key: Box<str>,\n    #[default(crate::Modifier::Super)]\n    pub modifier: crate::Modifier,\n    #[default(Vec::new())]\n    pub filter_by: Vec<crate::FilterBy>,\n    #[default = false]\n    pub hide_filtered: bool,\n}\n\n#[derive(SmartDefault, Debug, Clone, PartialEq, Eq, Deserialize)]\n#[serde(default, deny_unknown_fields)]\npub struct Launcher {\n    #[default(None)]\n    pub default_terminal: Option<Box<str>>,\n    #[default(crate::Modifier::Ctrl)]\n    pub launch_modifier: crate::Modifier,\n    #[default = 650]\n    pub width: u32,\n    #[default = 5]\n    pub max_items: u8,\n    #[default = true]\n    pub show_when_empty: bool,\n    #[default(Plugins{\n        applications: Some(crate::ApplicationsPluginConfig::default()),\n        terminal: Some(crate::EmptyConfig::default()),\n        shell: None,\n        websearch: Some(crate::WebSearchConfig::default()),\n        calc: Some(crate::EmptyConfig::default()),\n        path: Some(crate::EmptyConfig::default()),\n    })]\n    pub plugins: Plugins,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]\n#[serde(deny_unknown_fields)]\npub struct Plugins {\n    pub applications: Option<crate::ApplicationsPluginConfig>,\n    pub terminal: Option<crate::EmptyConfig>,\n    pub shell: Option<crate::EmptyConfig>,\n    pub websearch: Option<crate::WebSearchConfig>,\n    pub calc: Option<crate::EmptyConfig>,\n    pub path: Option<crate::EmptyConfig>,\n}\n"
  },
  {
    "path": "crates/config-lib/src/migrate/migrate_config.rs",
    "content": "use crate::load::load_config_file;\nuse crate::migrate::check::get_config_version;\nuse crate::{CURRENT_CONFIG_VERSION, migrate, write_config};\nuse anyhow::{Context, bail};\nuse std::path::Path;\nuse tracing::{debug_span, info, warn};\n\npub fn migrate(config_file: &Path) -> anyhow::Result<crate::Config> {\n    let _span = debug_span!(\"migrate\").entered();\n    let old_version = get_config_version(config_file)?;\n\n    let new_config = match old_version {\n        migrate::m1t2::PREV_CONFIG_VERSION => {\n            info!(\"Migrating from version {old_version} to new version {CURRENT_CONFIG_VERSION}\");\n            let old_config: migrate::m1t2::Config =\n                load_config_file(config_file).context(\"Failed to load old config\")?;\n            let i1 = migrate::m2t3::Config::from(old_config);\n            crate::Config::from(i1)\n        }\n        migrate::m2t3::PREV_CONFIG_VERSION => {\n            info!(\"Migrating from version {old_version} to new version {CURRENT_CONFIG_VERSION}\");\n            let old_config: migrate::m2t3::Config =\n                load_config_file(config_file).context(\"Failed to load old config\")?;\n            crate::Config::from(old_config)\n        }\n        _ => bail!(\"Unsupported old config version {old_version}, cannot migrate\"),\n    };\n    match write_config(config_file, &new_config, true) {\n        Ok(()) => {\n            info!(\"New config written successfully\");\n        }\n        Err(err) => {\n            warn!(\"Failed to write new config!, please update it manually. \\n{err:?}\");\n        }\n    }\n    Ok(new_config)\n}\n"
  },
  {
    "path": "crates/config-lib/src/migrate/mod.rs",
    "content": "mod check;\nmod m1t2;\nmod m2t3;\nmod migrate_config;\n\npub use check::check_migration_needed;\npub use migrate_config::migrate;\n"
  },
  {
    "path": "crates/config-lib/src/modifier.rs",
    "content": "use anyhow::bail;\nuse serde::de::Visitor;\nuse serde::{Deserialize, Deserializer, Serialize, Serializer};\nuse std::fmt;\n\n#[derive(Debug, Clone, Copy, Eq, PartialEq)]\npub enum Modifier {\n    Alt,\n    Ctrl,\n    Super,\n    None,\n}\n\n#[allow(clippy::must_use_candidate)]\nimpl Modifier {\n    pub fn to_l_key(&self) -> String {\n        match self {\n            Self::Alt => \"alt_l\".to_string(),\n            Self::Ctrl => \"ctrl_l\".to_string(),\n            Self::Super => \"super_l\".to_string(),\n            Self::None => String::new(),\n        }\n    }\n    pub const fn to_str(&self) -> &'static str {\n        match self {\n            Self::Alt => \"alt\",\n            Self::Ctrl => \"ctrl\",\n            Self::Super => \"super\",\n            Self::None => \"none\",\n        }\n    }\n}\n\nimpl<'de> Deserialize<'de> for Modifier {\n    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n    where\n        D: Deserializer<'de>,\n    {\n        struct ModVisitor;\n        impl Visitor<'_> for ModVisitor {\n            type Value = Modifier;\n            fn expecting(&self, fmt: &mut fmt::Formatter) -> fmt::Result {\n                fmt.write_str(\"one of: alt, ctrl, super, none\")\n            }\n            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>\n            where\n                E: serde::de::Error,\n            {\n                value\n                    .try_into()\n                    .map_err(|_e| E::unknown_variant(value, &[\"alt\", \"ctrl\", \"super\", \"none\"]))\n            }\n        }\n        deserializer.deserialize_str(ModVisitor)\n    }\n}\n\nimpl Serialize for Modifier {\n    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>\n    where\n        S: Serializer,\n    {\n        let s = self.to_str();\n        serializer.serialize_str(s)\n    }\n}\n\nimpl TryFrom<&str> for Modifier {\n    type Error = anyhow::Error;\n\n    fn try_from(value: &str) -> Result<Self, Self::Error> {\n        match value.to_ascii_lowercase().as_str() {\n            \"alt\" => Ok(Self::Alt),\n            \"ctrl\" | \"control\" => Ok(Self::Ctrl),\n            \"super\" | \"win\" | \"windows\" | \"meta\" => Ok(Self::Super),\n            \"none\" | \"\" => Ok(Self::None),\n            other => bail!(\"Invalid modifier: {other}\"),\n        }\n    }\n}\n\nimpl fmt::Display for Modifier {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        match self {\n            Self::Alt => write!(f, \"Alt\"),\n            Self::Ctrl => write!(f, \"Ctrl\"),\n            Self::Super => write!(f, \"Super\"),\n            Self::None => write!(f, \"None\"),\n        }\n    }\n}\n"
  },
  {
    "path": "crates/config-lib/src/save.rs",
    "content": "use crate::Config;\nuse anyhow::{Context, bail};\nuse ron::Options;\nuse ron::extensions::Extensions;\nuse ron::ser::PrettyConfig;\nuse std::ffi::OsStr;\nuse std::fs::{File, create_dir_all};\nuse std::io::Write;\nuse std::path::Path;\nuse tracing::{debug, debug_span, info};\n\nconst CONFIG_EXPLANATION: &str = \"Edit with `hyprshell config edit`\";\n\npub fn write_config(\n    config_file: &Path,\n    config: &Config,\n    override_file: bool,\n) -> anyhow::Result<()> {\n    let _span = debug_span!(\"write_config\").entered();\n    let config_file_display = config_file.display();\n    if config_file.exists() && !override_file {\n        bail!(\n            \"Config file at {config_file_display} already exists, delete it before generating a new one or use -f to override\"\n        );\n    }\n    if let Some(parent) = config_file.parent() {\n        create_dir_all(parent)\n            .with_context(|| format!(\"Failed to create config dir at ({})\", parent.display()))?;\n    }\n    let str = match config_file.extension().and_then(OsStr::to_str) {\n        None | Some(\"ron\") => Options::default()\n            .with_default_extension(Extensions::IMPLICIT_SOME)\n            .with_default_extension(Extensions::UNWRAP_NEWTYPES)\n            .with_default_extension(Extensions::UNWRAP_VARIANT_NEWTYPES)\n            .to_string_pretty(config, PrettyConfig::default())\n            .with_context(|| format!(\"Failed to write RON config to ({config_file_display})\")),\n        Some(\"json5\" | \"json\") => {\n            serde_json::to_string_pretty(config).context(\"Failed to generate JSON config\")\n        }\n        Some(\"toml\") => toml::to_string_pretty(config).context(\"Failed to generate TOML config\"),\n        Some(ext) => bail!(\n            \"Invalid config file extension: {ext} (run with -vv and check `FEATURES: ` debug log to see enabled extensions)\"\n        ),\n    }?;\n    #[allow(clippy::match_same_arms)]\n    let header = match config_file.extension().and_then(OsStr::to_str) {\n        None | Some(\"ron\") => format!(\"// {CONFIG_EXPLANATION}\"),\n        Some(\"json5\") => format!(\"// {CONFIG_EXPLANATION}\"),\n        Some(\"toml\") => format!(\"# {CONFIG_EXPLANATION}\"),\n        _ => String::new(),\n    };\n    let content = format!(\"{header}\\n{str}\");\n    let mut file = File::create(config_file)\n        .with_context(|| format!(\"Failed to create config file at ({config_file_display})\"))?;\n    file.write_all(content.as_bytes())\n        .with_context(|| format!(\"Failed to write to config file at ({config_file_display})\"))\n        .inspect_err(|_| {\n            info!(\"New config contents: {config:?}\");\n        })?;\n\n    debug!(\"Config file written successfully at {config_file_display}\");\n    Ok(())\n}\n"
  },
  {
    "path": "crates/config-lib/src/structs.rs",
    "content": "use crate::Modifier;\nuse serde::{Deserialize, Serialize};\nuse smart_default::SmartDefault;\nuse std::path::Path;\n\n#[derive(SmartDefault, Debug, Clone, PartialEq, Deserialize, Serialize)]\n#[cfg_attr(not(feature = \"ci_no_default_config_values\"), serde(default))]\n#[serde(deny_unknown_fields)]\npub struct Config {\n    #[default(crate::CURRENT_CONFIG_VERSION)]\n    pub version: u16,\n    #[default(None)]\n    pub windows: Option<Windows>,\n}\n\n#[derive(SmartDefault, Debug, Clone, PartialEq, Deserialize, Serialize)]\n#[cfg_attr(not(feature = \"ci_no_default_config_values\"), serde(default))]\n#[serde(deny_unknown_fields)]\npub struct Windows {\n    #[default = 8.5]\n    pub scale: f64,\n    #[default = 5]\n    pub items_per_row: u8,\n    #[default(None)]\n    pub overview: Option<Overview>,\n    #[default(None)]\n    pub switch: Option<Switch>,\n    #[default(None)]\n    pub switch_2: Option<Switch>,\n}\n\n#[derive(SmartDefault, Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]\n#[cfg_attr(not(feature = \"ci_no_default_config_values\"), serde(default))]\n#[serde(deny_unknown_fields)]\npub struct Overview {\n    pub launcher: Launcher,\n    #[default = \"Super_L\"]\n    pub key: Box<str>,\n    #[default(Modifier::Super)]\n    pub modifier: Modifier,\n    #[default(Vec::new())]\n    pub filter_by: Vec<FilterBy>,\n    #[default = false]\n    pub hide_filtered: bool,\n    #[default = \"\"]\n    pub exclude_special_workspaces: Box<str>,\n}\n\n#[derive(SmartDefault, Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]\n#[cfg_attr(not(feature = \"ci_no_default_config_values\"), serde(default))]\n#[serde(deny_unknown_fields)]\npub struct Launcher {\n    #[default(None)]\n    pub default_terminal: Option<Box<str>>,\n    #[default(Modifier::Ctrl)]\n    pub launch_modifier: Modifier,\n    #[default = 650]\n    pub width: u32,\n    #[default = 5]\n    pub max_items: u8,\n    #[default = true]\n    pub show_when_empty: bool,\n    #[default(Plugins{\n        applications: Some(ApplicationsPluginConfig::default()),\n        terminal: Some(EmptyConfig::default()),\n        shell: None,\n        websearch: Some(WebSearchConfig::default()),\n        calc: Some(EmptyConfig::default()),\n        path: Some(EmptyConfig::default()),\n        actions: Some(ActionsPluginConfig::default()),\n    })]\n    pub plugins: Plugins,\n}\n\n// no default for this, if some elements are missing, they should be None.\n// if no config for plugins is provided, use the default value from the launcher.\n#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]\n#[serde(deny_unknown_fields)]\npub struct Plugins {\n    pub applications: Option<ApplicationsPluginConfig>,\n    pub terminal: Option<EmptyConfig>,\n    pub shell: Option<EmptyConfig>,\n    pub websearch: Option<WebSearchConfig>,\n    pub calc: Option<EmptyConfig>,\n    pub path: Option<EmptyConfig>,\n    pub actions: Option<ActionsPluginConfig>,\n}\n\n#[derive(SmartDefault, Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]\n#[cfg_attr(not(feature = \"ci_no_default_config_values\"), serde(default))]\n#[serde(deny_unknown_fields)]\npub struct EmptyConfig {}\n\n#[derive(SmartDefault, Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]\n#[cfg_attr(not(feature = \"ci_no_default_config_values\"), serde(default))]\n#[serde(deny_unknown_fields)]\npub struct ActionsPluginConfig {\n    #[default(vec![\n        ActionsPluginAction::LockScreen,\n        ActionsPluginAction::Hibernate,\n        ActionsPluginAction::Logout,\n        ActionsPluginAction::Reboot,\n        ActionsPluginAction::Shutdown,\n        ActionsPluginAction::Suspend,\n        ActionsPluginAction::Custom(ActionsPluginActionCustom {\n            names: vec![\"Kill\".into(), \"Stop\".into()],\n            details: \"Kill or stop a process by name\".into(),\n            command: \"pkill \\\"{}\\\" && notify-send hyprshell \\\"stopped {}\\\"\".into(),\n            icon: Box::from(Path::new(\"remove\")),\n        }),\n        ActionsPluginAction::Custom(ActionsPluginActionCustom {\n            names: vec![\"Reload Hyprshell\".into()],\n            details: \"Reload Hyprshell\".into(),\n            command: \"sleep 1; hyprshell socat '\\\"Restart\\\"'\".into(),\n            icon: Box::from(Path::new(\"system-restart\")),\n        }),\n    ])]\n    pub actions: Vec<ActionsPluginAction>,\n}\n\n#[derive(SmartDefault, Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]\n#[cfg_attr(not(feature = \"ci_no_default_config_values\"), serde(default))]\n#[serde(deny_unknown_fields)]\npub struct ApplicationsPluginConfig {\n    #[default = 8]\n    pub run_cache_weeks: u8,\n    #[default = true]\n    pub show_execs: bool,\n    #[default = true]\n    pub show_actions_submenu: bool,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]\n#[serde(rename_all = \"snake_case\")]\npub enum ActionsPluginAction {\n    LockScreen,\n    Hibernate,\n    Logout,\n    Reboot,\n    Shutdown,\n    Suspend,\n    Custom(ActionsPluginActionCustom),\n}\n\n#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]\n#[serde(deny_unknown_fields)]\npub struct ActionsPluginActionCustom {\n    pub names: Vec<Box<str>>,\n    pub details: Box<str>,\n    pub command: Box<str>,\n    pub icon: Box<Path>,\n}\n\n#[derive(SmartDefault, Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]\n#[cfg_attr(not(feature = \"ci_no_default_config_values\"), serde(default))]\n#[serde(deny_unknown_fields)]\npub struct WebSearchConfig {\n    #[default(vec![SearchEngine {\n        url: \"https://www.google.com/search?q={}\".into(),\n        name: \"Google\".into(),\n        key: 'g',\n    }, SearchEngine {\n        url: \"https://en.wikipedia.org/wiki/Special:Search?search={}\".into(),\n        name: \"Wikipedia\".into(),\n        key: 'w',\n    }])]\n    pub engines: Vec<SearchEngine>,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]\n#[serde(deny_unknown_fields)]\npub struct SearchEngine {\n    pub url: Box<str>,\n    pub name: Box<str>,\n    pub key: char,\n}\n\n#[derive(SmartDefault, Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]\n#[cfg_attr(not(feature = \"ci_no_default_config_values\"), serde(default))]\n#[serde(deny_unknown_fields)]\npub struct Switch {\n    #[default(Modifier::Alt)]\n    pub modifier: Modifier,\n    #[default = \"Tab\"]\n    pub key: Box<str>,\n    #[default(vec![FilterBy::CurrentMonitor])]\n    pub filter_by: Vec<FilterBy>,\n    #[default = false]\n    pub switch_workspaces: bool,\n    #[default = \"\"]\n    pub exclude_special_workspaces: Box<str>,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]\n#[serde(rename_all = \"snake_case\")]\npub enum FilterBy {\n    SameClass,\n    CurrentWorkspace,\n    CurrentMonitor,\n}\n"
  },
  {
    "path": "crates/config-lib/src/style/load.rs",
    "content": "use crate::style::ThemeData;\nuse crate::style::structs::Theme;\nuse anyhow::{Context, bail};\nuse core_lib::ini::IniFile;\nuse std::fs;\nuse std::path::Path;\nuse tracing::{debug, instrument, warn};\n\n#[instrument(level = \"debug\")]\npub fn load_themes(\n    path: &Path,\n    current_css: &str,\n) -> anyhow::Result<(Vec<Theme>, Vec<anyhow::Error>)> {\n    let mut themes = Vec::new();\n    if !path.exists() {\n        bail!(\"Themes directory does not exist: {}\", path.display());\n    }\n\n    let mut errors = Vec::new();\n    for entry in fs::read_dir(path)\n        .with_context(|| format!(\"Failed to read themes directory ({})\", path.display()))?\n    {\n        let entry = match entry {\n            Ok(entry) => entry,\n            Err(err) => {\n                warn!(\"Failed to read theme directory in: {}\", path.display());\n                errors.push(err.into());\n                continue;\n            }\n        };\n\n        let file_type = match entry.file_type() {\n            Ok(ft) => ft,\n            Err(err) => {\n                warn!(\n                    \"Failed to get file type for theme directory: {}\",\n                    entry.path().display()\n                );\n                errors.push(err.into());\n                continue;\n            }\n        };\n        if !file_type.is_dir() {\n            warn!(\"Invalid theme directory: {}\", entry.path().display());\n            errors.push(anyhow::anyhow!(\n                \"Invalid theme directory: {}\",\n                entry.path().display()\n            ));\n            continue;\n        }\n        let dir_path = entry.path();\n        let style_path = dir_path.join(\"style.css\");\n        if style_path.is_file() {\n            if let Some(name) = dir_path.file_name().and_then(|n| n.to_str()) {\n                let Ok(theme_content) = fs::read_to_string(&style_path) else {\n                    warn!(\"Failed to read theme file: {}\", style_path.display());\n                    errors.push(anyhow::anyhow!(\n                        \"Failed to read theme file: {}\",\n                        style_path.display()\n                    ));\n                    continue;\n                };\n                let data_path = dir_path.join(\"data.ini\");\n                let Ok(data) = fs::read_to_string(&data_path) else {\n                    warn!(\"Failed to read theme data file: {}\", data_path.display());\n                    errors.push(anyhow::anyhow!(\n                        \"Failed to read theme data file: {}\",\n                        data_path.display()\n                    ));\n                    continue;\n                };\n\n                let data = parse_data(&data, name);\n                let image_path = dir_path.join(\"image.png\");\n                themes.push(Theme {\n                    is_current: theme_content == current_css,\n                    name: name.to_string(),\n                    path: dir_path.clone(),\n                    style: theme_content,\n                    data,\n                    image_path: if image_path.exists() {\n                        Some(image_path)\n                    } else {\n                        debug!(\"Image file not found: {}\", image_path.display());\n                        None\n                    },\n                });\n            }\n        } else {\n            warn!(\n                \"Invalid theme directory: {}, style file missing\",\n                dir_path.display()\n            );\n            errors.push(anyhow::anyhow!(\n                \"Invalid theme directory: {}, style file missing\",\n                dir_path.display()\n            ));\n        }\n    }\n\n    themes.sort_by(|a, b| a.name.cmp(&b.name));\n    Ok((themes, errors))\n}\n\nfn parse_data(data: &str, name: &str) -> ThemeData {\n    let data = IniFile::from_str(data);\n    ThemeData {\n        name: data\n            .get_section(\"\")\n            .and_then(|s| s.get_first(\"name\"))\n            .unwrap_or(name)\n            .to_string(),\n        description: data\n            .get_section(\"\")\n            .and_then(|s| s.get_first(\"description\"))\n            .unwrap_or(\"\")\n            .replace(\"\\\\n\", \"\\n\"),\n        experimental: data\n            .get_section(\"\")\n            .and_then(|s| s.get_first(\"experimental\"))\n            .unwrap_or(\"false\")\n            .parse::<bool>()\n            .unwrap_or(false),\n    }\n}\n"
  },
  {
    "path": "crates/config-lib/src/style/mod.rs",
    "content": "mod load;\nmod structs;\n\npub use load::load_themes;\npub use structs::*;\n"
  },
  {
    "path": "crates/config-lib/src/style/structs.rs",
    "content": "use std::path::PathBuf;\n\n#[derive(Debug)]\npub struct Theme {\n    pub name: String,\n    pub path: PathBuf,\n    pub style: String,\n    pub image_path: Option<PathBuf>,\n    pub data: ThemeData,\n    pub is_current: bool,\n}\n\n#[derive(Debug)]\npub struct ThemeData {\n    pub name: String,\n    pub description: String,\n    pub experimental: bool,\n}\n"
  },
  {
    "path": "crates/core-lib/Cargo.toml",
    "content": "[package]\nname = \"hyprshell-core-lib\"\ndocumentation = \"https://docs.rs/hyprshell-core-lib\"\nversion = \"4.9.5\"\nedition.workspace = true\ndescription.workspace = true\nlicense.workspace = true\nauthors.workspace = true\nkeywords.workspace = true\nrepository.workspace = true\nrust-version.workspace = true\n\n[dependencies]\nanyhow.workspace = true\ntracing.workspace = true\n#config-lib.workspace = true\nserde_json.workspace = true\nserde.workspace = true\nnotify = { version = \"8.2.0\", features = [], default-features = false }\nnotify-rust = { version = \"4.11.7\", features = [\"zbus\"] }\n\n[features]\njson5_config = []\n\n[lints]\nworkspace = true\n\n[dev-dependencies]\ntest-log.workspace = true\n"
  },
  {
    "path": "crates/core-lib/src/binds/mod.rs",
    "content": "mod structs;\nmod transfer;\n\npub use structs::*;\npub use transfer::{generate_transfer, generate_transfer_socat, get_hyprshell_path};\n"
  },
  {
    "path": "crates/core-lib/src/binds/structs.rs",
    "content": "#[derive(Debug)]\npub struct ExecBind {\n    pub mods: Vec<&'static str>,\n    pub key: Box<str>,\n    pub exec: Box<str>,\n}\n"
  },
  {
    "path": "crates/core-lib/src/binds/transfer.rs",
    "content": "use crate::transfer::TransferType;\nuse std::env;\n\n/// # Panics\n/// if the current executable couldn't be found\n#[must_use]\npub fn get_hyprshell_path() -> String {\n    env::current_exe()\n        .expect(\"Current executable not found\")\n        .display()\n        .to_string()\n        .replace(\"(deleted)\", \"\")\n}\n\n/// # Panics\n/// if the transfer could not be serialized into a string\n#[must_use]\npub fn generate_transfer_socat(transfer: &TransferType) -> String {\n    format!(\n        r\"{} socat '{}'\",\n        get_hyprshell_path(),\n        generate_transfer(transfer)\n    )\n}\n\n/// # Panics\n/// if the transfer could not be serialized into a string\n#[must_use]\npub fn generate_transfer(transfer: &TransferType) -> String {\n    serde_json::to_string(transfer).expect(\"serialize transfer\")\n}\n"
  },
  {
    "path": "crates/core-lib/src/const.rs",
    "content": "pub const APPLICATION_ID: &str = \"com.github.h3rmt.hyprshell\";\npub const OVERVIEW_NAMESPACE: &str = \"hyprshell_overview\";\npub const SWITCH_NAMESPACE: &str = \"hyprshell_switch\";\npub const LAUNCHER_NAMESPACE: &str = \"hyprshell_launcher\";\n\n// from https://github.com/i3/i3/blob/next/i3-sensible-terminal\n// shorted to only the most common ones that I know support -e option\npub const TERMINALS: [&str; 9] = [\n    \"alacritty\",\n    \"kitty\",\n    \"wezterm\",\n    \"foot\",\n    \"qterminal\",\n    \"lilyterm\",\n    \"tilix\",\n    \"terminix\",\n    \"konsole\",\n];\n"
  },
  {
    "path": "crates/core-lib/src/data.rs",
    "content": "pub type WorkspaceId = i32;\npub type MonitorId = i128;\npub type ClientId = u64;\n\n#[derive(Debug, Clone, Copy, Eq, PartialEq)]\npub struct Active {\n    pub client: Option<ClientId>,\n    pub workspace: WorkspaceId,\n    pub monitor: MonitorId,\n}\n\n#[derive(Debug, Clone)]\npub struct MonitorData {\n    pub x: i32,\n    pub y: i32,\n    pub width: u16,\n    pub height: u16,\n    pub connector: String,\n}\n\n#[derive(Debug, Clone)]\npub struct WorkspaceData {\n    pub name: String,\n    pub width: u16,\n    pub height: u16,\n    pub monitor: MonitorId,\n    pub any_client_enabled: bool,\n}\n\n#[derive(Debug, Clone)]\npub struct ClientData {\n    pub x: i16,\n    pub y: i16,\n    pub width: i16,\n    pub height: i16,\n    pub class: String,\n    pub title: String,\n    pub workspace: WorkspaceId,\n    pub monitor: MonitorId,\n    pub focus_history_id: i8,\n    pub floating: bool,\n    pub enabled: bool,\n    pub pid: i32,\n}\n\n#[derive(Debug, Default)]\npub struct HyprlandData {\n    pub clients: Vec<(ClientId, ClientData)>,\n    pub workspaces: Vec<(WorkspaceId, WorkspaceData)>,\n    pub monitors: Vec<(MonitorId, MonitorData)>,\n}\n\npub trait FindByFirst<ID, Data> {\n    fn find_by_first(&self, id: &ID) -> Option<&Data>;\n}\n\nimpl FindByFirst<ClientId, ClientData> for [(ClientId, ClientData)] {\n    fn find_by_first(&self, id: &ClientId) -> Option<&ClientData> {\n        self.iter().find(|(addr, _)| *addr == *id).map(|(_, cd)| cd)\n    }\n}\n\nimpl FindByFirst<WorkspaceId, WorkspaceData> for [(WorkspaceId, WorkspaceData)] {\n    fn find_by_first(&self, id: &WorkspaceId) -> Option<&WorkspaceData> {\n        self.iter().find(|(wid, _)| *wid == *id).map(|(_, wd)| wd)\n    }\n}\n\nimpl FindByFirst<MonitorId, MonitorData> for [(MonitorId, MonitorData)] {\n    fn find_by_first(&self, id: &MonitorId) -> Option<&MonitorData> {\n        self.iter().find(|(mid, _)| *mid == *id).map(|(_, md)| md)\n    }\n}\n"
  },
  {
    "path": "crates/core-lib/src/default/mod.rs",
    "content": "use crate::ini_owned::IniFileOwned;\nuse crate::util::{collect_desktop_files, collect_mime_files};\nuse anyhow::bail;\nuse std::collections::BTreeSet;\nuse std::fs::{DirEntry, read_dir, read_to_string};\nuse std::path::{Path, PathBuf};\nuse std::sync::{OnceLock, RwLock, RwLockReadGuard};\nuse std::{env, thread};\nuse tracing::{debug, debug_span, trace, warn};\n\nfn get_desktop_files_from_cache() -> &'static RwLock<Vec<(DirEntry, IniFileOwned)>> {\n    static MAP_LOCK: OnceLock<RwLock<Vec<(DirEntry, IniFileOwned)>>> = OnceLock::new();\n    MAP_LOCK.get_or_init(|| RwLock::new(Vec::default()))\n}\nfn get_mime_files_from_cache() -> &'static RwLock<Vec<(DirEntry, IniFileOwned)>> {\n    static MAP_LOCK: OnceLock<RwLock<Vec<(DirEntry, IniFileOwned)>>> = OnceLock::new();\n    MAP_LOCK.get_or_init(|| RwLock::new(Vec::default()))\n}\nfn get_icons_from_cache() -> &'static RwLock<BTreeSet<Box<str>>> {\n    static MAP_LOCK: OnceLock<RwLock<BTreeSet<Box<str>>>> = OnceLock::new();\n    MAP_LOCK.get_or_init(|| RwLock::new(BTreeSet::default()))\n}\n\npub fn get_all_desktop_files<'a>()\n-> anyhow::Result<RwLockReadGuard<'a, Vec<(DirEntry, IniFileOwned)>>> {\n    get_desktop_files_from_cache()\n        .read()\n        .map_err(|_| anyhow::anyhow!(\"Failed to lock desktop files mutex\"))\n}\n\npub fn get_all_mime_files<'a>() -> anyhow::Result<RwLockReadGuard<'a, Vec<(DirEntry, IniFileOwned)>>>\n{\n    get_mime_files_from_cache()\n        .read()\n        .map_err(|_| anyhow::anyhow!(\"Failed to lock desktop files mutex\"))\n}\n\npub fn get_all_icons<'a>() -> anyhow::Result<RwLockReadGuard<'a, BTreeSet<Box<str>>>> {\n    get_icons_from_cache()\n        .read()\n        .map_err(|_| anyhow::anyhow!(\"Failed to lock icon map\"))\n}\n\n#[must_use]\npub fn theme_has_icon_name(name: &str) -> bool {\n    get_icons_from_cache()\n        .read()\n        .map(|map| map.contains(&Box::from(name)))\n        .unwrap_or(false)\n}\n\npub fn get_default_desktop_file<F, R>(mime: &str, r#fn: F) -> Option<R>\nwhere\n    F: FnOnce(&(DirEntry, IniFileOwned)) -> Option<R>,\n{\n    let mime_apps = get_mime_files_from_cache().read().ok()?;\n    let desktop_files = get_desktop_files_from_cache().read().ok()?;\n\n    for (_, ini) in mime_apps.iter() {\n        if let Some(ini) = ini\n            .get_section(\"Default Applications\")\n            .and_then(|section| section.get_first(mime))\n            .or_else(|| {\n                ini.get_section(\"Added Associations\")\n                    .and_then(|section| section.get_first(mime))\n            })\n            .and_then(|default| {\n                desktop_files\n                    .iter()\n                    .find(|(entry, _)| entry.file_name() == *default)\n            })\n        {\n            return r#fn(ini);\n        }\n    }\n    drop((mime_apps, desktop_files));\n    None\n}\n\n/// Reloads desktop files and mime files from the system.\n///\n/// Stores them in global data mutexes.\npub fn reload_default_files() -> anyhow::Result<()> {\n    let _span = tracing::span!(tracing::Level::TRACE, \"reload_files\").entered();\n    let mut desktop_files_data = vec![];\n    let mut mime_files_data = vec![];\n    for file in collect_desktop_files() {\n        let Ok(content) = read_to_string(file.path()) else {\n            warn!(\"Failed to read desktop file: {}\", file.path().display());\n            continue;\n        };\n        let ini = IniFileOwned::from_str(&content);\n        desktop_files_data.push((file, ini));\n    }\n    trace!(\"Collected all desktop files\");\n\n    for file in collect_mime_files() {\n        let Ok(content) = read_to_string(file.path()) else {\n            warn!(\"Failed to read desktop file: {}\", file.path().display());\n            continue;\n        };\n        let ini = IniFileOwned::from_str(&content);\n        mime_files_data.push((file, ini));\n    }\n    trace!(\"Collected all mime files\");\n\n    let mut desktop_files = get_desktop_files_from_cache()\n        .write()\n        .map_err(|_| anyhow::anyhow!(\"Failed to lock desktop files global data mutex\"))?;\n    *desktop_files = desktop_files_data;\n    drop(desktop_files);\n    let mut mime_files = get_mime_files_from_cache()\n        .write()\n        .map_err(|_| anyhow::anyhow!(\"Failed to lock mime files global data mutex\"))?;\n    *mime_files = mime_files_data;\n    drop(mime_files);\n    Ok(())\n}\n\npub fn reload_available_icons(\n    icon_names: Vec<String>,\n    search_path: Vec<PathBuf>,\n    in_background: bool,\n) -> anyhow::Result<()> {\n    let span = debug_span!(\"reload_icons\");\n    let _span = span.enter();\n\n    let Ok(mut map) = get_icons_from_cache().write() else {\n        bail!(\"Failed to lock global data mutex\");\n    };\n    debug!(\"found {} icons from theme\", icon_names.len());\n    map.clear();\n    for icon in icon_names {\n        map.insert(icon.into_boxed_str());\n    }\n    drop(map);\n\n    if env::var_os(\"HYPRSHELL_NO_ALL_ICONS\").is_none() {\n        for path in search_path {\n            let span_2 = span.clone();\n            if path.exists() {\n                if in_background {\n                    thread::spawn(move || {\n                        let _span = span_2.entered();\n                        let paths = collect_unique_filenames_recursive(&path);\n                        debug!(\n                            \"found {} icons from filesystem in {path:?} paths (in background)\",\n                            paths.len()\n                        );\n                        let Ok(mut map) = get_icons_from_cache().write() else {\n                            warn!(\"Failed to lock global data mutex\");\n                            return;\n                        };\n                        map.extend(paths);\n                        drop(map);\n                    });\n                } else {\n                    let paths = collect_unique_filenames_recursive(&path);\n                    debug!(\n                        \"found {} icons from filesystem in {path:?} paths\",\n                        paths.len()\n                    );\n                    let Ok(mut map) = get_icons_from_cache().write() else {\n                        bail!(\"Failed to lock global data mutex\");\n                    };\n                    map.extend(paths);\n                    drop(map);\n                }\n            }\n        }\n    }\n    trace!(\"icon map filled\");\n    Ok(())\n}\n\nfn collect_unique_filenames_recursive(dir: &Path) -> BTreeSet<Box<str>> {\n    let mut names = BTreeSet::new();\n    let mut dirs_to_visit = vec![dir.to_path_buf()];\n\n    while let Some(current_dir) = dirs_to_visit.pop() {\n        if current_dir.is_dir()\n            && let Ok(entries) = read_dir(&current_dir)\n        {\n            for entry in entries.flatten() {\n                let path = entry.path();\n                if path.is_dir() {\n                    dirs_to_visit.push(path);\n                } else if let Some(name_osstr) = path.file_stem() {\n                    // Avoid allocation unless needed\n                    let name = name_osstr.to_string_lossy();\n                    if !name.is_empty() && !names.contains(&*name) {\n                        names.insert(name.into_owned().into_boxed_str());\n                    }\n                }\n            }\n        }\n    }\n    names\n}\n"
  },
  {
    "path": "crates/core-lib/src/ini.rs",
    "content": "use std::collections::HashMap;\nuse std::collections::hash_map::Entry;\nuse std::fmt::Write;\nuse std::path::Path;\nuse tracing::{debug_span, warn};\n\n#[derive(Debug, Default)]\npub struct Section<'a> {\n    entries: HashMap<&'a str, Vec<&'a str>>,\n}\n\nimpl<'a> Section<'a> {\n    #[must_use]\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    #[must_use]\n    pub fn get_all(&self, key: &str) -> Option<&Vec<&'a str>> {\n        self.entries.get(key)\n    }\n\n    #[must_use]\n    pub fn get_first(&self, key: &str) -> Option<&'a str> {\n        self.entries.get(key)?.first().copied()\n    }\n\n    #[must_use]\n    pub fn get_all_as_boxed(&self, key: &str) -> Option<Vec<Box<str>>> {\n        self.get_all(key)\n            .map(|vec| vec.iter().copied().map(Box::from).collect::<Vec<_>>())\n    }\n\n    #[must_use]\n    pub fn get_first_as_boxed(&self, key: &str) -> Option<Box<str>> {\n        self.get_first(key).map(Box::from)\n    }\n    #[must_use]\n    pub fn get_first_as_path_boxed(&self, key: &str) -> Option<Box<Path>> {\n        self.get_first(key).map(Path::new).map(Box::from)\n    }\n\n    #[must_use]\n    pub fn get_first_as_boolean(&self, key: &str) -> Option<bool> {\n        self.get_first(key).map(|s| s == \"true\")\n    }\n}\n\nimpl<'a> Section<'a> {\n    pub fn insert_item(&mut self, key: &'a str, desktop_file: &'a str) {\n        self.entries.entry(key).or_default().push(desktop_file);\n    }\n    pub fn insert_item_at_front(&mut self, key: &'a str, desktop_file: &'a str) {\n        self.entries.entry(key).or_default().insert(0, desktop_file);\n    }\n    pub fn insert_items(&mut self, key: &'a str, mut desktop_files: Vec<&'a str>) {\n        self.entries\n            .entry(key)\n            .or_default()\n            .append(&mut desktop_files);\n    }\n    pub fn set_items(&mut self, key: &'a str, mut desktop_files: Vec<&'a str>) {\n        self.entries\n            .entry(key)\n            .and_modify(|e| {\n                e.clear();\n                e.append(&mut desktop_files);\n            })\n            .or_insert_with(|| desktop_files);\n    }\n}\n\n#[derive(Debug, Default)]\npub struct IniFile<'a> {\n    sections: HashMap<&'a str, Section<'a>>,\n}\n\nimpl IniFile<'_> {\n    #[allow(clippy::should_implement_trait)]\n    pub fn from_str(content: &str) -> IniFile<'_> {\n        let _span = debug_span!(\"from_str\").entered();\n\n        let mut sections = HashMap::new();\n        let mut current_section = sections.entry(\"\").or_insert_with(Section::default);\n\n        for line in content.lines() {\n            let line = line.trim();\n            if line.is_empty() || line.starts_with('#') || line.starts_with(';') {\n                continue;\n            }\n\n            if line.starts_with('[') && line.ends_with(']') {\n                let current_section_name = &line[1..line.len() - 1];\n                current_section = sections\n                    .entry(current_section_name.trim())\n                    .or_insert_with(Section::default);\n                continue;\n            }\n\n            if let Some((key, value)) = line.split_once('=') {\n                let key = key.trim();\n                let value = value.trim();\n\n                // Skip localized entries (containing [...])\n                if key.contains('[') {\n                    continue;\n                }\n                let values = value\n                    .split(';')\n                    .map(str::trim)\n                    .filter(|s| !s.is_empty())\n                    .collect::<Vec<_>>();\n                current_section.insert_items(key, values);\n            } else {\n                warn!(\"malformed line: {line}\");\n            }\n        }\n\n        IniFile { sections }\n    }\n}\n\nimpl<'a> IniFile<'a> {\n    #[must_use]\n    pub fn get_section(&'a self, section_name: &str) -> Option<&'a Section<'a>> {\n        self.sections.get(section_name)\n    }\n\n    #[must_use]\n    pub const fn sections(&self) -> &HashMap<&'a str, Section<'a>> {\n        &self.sections\n    }\n\n    #[must_use]\n    pub fn format(&self) -> String {\n        let mut str = String::with_capacity(self.into_iter().count() * 20); // 20 chars per line should be good\n        let mut sections = self.sections().iter().collect::<Vec<_>>();\n        sections.sort_by_key(|&(name, _)| name);\n        for (name, section) in sections {\n            if !name.is_empty() {\n                if str.is_empty() {\n                    let _ = str.write_str(&format!(\"[{name}]\\n\"));\n                } else {\n                    let _ = str.write_str(&format!(\"\\n[{name}]\\n\"));\n                }\n            }\n            let mut section = section.into_iter().collect::<Vec<_>>();\n            section.sort_by_key(|(key, _)| *key);\n            for (key, values) in section {\n                let _ = str.write_str(&format!(\"{key}={}\\n\", values.join(\";\")));\n            }\n        }\n        str\n    }\n}\n\nimpl<'a> IniFile<'a> {\n    pub fn get_section_mut<'b>(&'b mut self, section_name: &str) -> Option<&'b mut Section<'a>>\n    where\n        'a: 'b,\n    {\n        self.sections.get_mut(section_name)\n    }\n\n    pub fn section_entry<'b>(&'b mut self, section_name: &'a str) -> Entry<'b, &'a str, Section<'a>>\n    where\n        'a: 'b,\n    {\n        self.sections.entry(section_name)\n    }\n    pub fn insert_section(&mut self, name: &'a str, section: Section<'a>) {\n        self.sections.insert(name, section);\n    }\n}\n\nimpl<'a> IniFile<'a> {\n    #[allow(dead_code)]\n    fn iter(&'a self) -> Box<dyn Iterator<Item = <&'a Self as IntoIterator>::Item> + 'a> {\n        <&Self as IntoIterator>::into_iter(self)\n    }\n}\n\nimpl<'a> IntoIterator for &'a IniFile<'a> {\n    type Item = (&'a str, &'a str, &'a Vec<&'a str>);\n    type IntoIter = Box<dyn Iterator<Item = Self::Item> + 'a>;\n\n    fn into_iter(self) -> Self::IntoIter {\n        let iter = self.sections.iter().flat_map(|(section_name, section)| {\n            section\n                .into_iter()\n                .map(move |(key, values)| (*section_name, key, values))\n        });\n        Box::new(iter)\n    }\n}\n\nimpl<'a> Section<'a> {\n    #[allow(dead_code)]\n    fn iter(&'a self) -> Box<dyn Iterator<Item = <&'a Self as IntoIterator>::Item> + 'a> {\n        <&Self as IntoIterator>::into_iter(self)\n    }\n}\n\nimpl<'a> IntoIterator for &'a Section<'a> {\n    type Item = (&'a str, &'a Vec<&'a str>);\n    type IntoIter = Box<dyn Iterator<Item = Self::Item> + 'a>;\n\n    fn into_iter(self) -> Self::IntoIter {\n        let iter = self.entries.iter().map(|(key, value)| (*key, value));\n        Box::new(iter)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_parse_ini() {\n        let content = r\"[Section1]\nkey1=value1\nkey2=value2\n\n[Section2]\nfoo=bar\nbaz=qux\n\n; Comment\n# Another comment\n[Empty Section]\n\n[Section With Spaces]\nkey with spaces=value with spaces; and more values\n\";\n\n        let ini = IniFile::from_str(content);\n\n        assert_eq!(\n            ini.get_section(\"Section1\")\n                .expect(\"section missing\")\n                .get_first(\"key1\"),\n            Some(\"value1\")\n        );\n        assert_eq!(\n            ini.get_section(\"Section2\")\n                .expect(\"section missing\")\n                .get_first(\"foo\"),\n            Some(\"bar\")\n        );\n\n        assert!(ini.get_section(\"Empty Section\").is_some());\n        assert_ne!(\n            ini.get_section(\"Section With Spaces\")\n                .expect(\"section missing\")\n                .get_all(\"key with spaces\"),\n            Some(&vec![\"value with spaces\"])\n        );\n        assert_ne!(\n            ini.get_section(\"Section With Spaces\")\n                .expect(\"section missing\")\n                .get_all(\"key with spaces\"),\n            Some(&vec![\"value with spaces\"])\n        );\n        assert_eq!(\n            ini.get_section(\"Section With Spaces\")\n                .expect(\"section missing\")\n                .get_all(\"key with spaces\"),\n            Some(&vec![\"value with spaces\", \"and more values\"])\n        );\n\n        assert!(ini.get_section(\"NonExistent\").is_none());\n        assert_eq!(\n            ini.get_section(\"Section1\")\n                .expect(\"section missing\")\n                .get_first(\"nonexistent\"),\n            None\n        );\n    }\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_empty_ini() {\n        let content = \"\";\n        let ini = IniFile::from_str(content);\n        assert_eq!(ini.sections().len(), 1);\n    }\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_no_sections() {\n        let content = \"key=value\";\n        let ini = IniFile::from_str(content);\n        assert_eq!(\n            ini.get_section(\"\")\n                .expect(\"section missing\")\n                .get_first(\"key\"),\n            Some(\"value\")\n        );\n    }\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_values_iterator() {\n        let content = r\"\n    [Section1]\n    key1=value1\n    key2=value2;values3\n\n    [Section2]\n    foo=bar\n    \";\n        let ini = IniFile::from_str(content);\n        let mut values: Vec<_> = ini.into_iter().collect();\n        values.sort_by_key(|&(section, key, _)| (section, key));\n        let mut iter = values.iter();\n        assert_eq!(iter.next(), Some(&(\"Section1\", \"key1\", &vec![\"value1\"])));\n        assert_eq!(\n            iter.next(),\n            Some(&(\"Section1\", \"key2\", &vec![\"value2\", \"values3\"]))\n        );\n        assert_eq!(iter.next(), Some(&(\"Section2\", \"foo\", &vec![\"bar\"])));\n        assert_eq!(values.len(), 3);\n    }\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_values_iterator_2() {\n        let content = r\"\n    [Section1]\n    key1=value1\n    key2=value2\n\n    [Section2]\n    foo=bar\n    \";\n        let ini = IniFile::from_str(content);\n        let mut count = 0;\n        for (section, name, value) in &ini {\n            assert!(!section.is_empty(), \"Item should not be empty\");\n            assert!(!name.is_empty(), \"Item should not be empty\");\n            assert!(!value.is_empty(), \"Item should not be empty\");\n            count += 1;\n        }\n        assert_eq!(count, 3, \"There should be 3 items in the iterator\");\n    }\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_format_empty() {\n        let content = \"test=test\";\n        let ini = IniFile::from_str(content);\n        assert_eq!(ini.format(), \"test=test\\n\");\n    }\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_format_multiple_sections() {\n        let content = r\"[B]\nkey1=value1\nkey2=value2;value3\n\n[A]\nfoo=bar\n\";\n        let content2 = r\"[A]\nfoo=bar\n\n[B]\nkey1=value1\nkey2=value2;value3\n\";\n        let ini = IniFile::from_str(content);\n        assert_eq!(ini.format(), content2);\n    }\n}\n"
  },
  {
    "path": "crates/core-lib/src/ini_owned.rs",
    "content": "use std::collections::HashMap;\nuse std::collections::hash_map::Entry;\nuse std::fmt::Write;\nuse std::path::Path;\nuse tracing::{debug_span, warn};\n\n#[derive(Debug, Default)]\npub struct OwnedSection {\n    entries: HashMap<Box<str>, Vec<Box<str>>>,\n}\n\nimpl OwnedSection {\n    #[must_use]\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    #[must_use]\n    pub fn get_all(&self, key: &str) -> Option<Vec<Box<str>>> {\n        self.entries.get(key).cloned()\n    }\n\n    #[must_use]\n    pub fn get_first(&self, key: &str) -> Option<Box<str>> {\n        self.entries.get(key)?.first().cloned()\n    }\n\n    #[must_use]\n    pub fn get_first_as_path(&self, key: &str) -> Option<Box<Path>> {\n        self.get_first(key).as_deref().map(Path::new).map(Box::from)\n    }\n\n    #[must_use]\n    pub fn get_first_as_boolean(&self, key: &str) -> Option<bool> {\n        self.get_first(key).map(|s| &*s == \"true\")\n    }\n}\n\nimpl OwnedSection {\n    pub fn insert_item(&mut self, mime: Box<str>, desktop_file: Box<str>) {\n        self.entries.entry(mime).or_default().push(desktop_file);\n    }\n    pub fn insert_item_at_front(&mut self, mime: Box<str>, desktop_file: Box<str>) {\n        self.entries\n            .entry(mime)\n            .or_default()\n            .insert(0, desktop_file);\n    }\n    pub fn insert_items(&mut self, mime: Box<str>, mut desktop_files: Vec<Box<str>>) {\n        self.entries\n            .entry(mime)\n            .or_default()\n            .append(&mut desktop_files);\n    }\n}\n\n#[derive(Debug, Default)]\npub struct IniFileOwned {\n    sections: HashMap<Box<str>, OwnedSection>,\n}\n\nimpl IniFileOwned {\n    #[allow(clippy::should_implement_trait)]\n    pub fn from_str(content: &str) -> Self {\n        let _span = debug_span!(\"from_str\").entered();\n\n        let mut sections = HashMap::new();\n        let mut current_section = sections\n            .entry(Box::from(\"\"))\n            .or_insert_with(OwnedSection::default);\n\n        for line in content.lines() {\n            let line = line.trim();\n            if line.is_empty() || line.starts_with('#') || line.starts_with(';') {\n                continue;\n            }\n\n            if line.starts_with('[') && line.ends_with(']') {\n                let current_section_name = &line[1..line.len() - 1];\n                current_section = sections\n                    .entry(Box::from(current_section_name.trim()))\n                    .or_insert_with(OwnedSection::default);\n                continue;\n            }\n\n            if let Some((key, value)) = line.split_once('=') {\n                let key = key.trim();\n                let value = value.trim();\n\n                // Skip localized entries (containing [...])\n                if key.contains('[') {\n                    continue;\n                }\n                let values = value\n                    .split(';')\n                    .filter(|s| !s.is_empty())\n                    .map(|s| Box::from(s.trim()))\n                    .collect::<Vec<_>>();\n                current_section.insert_items(Box::from(key), values);\n            } else {\n                warn!(\"malformed line: {line}\");\n            }\n        }\n\n        Self { sections }\n    }\n}\n\nimpl IniFileOwned {\n    #[must_use]\n    pub fn get_section(&self, section_name: &str) -> Option<&OwnedSection> {\n        self.sections.get(section_name)\n    }\n\n    #[must_use]\n    pub const fn sections(&self) -> &HashMap<Box<str>, OwnedSection> {\n        &self.sections\n    }\n\n    #[must_use]\n    pub fn format(&self) -> String {\n        let mut str = String::with_capacity(self.into_iter().count() * 20); // 20 chars per line should be good\n        let mut sections = self.sections().iter().collect::<Vec<_>>();\n        sections.sort_by_key(|&(name, _)| name);\n        for (name, section) in sections {\n            if !name.is_empty() {\n                if str.is_empty() {\n                    let _ = str.write_str(&format!(\"[{name}]\\n\"));\n                } else {\n                    let _ = str.write_str(&format!(\"\\n[{name}]\\n\"));\n                }\n            }\n            let mut section = section.into_iter().collect::<Vec<_>>();\n            section.sort_by_key(|(key, _)| *key);\n            for (key, values) in section {\n                let _ = str.write_str(&format!(\"{key}={}\\n\", values.join(\";\")));\n            }\n        }\n        str\n    }\n}\n\nimpl IniFileOwned {\n    pub fn get_section_mut(&mut self, section_name: &str) -> Option<&mut OwnedSection> {\n        self.sections.get_mut(section_name)\n    }\n\n    pub fn section_entry(&mut self, section_name: Box<str>) -> Entry<'_, Box<str>, OwnedSection> {\n        self.sections.entry(section_name)\n    }\n    pub fn insert_section(&mut self, name: Box<str>, section: OwnedSection) {\n        self.sections.insert(name, section);\n    }\n}\n\nimpl<'a> IniFileOwned {\n    #[allow(dead_code)]\n    fn iter(&'a self) -> Box<dyn Iterator<Item = <&'a Self as IntoIterator>::Item> + 'a> {\n        <&Self as IntoIterator>::into_iter(self)\n    }\n}\n\nimpl<'a> IntoIterator for &'a IniFileOwned {\n    type Item = (&'a Box<str>, &'a Box<str>, &'a Vec<Box<str>>);\n    type IntoIter = Box<dyn Iterator<Item = Self::Item> + 'a>;\n\n    fn into_iter(self) -> Self::IntoIter {\n        let iter = self.sections.iter().flat_map(|(section_name, section)| {\n            section\n                .into_iter()\n                .map(move |(key, values)| (section_name, key, values))\n        });\n        Box::new(iter)\n    }\n}\n\nimpl<'a> OwnedSection {\n    #[allow(dead_code)]\n    fn iter(&'a self) -> Box<dyn Iterator<Item = <&'a Self as IntoIterator>::Item> + 'a> {\n        <&Self as IntoIterator>::into_iter(self)\n    }\n}\n\nimpl<'a> IntoIterator for &'a OwnedSection {\n    type Item = (&'a Box<str>, &'a Vec<Box<str>>);\n    type IntoIter = Box<dyn Iterator<Item = Self::Item> + 'a>;\n\n    fn into_iter(self) -> Self::IntoIter {\n        let iter = self.entries.iter();\n        Box::new(iter)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_parse_ini() {\n        let content = r\"[Section1]\nkey1=value1\nkey2=value2\n\n[Section2]\nfoo=bar\nbaz=qux\n\n; Comment\n# Another comment\n[Empty Section]\n\n[Section With Spaces]\nkey with spaces=value with spaces; and more values\n\";\n\n        let ini = IniFileOwned::from_str(content);\n\n        assert_eq!(\n            ini.get_section(\"Section1\")\n                .expect(\"section missing\")\n                .get_first(\"key1\"),\n            Some(\"value1\".into())\n        );\n        assert_eq!(\n            ini.get_section(\"Section2\")\n                .expect(\"section missing\")\n                .get_first(\"foo\"),\n            Some(\"bar\".into())\n        );\n\n        assert!(ini.get_section(\"Empty Section\").is_some());\n        assert_ne!(\n            ini.get_section(\"Section With Spaces\")\n                .expect(\"section missing\")\n                .get_all(\"key with spaces\"),\n            Some(vec![\"value with spaces\".into()])\n        );\n        assert_ne!(\n            ini.get_section(\"Section With Spaces\")\n                .expect(\"section missing\")\n                .get_all(\"key with spaces\"),\n            Some(vec![\"value with spaces\".into()])\n        );\n        assert_eq!(\n            ini.get_section(\"Section With Spaces\")\n                .expect(\"section missing\")\n                .get_all(\"key with spaces\"),\n            Some(vec![\"value with spaces\".into(), \"and more values\".into()])\n        );\n\n        assert!(ini.get_section(\"NonExistent\").is_none());\n        assert_eq!(\n            ini.get_section(\"Section1\")\n                .expect(\"section missing\")\n                .get_first(\"nonexistent\"),\n            None\n        );\n    }\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_empty_ini() {\n        let content = \"\";\n        let ini = IniFileOwned::from_str(content);\n        assert_eq!(ini.sections().len(), 1);\n    }\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_no_sections() {\n        let content = \"key=value\";\n        let ini = IniFileOwned::from_str(content);\n        assert_eq!(\n            ini.get_section(\"\")\n                .expect(\"section missing\")\n                .get_first(\"key\"),\n            Some(\"value\".into())\n        );\n    }\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_values_iterator() {\n        let content = r\"\n    [Section1]\n    key1=value1\n    key2=value2;values3\n\n    [Section2]\n    foo=bar\n    \";\n        let ini = IniFileOwned::from_str(content);\n        let mut values: Vec<_> = ini.into_iter().collect();\n        values.sort_by_key(|&(section, key, _)| (section, key));\n        let mut iter = values.iter();\n        assert_eq!(\n            iter.next(),\n            Some(&(&\"Section1\".into(), &\"key1\".into(), &vec![\"value1\".into()]))\n        );\n        assert_eq!(\n            iter.next(),\n            Some(&(\n                &\"Section1\".into(),\n                &\"key2\".into(),\n                &vec![\"value2\".into(), \"values3\".into()]\n            ))\n        );\n        assert_eq!(\n            iter.next(),\n            Some(&(&\"Section2\".into(), &\"foo\".into(), &vec![\"bar\".into()]))\n        );\n        assert_eq!(values.len(), 3);\n    }\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_values_iterator_2() {\n        let content = r\"\n    [Section1]\n    key1=value1\n    key2=value2\n\n    [Section2]\n    foo=bar\n    \";\n        let ini = IniFileOwned::from_str(content);\n        let mut count = 0;\n        for (section, name, value) in &ini {\n            assert!(!section.is_empty(), \"Item should not be empty\");\n            assert!(!name.is_empty(), \"Item should not be empty\");\n            assert!(!value.is_empty(), \"Item should not be empty\");\n            count += 1;\n        }\n        assert_eq!(count, 3, \"There should be 3 items in the iterator\");\n    }\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_format_empty() {\n        let content = \"test=test\";\n        let ini = IniFileOwned::from_str(content);\n        assert_eq!(ini.format(), \"test=test\\n\");\n    }\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_format_multiple_sections() {\n        let content = r\"[B]\nkey1=value1\nkey2=value2;value3\n\n[A]\nfoo=bar\n\";\n        let content2 = r\"[A]\nfoo=bar\n\n[B]\nkey1=value1\nkey2=value2;value3\n\";\n        let ini = IniFileOwned::from_str(content);\n        assert_eq!(ini.format(), content2);\n    }\n}\n"
  },
  {
    "path": "crates/core-lib/src/lib.rs",
    "content": "pub mod binds;\nmod r#const;\nmod data;\npub mod default;\npub mod ini;\npub mod ini_owned;\npub mod listener;\nmod notify;\npub mod path;\npub mod transfer;\npub mod util;\n\npub use r#const::*;\npub use data::*;\npub use notify::*;\npub use util::{GetFirstOrLast, RevIf, Warn, WarnWithDetails};\n"
  },
  {
    "path": "crates/core-lib/src/listener.rs",
    "content": "use crate::WarnWithDetails;\nuse anyhow::{Context, bail};\nuse notify::event::{DataChange, ModifyKind};\nuse notify::{Config, Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher};\nuse std::path::Path;\nuse tracing::{debug, info, trace, warn};\n\npub fn hyprshell_config_listener<F>(\n    file_path: &Path,\n    callback: F,\n) -> anyhow::Result<RecommendedWatcher>\nwhere\n    F: Fn(&'static str) + 'static + Clone + Send,\n{\n    if !file_path.exists() {\n        bail!(\"unable to watch for file changes as the file doesnt exist\");\n    }\n\n    let mut watcher = RecommendedWatcher::new(\n        move |res: notify::Result<Event>| match res {\n            Ok(event) if event.kind == EventKind::Modify(ModifyKind::Data(DataChange::Any)) => {\n                trace!(\"Event: {event:?}\");\n                callback(\"hyprshell config change\");\n            }\n            Err(err) => {\n                warn!(\"Watch error: {err:?}\");\n            }\n            Ok(_) => {}\n        },\n        Config::default(),\n    )\n    .context(\"Failed to create watcher\")?;\n\n    info!(\"Starting hyprshell config reload listener\");\n    watcher\n        .watch(file_path, RecursiveMode::NonRecursive)\n        .context(\"Failed to start hyprshell config reload listener\")?;\n\n    Ok(watcher)\n}\n\npub fn hyprshell_css_listener<F>(\n    file_path: &Path,\n    callback: F,\n) -> anyhow::Result<RecommendedWatcher>\nwhere\n    F: Fn(&'static str) + 'static + Clone + Send,\n{\n    if !file_path.exists() {\n        bail!(\"unable to watch for file changes as the file doesnt exist\");\n    }\n\n    let mut watcher = RecommendedWatcher::new(\n        move |res: notify::Result<Event>| match res {\n            Ok(event) if event.kind == EventKind::Modify(ModifyKind::Data(DataChange::Any)) => {\n                trace!(\"Event: {event:?}\");\n                callback(\"hyprshell css change\");\n            }\n            Err(err) => {\n                warn!(\"Watch error: {err:?}\");\n            }\n            Ok(_) => {}\n        },\n        Config::default(),\n    )\n    .context(\"Failed to create watcher\")?;\n\n    info!(\"Starting hyprshell css reload listener\");\n    watcher\n        .watch(file_path.as_ref(), RecursiveMode::NonRecursive)\n        .context(\"Failed to start hyprshell css reload listener\")?;\n\n    Ok(watcher)\n}\n\npub fn hyprshell_config_block(file_path: &Path) -> anyhow::Result<()> {\n    if !file_path.exists() {\n        bail!(\"unable to watch for file changes as the file doesnt exist, exiting\");\n    }\n\n    let (tx, rx) = std::sync::mpsc::channel();\n    let mut watcher = RecommendedWatcher::new(\n        move |res: notify::Result<Event>| match res {\n            Ok(event) if event.kind == EventKind::Modify(ModifyKind::Data(DataChange::Any)) => {\n                trace!(\"Event: {event:?}\");\n                tx.send(()).warn_details(\"Failed to send reload signal\");\n            }\n            Err(err) => {\n                warn!(\"Watch error: {err:?}\");\n            }\n            Ok(_) => {}\n        },\n        Config::default(),\n    )\n    .context(\"Failed to create watcher\")?;\n    debug!(\"Starting hyprshell config reload blocker\");\n\n    watcher\n        .watch(file_path.as_ref(), RecursiveMode::NonRecursive)\n        .context(\"Failed to start hyprshell config reload blocker\")?;\n    rx.recv().warn_details(\"Failed to receive reload signal\");\n    Ok(())\n}\n"
  },
  {
    "path": "crates/core-lib/src/notify.rs",
    "content": "use notify_rust::{Hint, Notification};\nuse std::time::Duration;\nuse tracing::{info, warn};\n\npub fn notify(body: &str, duration: Duration) {\n    info!(\"{}\", body);\n    let _ = Notification::new()\n        .summary(\"Hyprshell\")\n        .body(body)\n        .appname(\"hyprshell\")\n        .timeout(duration)\n        .urgency(notify_rust::Urgency::Normal)\n        .show();\n}\n\npub fn notify_resident(body: &str, duration: Duration) {\n    info!(\"{}\", body);\n    let _ = Notification::new()\n        .summary(\"Hyprshell\")\n        .body(body)\n        .appname(\"hyprshell\")\n        .timeout(duration)\n        .hint(Hint::Resident(true))\n        .timeout(Duration::from_secs(0))\n        .urgency(notify_rust::Urgency::Normal)\n        .show();\n}\n\npub fn notify_warn(body: &str) {\n    warn!(\"{}\", body);\n    let _ = Notification::new()\n        .summary(\"Hyprshell\")\n        .body(body)\n        .appname(\"hyprshell\")\n        .timeout(Duration::from_secs(8))\n        .urgency(notify_rust::Urgency::Critical)\n        .show();\n}\n"
  },
  {
    "path": "crates/core-lib/src/path.rs",
    "content": "use std::env;\nuse std::path::PathBuf;\nuse tracing::trace;\n\npub fn get_default_config_file() -> PathBuf {\n    let mut path = get_config_home();\n    #[cfg(debug_assertions)]\n    path.push(\"hyprshell.debug/\");\n    #[cfg(not(debug_assertions))]\n    path.push(\"hyprshell/\");\n    path.push(\"config.ron\");\n    if path.exists() {\n        trace!(\"Found config file at {path:?}\");\n        return path;\n    }\n\n    path.set_extension(\"toml\");\n    if path.exists() {\n        trace!(\"Found config file at {path:?}\");\n        return path;\n    }\n\n    path.set_extension(\"json\");\n    if path.exists() {\n        trace!(\"Found config file at {path:?}\");\n        return path;\n    }\n\n    #[cfg(feature = \"json5_config\")]\n    {\n        path.set_extension(\"json5\");\n        if path.exists() {\n            trace!(\"Found config file at {path:?}\");\n            return path;\n        }\n    }\n\n    path.set_extension(\"ron\");\n    path\n}\n\n#[must_use]\npub fn get_default_css_file() -> PathBuf {\n    let mut path = get_config_home();\n\n    #[cfg(debug_assertions)]\n    path.push(\"hyprshell.debug/styles.css\");\n    #[cfg(not(debug_assertions))]\n    path.push(\"hyprshell/styles.css\");\n    path\n}\n\n#[must_use]\npub fn get_default_data_dir() -> PathBuf {\n    let mut path = get_data_home();\n\n    #[cfg(debug_assertions)]\n    path.push(\"hyprshell.debug\");\n    #[cfg(not(debug_assertions))]\n    path.push(\"hyprshell\");\n    path\n}\n\n#[must_use]\npub fn get_default_cache_dir() -> PathBuf {\n    let mut path = get_cache_home();\n\n    #[cfg(debug_assertions)]\n    path.push(\"hyprshell.debug\");\n    #[cfg(not(debug_assertions))]\n    path.push(\"hyprshell\");\n    path\n}\n\n#[must_use]\npub fn get_default_system_data_dir() -> PathBuf {\n    let mut path = get_system_data_home();\n\n    #[cfg(debug_assertions)]\n    path.push(\"hyprshell.debug\");\n    #[cfg(not(debug_assertions))]\n    path.push(\"hyprshell\");\n    path\n}\n\n/// # Panics\n/// if neither `XDG_DATA_HOME` nor HOME is set\n///\n/// returns `XDG_DATA_HOME` or `$HOME/.local/share`\n#[must_use]\npub fn get_data_home() -> PathBuf {\n    env::var_os(\"XDG_DATA_HOME\")\n        .map(PathBuf::from)\n        .or_else(|| {\n            env::var_os(\"HOME\")\n                .map(|home| PathBuf::from(format!(\"{}/.local/share\", home.to_string_lossy())))\n        })\n        .expect(\"Failed to get config dir (XDG_DATA_HOME or HOME not set)\")\n}\n\n/// returns `/usr/share`\n#[must_use]\npub fn get_system_data_home() -> PathBuf {\n    PathBuf::from(\"/usr/share\")\n}\n\n/// # Panics\n/// if neither `XDG_CACHE_HOME` nor HOME is set\n///\n/// Returns `XDG_CACHE_HOME` or `$HOME/.cache`\n#[must_use]\npub fn get_cache_home() -> PathBuf {\n    env::var_os(\"XDG_CACHE_HOME\")\n        .map(PathBuf::from)\n        .or_else(|| {\n            env::var_os(\"HOME\")\n                .map(|home| PathBuf::from(format!(\"{}/.cache\", home.to_string_lossy())))\n        })\n        .expect(\"Failed to get config dir (XDG_CACHE_HOME or HOME not set)\")\n}\n\n/// # Panics\n/// if neither `XDG_CONFIG_HOME` nor HOME is set\n///\n/// Returns `XDG_CONFIG_HOME` or `$HOME/.config`\n#[must_use]\npub fn get_config_home() -> PathBuf {\n    env::var_os(\"XDG_CONFIG_HOME\")\n        .map(PathBuf::from)\n        .or_else(|| {\n            env::var_os(\"HOME\")\n                .map(|home| PathBuf::from(format!(\"{}/.config\", home.to_string_lossy())))\n        })\n        .expect(\"Failed to get config dir (XDG_CONFIG_HOME or HOME not set)\")\n}\n\n/// Returns `XDG_CONFIG_DIRS` or `/etc/xdg/`\n#[must_use]\npub fn get_config_dirs() -> Vec<PathBuf> {\n    env::var_os(\"XDG_CONFIG_DIRS\").map_or_else(\n        || vec![PathBuf::from(\"/etc/xdg/\")],\n        |val| env::split_paths(&val).collect(),\n    )\n}\n\n/// Returns `XDG_DATA_DIRS` or `/usr/local/share` and `/usr/share`\n#[must_use]\npub fn get_data_dirs() -> Vec<PathBuf> {\n    env::var_os(\"XDG_DATA_DIRS\").map_or_else(\n        || {\n            vec![\n                PathBuf::from(\"/usr/local/share\"),\n                PathBuf::from(\"/usr/share\"),\n            ]\n        },\n        |val| env::split_paths(&val).collect(),\n    )\n}\n"
  },
  {
    "path": "crates/core-lib/src/transfer/mod.rs",
    "content": "mod receive;\nmod send;\nmod structs;\n\npub use receive::*;\npub use send::*;\npub use structs::*;\n"
  },
  {
    "path": "crates/core-lib/src/transfer/receive.rs",
    "content": "use crate::transfer::TransferType;\nuse anyhow::Context;\nuse tracing::debug;\n\npub fn receive_from_buffer(mut buffer: Vec<u8>) -> anyhow::Result<TransferType> {\n    if buffer.last() == Some(&0) {\n        let _ = buffer.pop();\n    }\n    let str =\n        str::from_utf8(&buffer).with_context(|| format!(\"Failed to convert buffer: {buffer:?}\"))?;\n    let transfer: TransferType =\n        serde_json::from_str(str).with_context(|| format!(\"Failed to deserialize str: {str:?}\"))?;\n    debug!(\"Received command: {transfer:?}\");\n    Ok(transfer)\n}\n"
  },
  {
    "path": "crates/core-lib/src/transfer/send.rs",
    "content": "use crate::util::get_daemon_socket_path_buff;\nuse anyhow::Context;\nuse std::io::{BufRead, BufReader, Write};\nuse std::os::unix::net::UnixStream;\nuse tracing::{debug, trace};\n\npub fn send_raw_to_socket(data: &str) -> anyhow::Result<()> {\n    let path = get_daemon_socket_path_buff();\n    debug!(\"Sending data to socket: {data}\");\n    trace!(\"Socket path: {}\", path.display());\n    let mut stream = UnixStream::connect(&path)\n        .with_context(|| format!(\"Can't connect to daemon socket {}\", path.display()))?;\n    trace!(\"Socket connected, sending data\");\n    stream\n        .write_all(data.as_bytes())\n        .and_then(|()| stream.write_all(b\"\\0\"))\n        .context(\"Can't send data to socket\")?;\n    trace!(\"Data sent\");\n    let mut reader = BufReader::new(stream);\n    let mut buffer = vec![];\n    reader\n        .read_until(b'\\0', &mut buffer)\n        .context(\"Can't read data from socket\")?;\n    let ret = String::from_utf8(buffer).context(\"Failed to convert buffer\")?;\n    debug!(\"Received data from socket: {ret}\");\n    Ok(())\n}\n"
  },
  {
    "path": "crates/core-lib/src/transfer/structs.rs",
    "content": "use crate::{ClientId, WorkspaceId};\nuse serde::{Deserialize, Serialize};\n\n#[derive(Debug, Serialize, Deserialize)]\npub enum TransferType {\n    /// send from the keybind to open the overview\n    OpenOverview,\n    /// send from the keybind to open the switch\n    OpenSwitch(OpenSwitch),\n    /// send from the keybinds like arrow keys or tab on overview\n    SwitchOverview(SwitchOverviewConfig),\n    /// send from the keybinds like arrow keys or tab on switch\n    SwitchSwitch(SwitchSwitchConfig),\n    CloseOverview(CloseOverviewConfig),\n    CloseSwitch,\n    /// send from the gui itself when typing the launcher\n    Type(String),\n    /// send from pressing ESC or repressing openOverview\n    Exit,\n    /// send from the app itself when new monitor / config changes detected\n    Restart,\n}\n#[derive(Debug, Serialize, Deserialize)]\npub struct OpenSwitch {\n    pub reverse: bool,\n}\n\n#[derive(Debug, Serialize, Deserialize)]\npub struct SwitchOverviewConfig {\n    pub direction: Direction,\n    pub workspace: bool,\n}\n\n#[derive(Debug, Serialize, Deserialize)]\npub struct SwitchSwitchConfig {\n    pub direction: Direction,\n}\n\n#[derive(Debug, Serialize, Deserialize)]\npub enum CloseOverviewConfig {\n    LauncherClick(Identifier),\n    LauncherPress(char),\n    Windows(WindowsOverride),\n    None,\n}\n\n#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]\npub enum PluginNames {\n    Applications,\n    Shell,\n    Terminal,\n    WebSearch,\n    Calc,\n    Path,\n    Actions,\n}\n\n#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]\npub struct Identifier {\n    pub plugin: PluginNames,\n    // identifies the box in the launcher results\n    pub data: Option<Box<str>>,\n    // additional data used to get suboption in submenu (only available when launched through click)\n    pub data_additional: Option<Box<str>>,\n}\n\nimpl Identifier {\n    #[must_use]\n    pub const fn plugin(plugin: PluginNames) -> Self {\n        Self {\n            plugin,\n            data: None,\n            data_additional: None,\n        }\n    }\n\n    #[must_use]\n    pub const fn data(plugin: PluginNames, data: Box<str>) -> Self {\n        Self {\n            plugin,\n            data: Some(data),\n            data_additional: None,\n        }\n    }\n\n    #[must_use]\n    pub const fn data_additional(\n        plugin: PluginNames,\n        data: Box<str>,\n        data_additional: Box<str>,\n    ) -> Self {\n        Self {\n            plugin,\n            data: Some(data),\n            data_additional: Some(data_additional),\n        }\n    }\n}\n\n#[derive(Debug, Serialize, Deserialize)]\npub enum WindowsOverride {\n    ClientId(ClientId),\n    WorkspaceID(WorkspaceId),\n}\n\n#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]\npub enum Direction {\n    Right,\n    Left,\n    Up,\n    Down,\n}\n"
  },
  {
    "path": "crates/core-lib/src/util/boot.rs",
    "content": "use anyhow::Context;\nuse std::fs::File;\nuse std::io::Read;\nuse std::sync::OnceLock;\nuse tracing::{instrument, warn};\n\npub fn get_boot_id() -> &'static Option<String> {\n    static BOOT_ID: OnceLock<Option<String>> = OnceLock::new();\n    BOOT_ID.get_or_init(|| {\n        load_boot_id().map_or_else(\n            |e| {\n                warn!(\"Failed to load boot ID: {e}\");\n                None\n            },\n            Some,\n        )\n    })\n}\n\n#[instrument(level = \"debug\", ret(level = \"trace\"))]\nfn load_boot_id() -> anyhow::Result<String> {\n    let mut file = File::open(\"/proc/sys/kernel/random/boot_id\")\n        .context(\"Failed to open /proc/sys/kernel/random/boot_id\")?;\n    let mut contents = String::new();\n    file.read_to_string(&mut contents)\n        .context(\"Failed to read boot_id\")?;\n    Ok(contents.trim().to_string())\n}\n"
  },
  {
    "path": "crates/core-lib/src/util/exec.rs",
    "content": "#[derive(Debug, Clone)]\npub enum ExecType {\n    Flatpak(Box<str>, Box<str>),\n    PWA(Box<str>, Box<str>),\n    FlatpakPWA(Box<str>, Box<str>),\n    Absolute(Box<str>, Box<str>),\n    AppImage(Box<str>, Box<str>),\n    Relative(Box<str>),\n}\n\nconst UNKNOWN_EXEC: &str = \"unknown\";\n\n#[must_use]\npub fn analyse_exec(exec: &str) -> ExecType {\n    let exec_trim = exec.replace(['\\'', '\"'], \"\");\n    // pwa detection\n    if exec.contains(\"--app-id=\") && exec.contains(\"--profile-directory=\") {\n        // \"flatpak 'run'\" = pwa from browser inside flatpak\n        if exec.contains(\"flatpak run\") || exec.contains(\"flatpak 'run'\") {\n            let browser_exec_in_flatpak = exec_trim\n                .split_whitespace()\n                .find(|s| s.contains(\"--command=\"))\n                .and_then(|s| {\n                    s.split('=')\n                        .next_back()\n                        .and_then(|s| s.split('/').next_back())\n                })\n                .unwrap_or(UNKNOWN_EXEC);\n            let flatpak_identifier = exec_trim\n                .split_whitespace()\n                .skip(2)\n                .find(|arg| !arg.starts_with(\"--\"))\n                .unwrap_or(UNKNOWN_EXEC);\n            ExecType::FlatpakPWA(\n                Box::from(flatpak_identifier),\n                Box::from(browser_exec_in_flatpak),\n            )\n        } else {\n            // normal PWA\n            let browser_exec = exec\n                .split_whitespace()\n                .next()\n                .and_then(|s| s.split('/').next_back())\n                .unwrap_or(UNKNOWN_EXEC);\n            let browser_full_exec = exec.split_whitespace().next().unwrap_or(UNKNOWN_EXEC);\n            ExecType::PWA(Box::from(browser_exec), Box::from(browser_full_exec))\n        }\n        // flatpak detection\n    } else if exec.contains(\"flatpak run\") || exec.contains(\"flatpak 'run'\") {\n        let command_in_flatpak = exec_trim\n            .split_whitespace()\n            .find(|s| s.contains(\"--command=\"))\n            .and_then(|s| {\n                s.split('=')\n                    .next_back()\n                    .and_then(|s| s.split('/').next_back())\n            })\n            .unwrap_or(UNKNOWN_EXEC);\n        let flatpak_identifier = exec_trim\n            .split_whitespace()\n            .skip(2)\n            .find(|arg| !arg.starts_with(\"--\"))\n            .unwrap_or(UNKNOWN_EXEC);\n        ExecType::Flatpak(Box::from(flatpak_identifier), Box::from(command_in_flatpak))\n    } else if exec_trim.contains(\".AppImage\") {\n        // AppImage detection\n        let appimage_name = exec_trim\n            .split_whitespace()\n            .next()\n            .and_then(|s| s.split('/').next_back())\n            .and_then(|s| s.split('_').next())\n            .unwrap_or(UNKNOWN_EXEC);\n        ExecType::AppImage(Box::from(appimage_name), Box::from(exec))\n    } else if exec_trim.starts_with('/') {\n        let exec_name = exec_trim\n            .split_whitespace()\n            .next()\n            .and_then(|s| s.split('/').next_back())\n            .unwrap_or(UNKNOWN_EXEC);\n        ExecType::Absolute(Box::from(exec_name), Box::from(exec))\n    } else {\n        ExecType::Relative(Box::from(exec_trim))\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_relative_exec() {\n        assert!(matches!(\n            analyse_exec(\"nautilus --new-window\"),\n            ExecType::Relative(ref s) if &**s == \"nautilus --new-window\"\n        ));\n    }\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_flatpak_pwa_exec() {\n        assert!(matches!(\n            analyse_exec(\n                \"flatpak 'run' '--command=/app/bin/chromium' 'org.chromium.Chromium' '--profile-directory=Default' '--app-id=awf'\"\n            ),\n            ExecType::FlatpakPWA(ref id, ref browser) if &**id == \"org.chromium.Chromium\" && &**browser == \"chromium\"\n        ));\n    }\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_appimage_exec() {\n        assert!(matches!(\n            analyse_exec(\n                \"/home/user/Applications/ungoogled-chromium_71.0.3578.98-2_linux_awf.AppImage %u\"\n            ),\n            ExecType::AppImage(ref name, ref path) if &**name == \"ungoogled-chromium\" && &**path == \"/home/user/Applications/ungoogled-chromium_71.0.3578.98-2_linux_awf.AppImage %u\"\n        ));\n    }\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_absolute_pwa_exec() {\n        assert!(matches!(\n            analyse_exec(\n                \"/opt/google/chrome/google-chrome --profile-directory=Default --app-id=awf\"\n            ),\n            ExecType::PWA(ref browser, ref path) if &**browser == \"google-chrome\" && &**path == \"/opt/google/chrome/google-chrome\"\n        ));\n    }\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_flatpak_exec() {\n        assert!(matches!(\n            analyse_exec(\"flatpak run org.mozilla.firefox\"),\n            ExecType::Flatpak(ref id, ref command) if &**id == \"org.mozilla.firefox\" && &**command == \"unknown\"\n        ));\n    }\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_absolute_exec() {\n        assert!(matches!(\n            analyse_exec(\"/usr/bin/firefox\"),\n            ExecType::Absolute(ref name, ref path) if &**name == \"firefox\" && &**path == \"/usr/bin/firefox\"\n        ));\n    }\n}\n"
  },
  {
    "path": "crates/core-lib/src/util/exists.rs",
    "content": "use std::env;\nuse std::env::split_paths;\nuse std::fs;\nuse std::path::{Path, PathBuf};\n\n#[cfg(unix)]\nuse std::os::unix::fs::PermissionsExt;\n\n// Common fallback directories.\nconst COMMON_DIRS: &[&str] = &[\n    \"/usr/bin\",\n    \"/usr/local/bin\",\n    \"/bin\",\n    \"/sbin\",\n    \"/usr/sbin\",\n    \"/usr/local/sbin\",\n    \"/snap/bin\",\n];\n// NixOS-specific locations.\nconst NIX_DIRS: &[&str] = &[\n    \"/run/current-system/sw/bin\",\n    \"/nix/var/nix/profiles/default/bin\",\n];\n\n/// Try to find an executable named `name`.\n///\n/// - If `name` contains a path separator, that path is checked directly.\n/// - Otherwise the function searches:\n///   - directories from `PATH`\n///   - some common system directories (`/usr/bin`, `/usr/local/bin`, `/bin`, ...)\n///   - NixOS-specific locations (`/run/current-system/sw/bin`, `$HOME/.nix-profile/bin`, `/nix/var/nix/profiles/default/bin`)\n///\n/// Returns `Some(PathBuf)` of the first found executable, or `None`.\n#[must_use]\npub fn find_command(name: &str) -> Option<PathBuf> {\n    if name.is_empty() {\n        return None;\n    }\n\n    let path = Path::new(name);\n\n    // If name contains a path separator, check that directly.\n    if path.components().count() > 1 {\n        return if is_executable(path) {\n            Some(path.to_path_buf())\n        } else {\n            None\n        };\n    }\n\n    // Collect candidate directories from PATH.\n    let env_path = env::var_os(\"PATH\").unwrap_or_default();\n    let mut candidates: Vec<_> = split_paths(&env_path).collect();\n\n    for d in COMMON_DIRS {\n        candidates.push(PathBuf::from(d));\n    }\n\n    for d in NIX_DIRS {\n        candidates.push(PathBuf::from(d));\n    }\n\n    // User Nix profile if HOME is present.\n    if let Some(home) = env::var_os(\"HOME\") {\n        candidates.push(PathBuf::from(home).join(\".nix-profile\").join(\"bin\"));\n    }\n\n    // Check each candidate directory for the executable.\n    for dir in candidates {\n        if dir.as_os_str().is_empty() {\n            continue;\n        }\n        let candidate = dir.clone().join(name);\n        if is_executable(&candidate) {\n            return Some(candidate);\n        }\n    }\n\n    None\n}\n\n/// Convenience wrapper that returns true if the command exists somewhere.\n#[must_use]\npub fn command_exists(name: &str) -> bool {\n    find_command(name).is_some()\n}\n\nfn is_executable(path: &Path) -> bool {\n    if !path.exists() {\n        return false;\n    }\n    if !path.is_file() {\n        return false;\n    }\n\n    #[cfg(unix)]\n    {\n        if let Ok(meta) = fs::metadata(path) {\n            let mode = meta.permissions().mode();\n            // Check any of the execute bits (owner/group/other).\n            return mode & 0o111 != 0;\n        }\n        false\n    }\n\n    #[cfg(not(unix))]\n    {\n        // On non-unix platforms, fallback to \"exists and is file\".\n        true\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn find_shell_exists() {\n        // common shell should exist on CI / dev machines; skip strict assertion\n        let candidates = [\"sh\", \"bash\", \"zsh\"];\n        let found = candidates.iter().any(|c| command_exists(c));\n        assert!(found, \"expected at least one common shell to exist\");\n    }\n\n    #[test]\n    fn nonexistent_command_does_not_exist() {\n        assert!(!command_exists(\n            \"this-command-should-never-exist-12345-amogus\"\n        ));\n    }\n}\n"
  },
  {
    "path": "crates/core-lib/src/util/helpers.rs",
    "content": "use std::fmt;\nuse tracing::{debug, warn};\n\npub trait GetFirstOrLast: Iterator + Sized {\n    fn get_first_or_last(self, last: bool) -> Option<Self::Item>;\n}\nimpl<I: Iterator> GetFirstOrLast for I {\n    fn get_first_or_last(mut self, last: bool) -> Option<Self::Item> {\n        if last { self.last() } else { self.next() }\n    }\n}\n\npub trait GetNextOrPrev: Iterator + Sized {\n    fn get_next_or_prev(self, last: bool, len: usize) -> Option<Self::Item>;\n}\nimpl<I: Iterator> GetNextOrPrev for I {\n    fn get_next_or_prev(mut self, last: bool, len: usize) -> Option<Self::Item> {\n        if last {\n            if len == 0 {\n                None\n            } else {\n                // skip to the last element\n                self.nth(len - 1)\n            }\n        } else {\n            self.next()\n        }\n    }\n}\n\npub trait RevIf<'a>: Iterator + Sized + 'a {\n    fn reverse_if(self, cond: bool) -> Box<dyn Iterator<Item = Self::Item> + 'a>;\n}\n\nimpl<'a, I: DoubleEndedIterator + 'a> RevIf<'a> for I {\n    fn reverse_if(self, cond: bool) -> Box<dyn Iterator<Item = Self::Item> + 'a> {\n        if cond {\n            Box::new(self.rev())\n        } else {\n            Box::new(self)\n        }\n    }\n}\n\npub trait WarnWithDetails<A> {\n    fn warn_details(self, msg: &str) -> Option<A>;\n}\n\npub trait Warn<A> {\n    fn warn(self) -> Option<A>;\n}\n\nimpl<A> WarnWithDetails<A> for Option<A> {\n    fn warn_details(self, msg: &str) -> Self {\n        #[allow(clippy::option_if_let_else)]\n        if let Some(o) = self {\n            Some(o)\n        } else {\n            warn!(\"{msg}\");\n            None\n        }\n    }\n}\n\nimpl<A, E: fmt::Debug + fmt::Display> WarnWithDetails<A> for Result<A, E> {\n    fn warn_details(self, msg: &str) -> Option<A> {\n        match self {\n            Ok(o) => Some(o),\n            Err(e) => {\n                warn!(\"{msg}: {e:?}\");\n                debug!(\"Error: {e:?}\");\n                None\n            }\n        }\n    }\n}\n\nimpl<A, E: fmt::Debug + fmt::Display> Warn<A> for Result<A, E> {\n    fn warn(self) -> Option<A> {\n        match self {\n            Ok(o) => Some(o),\n            Err(e) => {\n                warn!(\"{e}\");\n                debug!(\"Error: {e:?}\");\n                None\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "crates/core-lib/src/util/mod.rs",
    "content": "mod boot;\nmod exec;\nmod exists;\nmod helpers;\nmod path;\n\npub use boot::*;\npub use exec::*;\npub use exists::*;\npub use helpers::*;\npub use path::*;\nuse std::env::{var, var_os};\nuse std::os::unix::net::UnixStream;\nuse std::path::PathBuf;\nuse tracing::debug;\n\n#[must_use]\npub fn get_daemon_socket_path_buff() -> PathBuf {\n    #[allow(clippy::option_if_let_else)]\n    let mut buf = if let Some(runtime_path) = var_os(\"XDG_RUNTIME_DIR\") {\n        if let Some(instance) = var_os(\"HYPRLAND_INSTANCE_SIGNATURE\") {\n            PathBuf::from(runtime_path).join(\"hypr\").join(instance)\n        } else {\n            PathBuf::from(runtime_path)\n        }\n    } else if let Ok(uid) = var(\"UID\") {\n        PathBuf::from(\"/run/user/\".to_owned() + &uid)\n    } else {\n        PathBuf::from(\"/tmp\")\n    };\n    buf.push(\"hyprshell.sock\");\n    buf\n}\n\npub fn daemon_running() -> bool {\n    // check if socket exists and socket is open\n    let buf = get_daemon_socket_path_buff();\n    if buf.exists() {\n        debug!(\"Checking if daemon is running on {}\", buf.display());\n        UnixStream::connect(buf).is_ok()\n    } else {\n        debug!(\"Daemon not running\");\n        false\n    }\n}\n"
  },
  {
    "path": "crates/core-lib/src/util/path.rs",
    "content": "use crate::path::{get_config_dirs, get_config_home, get_data_dirs, get_data_home};\nuse std::fs::DirEntry;\nuse tracing::{trace, warn};\n\n/// Collects all .desktop files from standard directories\n/// according to the XDG Base Directory Specification.\npub fn collect_desktop_files() -> Vec<DirEntry> {\n    let mut res = Vec::new();\n    let dirs = {\n        // ensure correct order\n        let mut dirs = Vec::new();\n        dirs.push(get_data_home().join(\"applications\"));\n        get_data_dirs()\n            .iter()\n            .map(|d| d.join(\"applications\"))\n            .for_each(|d| dirs.push(d));\n        dirs\n    };\n    for dir in dirs {\n        if !dir.exists() {\n            continue;\n        }\n        match dir.read_dir() {\n            Ok(dir) => {\n                for entry in dir.flatten() {\n                    let path = entry.path();\n                    if path.is_file()\n                        && path.extension().is_some_and(|e| e == \"desktop\")\n                        && !res\n                            .iter()\n                            .any(|e: &DirEntry| e.file_name() == entry.file_name())\n                    {\n                        res.push(entry);\n                    }\n                }\n            }\n            Err(e) => {\n                warn!(\"Failed to read dir {dir:?}: {e}\");\n            }\n        }\n    }\n    trace!(\"found {} desktop files\", res.len());\n    res\n}\n\n/// Collects all mimeapps.list files from standard directories\n/// according to the XDG Base Directory Specification.\npub fn collect_mime_files() -> Vec<DirEntry> {\n    let mut res = Vec::new();\n    let dirs = {\n        // ensure correct order\n        let mut dirs = Vec::new();\n        dirs.push(get_config_home());\n        dirs.append(&mut get_config_dirs());\n        dirs.push(get_data_home().join(\"applications\"));\n        get_data_dirs()\n            .iter()\n            .map(|d| d.join(\"applications\"))\n            .for_each(|d| dirs.push(d));\n        dirs\n    };\n    for dir in dirs {\n        if !dir.exists() {\n            continue;\n        }\n        match dir.read_dir() {\n            Ok(dir) => {\n                for entry in dir.flatten() {\n                    let path = entry.path();\n                    if path.is_file()\n                        && path\n                            .file_name()\n                            .is_some_and(|e| e.to_string_lossy().ends_with(\"mimeapps.list\"))\n                    {\n                        res.push(entry);\n                    }\n                }\n            }\n            Err(e) => {\n                warn!(\"Failed to read dir {dir:?}: {e}\");\n            }\n        }\n    }\n    trace!(\"found {} mimeapps lists\", res.len());\n    res\n}\n"
  },
  {
    "path": "crates/exec-lib/Cargo.toml",
    "content": "[package]\nname = \"hyprshell-exec-lib\"\ndocumentation = \"https://docs.rs/hyprshell-exec-lib\"\nversion = \"4.9.5\"\nedition.workspace = true\ndescription.workspace = true\nlicense.workspace = true\nauthors.workspace = true\nkeywords.workspace = true\nrepository.workspace = true\nrust-version.workspace = true\n\n[dependencies]\nanyhow.workspace = true\ntracing.workspace = true\ncore-lib.workspace = true\nconfig-lib.workspace = true\nhyprland-plugin.workspace = true\nsemver.workspace = true\nhyprland = { package = \"hyprshell-hyprland\", path = \"../../dep-crates/hyprland-rs\", features = [\"async-lite\", \"dispatch\", \"keyword\", \"data\", \"listener\", \"ctl\", \"config\"], default-features = false, version = \"=4.9.5\" }\n\n[lints]\nworkspace = true\n"
  },
  {
    "path": "crates/exec-lib/src/binds.rs",
    "content": "use anyhow::Context;\nuse core_lib::binds::ExecBind;\nuse core_lib::{LAUNCHER_NAMESPACE, OVERVIEW_NAMESPACE, SWITCH_NAMESPACE};\nuse hyprland::config::binds;\nuse hyprland::config::binds::{Binder, Binding};\nuse hyprland::dispatch::DispatchType;\nuse hyprland::keyword::Keyword;\nuse tracing::{trace, warn};\n\npub fn apply_layerrules() -> anyhow::Result<()> {\n    // TODO add option to enable blur\n\n    Keyword::set(\"layerrule\", format!(\"noanim, {LAUNCHER_NAMESPACE}\"))?;\n    Keyword::set(\"layerrule\", format!(\"xray 0, {LAUNCHER_NAMESPACE}\"))?;\n\n    Keyword::set(\"layerrule\", format!(\"noanim, {OVERVIEW_NAMESPACE}\"))?;\n    Keyword::set(\"layerrule\", format!(\"xray 0, {OVERVIEW_NAMESPACE}\"))?;\n\n    Keyword::set(\"layerrule\", format!(\"noanim, {SWITCH_NAMESPACE}\"))?;\n    Keyword::set(\"layerrule\", format!(\"dimaround, {SWITCH_NAMESPACE}\"))?;\n    Keyword::set(\"layerrule\", format!(\"xray 0, {SWITCH_NAMESPACE}\"))?;\n    trace!(\"layerrules applied\");\n    Ok(())\n}\n\n// ctrl+shift+alt, h\n// hyprland::bind!(d e | SUPER, Key, \"a\" => Exec, \"pkill hyprshell\");\npub fn apply_exec_bind(bind: &ExecBind) -> anyhow::Result<()> {\n    let binding = Binding {\n        mods: bind\n            .mods\n            .iter()\n            .filter_map(|m| match m.to_lowercase().as_str() {\n                \"alt\" => Some(binds::Mod::ALT),\n                \"control\" | \"ctrl\" => Some(binds::Mod::CTRL),\n                \"super\" | \"win\" => Some(binds::Mod::SUPER),\n                \"shift\" => Some(binds::Mod::SHIFT),\n                _ => {\n                    warn!(\"unknown mod: {m}\");\n                    None\n                }\n            })\n            .collect(),\n        key: binds::Key::Key(&bind.key),\n        flags: vec![],\n        dispatcher: DispatchType::Exec(&bind.exec),\n    };\n    trace!(\"binding exec: {binding:?}\");\n    Binder::bind(binding).with_context(|| format!(\"binding exec failed: {bind:?}\"))?;\n    Ok(())\n}\n"
  },
  {
    "path": "crates/exec-lib/src/collect.rs",
    "content": "use crate::to_client_id;\nuse anyhow::Context;\nuse core_lib::{\n    ClientData, ClientId, FindByFirst, MonitorData, MonitorId, WorkspaceData, WorkspaceId,\n};\nuse hyprland::data::{Client, Clients, Monitor, Monitors, Workspace, Workspaces};\nuse hyprland::prelude::*;\nuse tracing::{debug_span, warn};\n\nfn get_hypr_data() -> anyhow::Result<(Vec<Monitor>, Vec<Workspace>, Vec<Client>)> {\n    let _span = debug_span!(\"get_hypr_data\").entered();\n    let monitors = Monitors::get().context(\"monitors failed\")?.to_vec();\n    // sort and filter all workspaces sorted by ID\n    let workspaces = {\n        let mut workspaces = Workspaces::get()\n            .context(\"workspaces failed\")?\n            .into_iter()\n            .filter(|w| w.id != -1) // TODO: check if still needed: ignore clients on invalid workspaces\n            .collect::<Vec<_>>();\n\n        workspaces.sort_by(|a, b| a.id.cmp(&b.id));\n        workspaces\n    };\n    let clients = Clients::get()\n        .context(\"clients failed\")?\n        .into_iter()\n        .filter(|c| c.workspace.id != -1) // TODO: check if still needed: ignore clients on invalid workspaces\n        .collect::<Vec<_>>();\n\n    Ok((monitors, workspaces, clients))\n}\n\n#[allow(clippy::type_complexity)]\npub fn collect_hypr_data() -> anyhow::Result<(\n    Vec<(ClientId, ClientData)>,\n    Vec<(WorkspaceId, WorkspaceData)>,\n    Vec<(MonitorId, MonitorData)>,\n    Option<(String, ClientId)>,\n    WorkspaceId,\n    MonitorId,\n)> {\n    let _span = debug_span!(\"convert_hypr_data\").entered();\n\n    let (monitors, workspaces, clients) =\n        get_hypr_data().context(\"loading hyprland data failed\")?;\n\n    // all monitors with their data, x and y are the offset of the monitor, width and height are the size of the monitor.\n    // combined_width and combined_height are the combined size of all workspaces on the monitor and workspaces_on_monitor is the number of workspaces on the monitor\n    let mut monitor_data = {\n        let mut md: Vec<(MonitorId, MonitorData)> = Vec::with_capacity(monitors.iter().len());\n\n        for monitor in &monitors {\n            #[allow(clippy::cast_sign_loss)]\n            md.push((\n                monitor.id,\n                MonitorData {\n                    x: monitor.x,\n                    y: monitor.y,\n                    width: (f32::from(monitor.width) / monitor.scale) as u16,\n                    height: (f32::from(monitor.height) / monitor.scale) as u16,\n                    connector: monitor.name.clone(),\n                },\n            ));\n        }\n        md\n    };\n\n    // all workspaces with their data, x and y are the offset of the workspace\n    let mut workspace_data = {\n        let mut wd: Vec<(WorkspaceId, WorkspaceData)> = Vec::with_capacity(workspaces.len());\n\n        for (monitor_id, monitor_data) in &monitor_data {\n            workspaces\n                .iter()\n                .filter(|ws| ws.monitor_id == Some(*monitor_id))\n                .for_each(|workspace| {\n                    wd.push((\n                        workspace.id,\n                        WorkspaceData {\n                            name: workspace.name.clone(),\n                            monitor: *monitor_id,\n                            height: monitor_data.height,\n                            width: monitor_data.width,\n                            any_client_enabled: true, // gets updated later\n                        },\n                    ));\n                });\n        }\n        wd\n    };\n\n    let client_data = {\n        let mut cd: Vec<(ClientId, ClientData)> = Vec::with_capacity(clients.len());\n\n        for client in clients {\n            let Some(monitor) = client.monitor else {\n                continue;\n            };\n            if workspace_data.find_by_first(&client.workspace.id).is_some() {\n                cd.push((\n                    to_client_id(&client.address),\n                    ClientData {\n                        x: client.at.0,\n                        y: client.at.1,\n                        width: client.size.0,\n                        height: client.size.1,\n                        class: client.class.clone(),\n                        workspace: client.workspace.id,\n                        monitor,\n                        focus_history_id: client.focus_history_id,\n                        title: client.title.clone(),\n                        floating: client.floating,\n                        pid: client.pid,\n                        enabled: true, // gets updated later\n                    },\n                ));\n            } else {\n                warn!(\n                    \"workspace {:?} not found for client {client:?}\",\n                    client.workspace\n                );\n            }\n        }\n        cd\n    };\n\n    workspace_data.sort_by(|a, b| a.0.cmp(&b.0));\n    monitor_data.sort_by(|a, b| a.0.cmp(&b.0));\n\n    // is broken, reports the \"normal\" workspace as active when a client in special workspace is selected\n    // let active_ws = Workspace::get_active()?.id;\n    let active_ws = Workspace::get_active()\n        .map(|w| w.id)\n        .context(\"active workspace failed\")?;\n    let active_ws = Client::get_active()\n        .context(\"active client failed\")?\n        .map_or(active_ws, |a| a.workspace.id);\n    let active_monitor = Monitor::get_active().context(\"active monitor failed\")?.id;\n    let active_client = Client::get_active()\n        .context(\"active client failed\")?\n        .map(|a| (a.class.clone(), to_client_id(&a.address)));\n\n    Ok((\n        client_data,\n        workspace_data,\n        monitor_data,\n        active_client,\n        active_ws,\n        active_monitor,\n    ))\n}\n"
  },
  {
    "path": "crates/exec-lib/src/lib.rs",
    "content": "pub mod collect;\npub mod listener;\npub mod switch;\nmod util;\n\npub mod binds;\npub mod plugin;\npub mod run;\n\npub use util::*;\n"
  },
  {
    "path": "crates/exec-lib/src/listener.rs",
    "content": "use core_lib::WarnWithDetails;\nuse tracing::info;\n\n#[allow(clippy::future_not_send)]\npub async fn monitor_listener<F>(callback: F)\nwhere\n    F: Fn(&'static str) + 'static + Clone,\n{\n    let mut event_listener = hyprland::event_listener::EventListener::new();\n    let callback_clone = callback.clone();\n    event_listener.add_monitor_added_handler(move |_data| {\n        callback(\"monitor added\");\n    });\n    event_listener.add_monitor_removed_handler(move |_data| {\n        callback_clone(\"monitor removed\");\n    });\n    info!(\"Starting monitor added/removed listener\");\n    event_listener\n        .start_listener_async()\n        .await\n        .warn_details(\"Failed to start monitor added/removed listener\");\n}\n\n#[allow(clippy::future_not_send)]\npub async fn hyprland_config_listener<F>(callback: F)\nwhere\n    F: Fn(&'static str) + 'static,\n{\n    let mut event_listener = hyprland::event_listener::EventListener::new();\n    event_listener.add_config_reloaded_handler(move || {\n        callback(\"hyprland config reload\");\n    });\n    info!(\"Starting hyprland config reload listener\");\n    event_listener\n        .start_listener_async()\n        .await\n        .warn_details(\"Failed to start hyprland config reload listener\");\n}\n"
  },
  {
    "path": "crates/exec-lib/src/plugin.rs",
    "content": "use anyhow::{Context, bail};\nuse config_lib::Modifier;\nuse hyprland::ctl::plugin;\nuse hyprland_plugin::PluginConfig;\nuse std::path::Path;\nuse std::sync::OnceLock;\nuse tracing::{debug, debug_span, info, trace};\n\n// info: trying to load a plugin causes hyprland to issue a reload\n// this will cause hyprshell to restart.\n// this second restart wont reload the plugin as the plugin config didnt change\n// if the plugin fails to load it however tries again which the triggers the next reload\nstatic PLUGIN_COULD_BE_BUILD: OnceLock<bool> = OnceLock::new();\n\npub fn load_plugin(\n    switch: Option<(Modifier, Box<str>)>,\n    overview: Option<(Modifier, Box<str>)>,\n    version: &semver::Version,\n) -> anyhow::Result<()> {\n    let _span = debug_span!(\"load_plugin\").entered();\n\n    if PLUGIN_COULD_BE_BUILD.get() == Some(&false) {\n        bail!(\"plugin could not be built last, skipping to prevent reload loop\");\n    }\n\n    let config = PluginConfig {\n        xkb_key_switch_mod: switch\n            .as_ref()\n            .map(|(r#mod, _)| Box::from(mod_to_xkb_key(*r#mod))),\n        xkb_key_switch_key: switch.map(|(_, key)| key),\n        xkb_key_overview_mod: overview\n            .as_ref()\n            .map(|(r#mod, _)| Box::from(r#mod.to_string())),\n        xkb_key_overview_key: overview.map(|(_, key)| key),\n    };\n\n    if check_new_plugin_needed(&config) {\n        unload().context(\"unable to unload old plugin\")?;\n        info!(\"Building plugin, this may take a while, please wait\");\n        hyprland_plugin::generate(&config, version).context(\"unable to generate plugin\")?;\n        trace!(\n            \"generated plugin at {:?}\",\n            hyprland_plugin::PLUGIN_OUTPUT_PATH\n        );\n        if let Err(err) = plugin::load(Path::new(hyprland_plugin::PLUGIN_OUTPUT_PATH)) {\n            PLUGIN_COULD_BE_BUILD.get_or_init(|| false);\n            trace!(\"plugin failed to load, disabling plugin\");\n            bail!(\"unable to load plugin: {err:?}\")\n        }\n        trace!(\"loaded plugin\");\n    } else {\n        debug!(\"plugin already loaded, skipping\");\n    }\n\n    Ok(())\n}\n\npub fn check_new_plugin_needed(config: &PluginConfig) -> bool {\n    let plugins = plugin::list().unwrap_or_default();\n    trace!(\"plugins: {plugins:?}\");\n    for plugin in plugins {\n        if plugin.name == hyprland_plugin::PLUGIN_NAME {\n            let Some(desc) = plugin.description.split(\" - \").last() else {\n                continue;\n            };\n            if desc == config.to_string() {\n                // config didn't change, no need to reload\n                return false;\n            }\n        }\n    }\n    true\n}\n\npub fn unload() -> anyhow::Result<()> {\n    let plugins = plugin::list().unwrap_or_default();\n    for plugin in plugins {\n        if plugin.name == hyprland_plugin::PLUGIN_NAME {\n            debug!(\"plugin loaded, unloading it\");\n            plugin::unload(Path::new(hyprland_plugin::PLUGIN_OUTPUT_PATH)).with_context(|| {\n                format!(\n                    \"unable to unload old plugin at: {}\",\n                    hyprland_plugin::PLUGIN_OUTPUT_PATH\n                )\n            })?;\n            debug!(\"plugin unloaded\");\n        }\n    }\n    Ok(())\n}\n\n#[allow(clippy::must_use_candidate)]\npub const fn mod_to_xkb_key(r#mod: Modifier) -> &'static str {\n    match r#mod {\n        Modifier::Alt => \"XKB_KEY_Alt\",\n        Modifier::Ctrl => \"XKB_KEY_Control\",\n        Modifier::Super => \"XKB_KEY_Super\",\n        Modifier::None => \"XKB_KEY_NoSymbol\",\n    }\n}\n"
  },
  {
    "path": "crates/exec-lib/src/run.rs",
    "content": "use anyhow::{Context, bail};\nuse core_lib::TERMINALS;\nuse std::ffi::OsString;\nuse std::os::unix::prelude::CommandExt;\nuse std::path::{Path, PathBuf};\nuse std::process::{Command, Stdio};\nuse std::{env, thread};\nuse tracing::{debug, trace};\n\npub fn run_program(\n    run: &str,\n    path: Option<&Path>,\n    terminal: bool,\n    default_terminal: Option<&str>,\n) -> anyhow::Result<()> {\n    debug!(\"Running: {run}\");\n    let home_path_buf = env::var_os(\"HOME\").map(PathBuf::from);\n    let path = path.map_or(home_path_buf.as_deref(), Some);\n    if terminal {\n        if let Some(term) = default_terminal {\n            let command = format!(\"{term} -e {run}\");\n            run_command(&command, path).context(\"Failed to run command\")?;\n        } else {\n            let env_path = env::var_os(\"PATH\")\n                .unwrap_or_else(|| OsString::from(\"/usr/bin:/bin:/usr/local/bin\"));\n            debug!(\n                \"No default terminal found, searching common terminals in PATH. (Set default_terminal in config to avoid this search)\"\n            );\n            trace!(\"PATH: {}\", env_path.to_string_lossy());\n            let paths: Vec<_> = env::split_paths(&env_path).collect();\n            let mut found_terminal = false;\n            for term in TERMINALS {\n                if paths.iter().any(|p| p.join(term).exists()) {\n                    let command = format!(\"{term} -e {run}\");\n                    if run_command(&command, path).is_ok() {\n                        trace!(\"Found and launched terminal: {term}\");\n                        found_terminal = true;\n                        break;\n                    }\n                }\n            }\n            if !found_terminal {\n                bail!(\"Failed to find a terminal to run the command\");\n            }\n        }\n    } else {\n        run_command(run, path).context(\"Failed to run command\")?;\n    }\n    Ok(())\n}\n\nfn get_command(command: &str) -> Command {\n    // replace common exec placeholders\n    let mut command = command.to_string();\n    for replacement in [\"%f\", \"%F\", \"%u\", \"%U\"] {\n        command = command.replace(replacement, \"\");\n    }\n    // if run as systemd unit all programs exit when not run outside the units cgroup\n    if env::var_os(\"INVOCATION_ID\").is_some() {\n        let mut cmd = Command::new(\"systemd-run\");\n        cmd.args([\n            \"--user\",\n            \"--scope\",\n            \"--collect\",\n            \"/usr/bin/env\",\n            \"bash\",\n            \"-c\",\n            &command,\n        ]);\n        cmd\n    } else {\n        let mut cmd = Command::new(\"/usr/bin/env\");\n        cmd.args([\"bash\", \"-c\", &command]);\n        cmd\n    }\n}\n\nfn run_command(run: &str, path: Option<&Path>) -> anyhow::Result<()> {\n    trace!(\"Original command: {run:?}\");\n    let mut cmd = get_command(run);\n    cmd.process_group(0);\n    if let Some(path) = path {\n        cmd.current_dir(path);\n    }\n\n    debug!(\"Running command: {cmd:?}\");\n    let out = cmd.stdout(Stdio::piped()).stderr(Stdio::piped()).spawn()?;\n    thread::spawn(move || {\n        let start = std::time::Instant::now();\n        let output = out.wait_with_output();\n        trace!(\"Command [{cmd:?}] finished\");\n        if let Ok(output) = output\n            && start.elapsed().as_secs() < 2\n            && (!output.stdout.is_empty() || !output.stderr.is_empty())\n        {\n            trace!(\"Output from [{cmd:?}]: {output:?}\");\n        }\n    });\n    Ok(())\n}\n"
  },
  {
    "path": "crates/exec-lib/src/switch.rs",
    "content": "use anyhow::Context;\nuse core_lib::Warn;\nuse hyprland::data::{Client, Monitors, Workspace, Workspaces};\nuse hyprland::dispatch::{\n    Dispatch, DispatchType, WindowIdentifier, WorkspaceIdentifierWithSpecial,\n};\nuse hyprland::prelude::*;\nuse hyprland::shared::{Address, WorkspaceId};\nuse tracing::{debug, trace};\n\npub fn switch_client(address: Address) -> anyhow::Result<()> {\n    debug!(\"execute switch to client: {address}\");\n    deactivate_special_workspace_if_needed().warn();\n    Dispatch::call(DispatchType::FocusWindow(WindowIdentifier::Address(\n        address,\n    )))?;\n    Dispatch::call(DispatchType::BringActiveToTop)?;\n    Ok(())\n}\n\npub fn switch_client_by_initial_class(class: &str) -> anyhow::Result<()> {\n    debug!(\"execute switch to client: {class} by initial_class\");\n    deactivate_special_workspace_if_needed().warn();\n    Dispatch::call(DispatchType::FocusWindow(\n        WindowIdentifier::ClassRegularExpression(&format!(\n            \"initialclass:{}\",\n            class.to_ascii_lowercase()\n        )),\n    ))?;\n    Dispatch::call(DispatchType::BringActiveToTop)?;\n    Ok(())\n}\n\npub fn switch_workspace(workspace_id: WorkspaceId) -> anyhow::Result<()> {\n    deactivate_special_workspace_if_needed().warn();\n\n    // check if already on workspace (if so, don't switch because it throws an error `Previous workspace doesn't exist`)\n    let current_workspace = Workspace::get_active();\n    if let Ok(workspace) = current_workspace\n        && workspace_id == workspace.id\n    {\n        trace!(\"Already on workspace {}\", workspace_id);\n        return Ok(());\n    }\n\n    if workspace_id < 0 {\n        switch_special_workspace(workspace_id).with_context(|| {\n            format!(\"Failed to execute switch special workspace with id {workspace_id}\")\n        })?;\n    } else {\n        switch_normal_workspace(workspace_id).with_context(|| {\n            format!(\"Failed to execute switch workspace with id {workspace_id}\")\n        })?;\n    }\n    Ok(())\n}\n\nfn switch_special_workspace(workspace_id: WorkspaceId) -> anyhow::Result<()> {\n    let special = Monitors::get()?\n        .into_iter()\n        .find(|m| m.special_workspace.id == workspace_id);\n    if let Some(special) = special {\n        trace!(\"Special workspace already toggled: {special:?}\");\n        return Ok(());\n    }\n    let ws = Workspaces::get()?\n        .into_iter()\n        .find(|w| w.id == workspace_id)\n        .context(\"workspace not found\")?;\n    Dispatch::call(DispatchType::ToggleSpecialWorkspace(Some(\n        ws.name.trim_start_matches(\"special:\").to_string(),\n    )))\n    .context(\"failed to execute toggle special workspace\")\n}\n\nfn switch_normal_workspace(workspace_id: WorkspaceId) -> anyhow::Result<()> {\n    debug!(\"execute switch to workspace {workspace_id}\");\n    Dispatch::call(DispatchType::Workspace(WorkspaceIdentifierWithSpecial::Id(\n        workspace_id,\n    )))?;\n    Ok(())\n}\n\n/// always run when changing client or workspace\n///\n/// if client on special workspace is opened the workspace is activated\nfn deactivate_special_workspace_if_needed() -> anyhow::Result<()> {\n    let active_ws = Workspace::get_active()\n        .map(|w| w.name)\n        .context(\"active workspace failed\")?;\n    let active_ws = Client::get_active()\n        .context(\"active client failed\")?\n        .map_or(active_ws, |a| a.workspace.name);\n    trace!(\"current workspace: {active_ws}\");\n    if active_ws.starts_with(\"special:\") {\n        debug!(\"current client is on special workspace, deactivating special workspace\");\n        // current client is on special workspace\n        Dispatch::call(DispatchType::ToggleSpecialWorkspace(Some(\n            active_ws.trim_start_matches(\"special:\").to_string(),\n        )))?;\n    }\n    Ok(())\n}\n"
  },
  {
    "path": "crates/exec-lib/src/util.rs",
    "content": "use anyhow::{Context, anyhow};\nuse core_lib::{Active, ClientId, notify_warn};\nuse hyprland::ctl::reload;\nuse hyprland::data::{Client, Clients, Monitor, Monitors, Workspace};\nuse hyprland::keyword::Keyword;\nuse hyprland::prelude::*;\nuse semver::Version;\nuse std::sync::{Mutex, OnceLock};\nuse std::thread;\nuse std::time::Duration;\nuse tracing::{debug, info, trace, warn};\n\npub fn get_clients() -> Vec<Client> {\n    Clients::get().map_or(vec![], HyprDataVec::to_vec)\n}\n\npub fn get_monitors() -> Vec<Monitor> {\n    Monitors::get().map_or(vec![], HyprDataVec::to_vec)\n}\n\n#[must_use]\npub fn get_current_monitor() -> Option<Monitor> {\n    Monitor::get_active().ok()\n}\n\npub fn reload_hyprland_config() -> anyhow::Result<()> {\n    debug!(\"Reloading hyprland config\");\n    reload::call().context(\"Failed to reload hyprland config\")\n}\n\n/// trim 0x from hexadecimal (base-16) string and convert to id\n///\n/// # Panics\n/// Panics if the id cannot be parsed, this should never happen as the id is always a valid hexadecimal string\n#[must_use]\npub fn to_client_id(id: &hyprland::shared::Address) -> ClientId {\n    u64::from_str_radix(id.to_string().trim_start_matches(\"0x\"), 16)\n        .expect(\"Failed to parse client id, this should never happen\")\n}\n\n/// convert id to hexadecimal (base-16) string\n#[must_use]\npub fn to_client_address(id: ClientId) -> hyprland::shared::Address {\n    hyprland::shared::Address::new(format!(\"{id:x}\"))\n}\n\nfn get_prev_follow_mouse() -> &'static Mutex<Option<String>> {\n    static PREV_FOLLOW_MOUSE: OnceLock<Mutex<Option<String>>> = OnceLock::new();\n    PREV_FOLLOW_MOUSE.get_or_init(|| Mutex::new(None))\n}\n\npub fn set_no_follow_mouse() -> anyhow::Result<()> {\n    Keyword::set(\"input:follow_mouse\", \"3\").context(\"keyword failed\")?;\n    trace!(\"Set follow_mouse to 3\");\n    Ok(())\n}\n\npub fn reset_no_follow_mouse() -> anyhow::Result<()> {\n    let follow = get_prev_follow_mouse()\n        .lock()\n        .map_err(|e| anyhow::anyhow!(\"unable to lock get_prev_follow_mouse mutex: {e:?}\"))?;\n    if let Some(follow) = follow.as_ref() {\n        Keyword::set(\"input:follow_mouse\", follow.clone()).context(\"keyword failed\")?;\n        trace!(\"Restored previous follow_mouse value: {follow}\");\n    } else {\n        trace!(\"No previous follow_mouse value stored, skipping reset\");\n    }\n    drop(follow);\n    Ok(())\n}\n\npub fn set_follow_mouse_default() -> anyhow::Result<()> {\n    let mut lock = get_prev_follow_mouse()\n        .lock()\n        .map_err(|e| anyhow::anyhow!(\"unable to lock get_prev_follow_mouse mutex: {e:?}\"))?;\n    let follow = Keyword::get(\"input:follow_mouse\").context(\"keyword failed\")?;\n    trace!(\"Storing previous follow_mouse value: {}\", follow.value);\n    *lock = Some(follow.value.to_string());\n    drop(lock);\n    Ok(())\n}\n\n/// tries to get initial data for 500 ms * 40 = 20 s\n///\n/// # Errors\n/// Returns an error if the initial data is not available after 5000 ms\npub fn get_initial_active() -> anyhow::Result<Active> {\n    let mut tries = 0;\n    loop {\n        match internal_get_initial_active() {\n            Ok(a) => break Ok(a),\n            Err(e) => {\n                if tries > 40 {\n                    break Err(e);\n                }\n                warn!(\"waiting for correct initial active state: {e:?}\");\n                thread::sleep(Duration::from_millis(500));\n            }\n        }\n        tries += 1;\n    }\n}\n\nfn internal_get_initial_active() -> anyhow::Result<Active> {\n    let active_client = Client::get_active()\n        .ok()\n        .flatten()\n        .map(|c| to_client_id(&c.address));\n    let active_ws = Workspace::get_active()\n        .context(\"unable to get initial workspace\")?\n        .id;\n    let active_monitor = Monitor::get_active()\n        .context(\"unable to get initial monitor\")?\n        .id;\n\n    Ok(Active {\n        client: active_client,\n        workspace: active_ws,\n        monitor: active_monitor,\n    })\n}\n\npub fn check_version() -> anyhow::Result<Version> {\n    pub const MIN_VERSION: Version = Version::new(0, 52, 1);\n\n    let version = get_version()\n        .context(\"Failed to get version! (hyprland is probably outdated or too new??)\")?;\n    trace!(\"hyprland {version:?}\");\n\n    let version = version\n        .version\n        .unwrap_or_else(|| version.tag.trim_start_matches('v').to_string());\n    info!(\n        \"Starting hyprshell {} in {} mode on hyprland {version}\",\n        env!(\"CARGO_PKG_VERSION\"),\n        if cfg!(debug_assertions) {\n            \"debug\"\n        } else {\n            \"release\"\n        },\n    );\n    let parsed_version = Version::parse(&version).context(\"Unable to parse hyprland Version\")?;\n    if parsed_version.lt(&MIN_VERSION) {\n        notify_warn(&format!(\n            \"hyprland version {parsed_version} is too old or unknown, please update to at least {MIN_VERSION}\",\n        ));\n    }\n    Ok(parsed_version)\n}\n\nfn get_version() -> anyhow::Result<hyprland::data::Version> {\n    let mut tries = 0;\n    loop {\n        match hyprland::data::Version::get() {\n            Ok(a) => break Ok(a),\n            Err(e) => {\n                if tries > 40 {\n                    break Err(anyhow!(e));\n                }\n                warn!(\"waiting for correct initial active state: {e:?}\");\n                thread::sleep(Duration::from_millis(500));\n            }\n        }\n        tries += 1;\n    }\n}\n"
  },
  {
    "path": "crates/hyprland-plugin/Cargo.toml",
    "content": "[package]\nname = \"hyprshell-hyprland-plugin\"\ndocumentation = \"https://docs.rs/hyprshell-hyprland-plugin\"\nversion = \"4.9.5\"\ndescription = \"Plugin for hyprland, used to monitor keypresses\"\nedition.workspace = true\nlicense.workspace = true\nauthors.workspace = true\nkeywords.workspace = true\nrepository.workspace = true\nrust-version.workspace = true\n\n[dependencies]\nanyhow.workspace = true\ntracing.workspace = true\ncore-lib.workspace = true\nsemver.workspace = true\ntempfile = { version = \"3.20.0\", default-features = false }\nzip = { version = \"5.0.0\", default-features = false }\n\n[build-dependencies]\nzip = { version = \"5.0.0\", default-features = false }\n\n[lints]\nworkspace = true\n\n[dev-dependencies]\ntest-log.workspace = true\n"
  },
  {
    "path": "crates/hyprland-plugin/build.rs",
    "content": "use std::fs::read_dir;\nuse std::io::{Read, Write};\nuse std::path::PathBuf;\nuse std::{env, fs, fs::File, io, path::Path};\nuse zip::ZipWriter;\nuse zip::write::FileOptions;\n\nfn include_plugin(srcs_dir: &Path, out_dir: &Path) {\n    let prepare_dir = combine(srcs_dir, out_dir);\n\n    let zip_path = Path::new(out_dir).join(\"plugin.zip\");\n    let file = File::create(&zip_path).expect(\"Failed to create zip file\");\n    let mut zip = ZipWriter::new(&file);\n    let options: FileOptions<()> = FileOptions::default()\n        .compression_method(zip::CompressionMethod::Stored)\n        .compression_level(None)\n        .unix_permissions(0o755);\n    let mut buffer = Vec::new();\n    for file in read_dir(prepare_dir)\n        .expect(\"Failed to read prepare dir\")\n        .flatten()\n    {\n        // we can use the name as we dont allow for folders here\n        zip.start_file(file.file_name().to_string_lossy(), options)\n            .expect(\"Failed to start file in zip\");\n        let mut f = File::open(file.path()).expect(\"Failed to open file\");\n        f.read_to_end(&mut buffer).expect(\"Failed to read file\");\n        zip.write_all(&buffer).expect(\"Failed to write file to zip\");\n        buffer.clear();\n    }\n    zip.finish().expect(\"Failed to finish zip\");\n}\n\nfn combine(srcs_dir: &Path, out_dir: &Path) -> PathBuf {\n    let prepare_dir = Path::new(&out_dir).join(\"prepare\");\n    fs::create_dir_all(&prepare_dir).expect(\"Failed to create prepare dir\");\n    // Combine all source files into one for easier compilation\n    let all_cpp_path = prepare_dir.join(\"all.cpp\");\n    let mut all_cpp = File::create(&all_cpp_path).expect(\"Failed to create all.cpp\");\n\n    let cpp_files = read_dir(srcs_dir)\n        .expect(\"Failed to read srcs dir\")\n        .filter_map(Result::ok)\n        .filter(|entry| entry.path().extension().is_some_and(|ext| ext == \"cpp\"));\n    let header_files = read_dir(srcs_dir)\n        .expect(\"Failed to read srcs dir\")\n        .filter_map(Result::ok)\n        .filter(|entry| {\n            entry.path().extension().is_some_and(|ext| ext == \"h\")\n                && entry.path().file_name() != Some(\"defs-test.h\".as_ref())\n        });\n\n    for entry in cpp_files {\n        let src_path = entry.path();\n        #[allow(clippy::print_stderr)]\n        let Some(file_name) = src_path.file_name() else {\n            eprintln!(\n                \"Warning: could not get file name for path {}\",\n                src_path.display()\n            );\n            continue;\n        };\n        all_cpp\n            .write_fmt(format_args!(\"\\n\\n// {} \\n\", file_name.to_string_lossy()))\n            .expect(\"Failed to write to all.cpp\");\n        let mut src_file = File::open(&src_path).expect(\"Failed to open source file\");\n        io::copy(&mut src_file, &mut all_cpp).expect(\"Failed to copy source file to all.cpp\");\n    }\n    for header in header_files {\n        let dest_path = prepare_dir.join(header.file_name());\n        fs::copy(header.path(), dest_path).expect(\"Failed to copy header file\");\n    }\n    all_cpp.flush().expect(\"Failed to flush all.cpp\");\n    all_cpp.sync_all().expect(\"Failed to sync all.cpp\");\n    prepare_dir\n}\n\nfn main() {\n    let out_dir = env::var(\"OUT_DIR\").expect(\"out dir missing??\");\n    include_plugin(Path::new(\"plugin/src-52\"), &Path::new(&out_dir).join(\"52\"));\n    include_plugin(Path::new(\"plugin/src-54\"), &Path::new(&out_dir).join(\"54\"));\n    println!(\"cargo:rerun-if-changed=plugin/*\");\n}\n"
  },
  {
    "path": "crates/hyprland-plugin/plugin/.gitignore",
    "content": "out\nhyprshell.so"
  },
  {
    "path": "crates/hyprland-plugin/plugin/.idea/copilot.data.migration.agent.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"AgentMigrationStateService\">\n    <option name=\"migrationStatus\" value=\"COMPLETED\" />\n  </component>\n</project>"
  },
  {
    "path": "crates/hyprland-plugin/plugin/.idea/copilot.data.migration.ask2agent.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"Ask2AgentMigrationStateService\">\n    <option name=\"migrationStatus\" value=\"COMPLETED\" />\n  </component>\n</project>"
  },
  {
    "path": "crates/hyprland-plugin/plugin/.idea/copilot.data.migration.edit.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"EditMigrationStateService\">\n    <option name=\"migrationStatus\" value=\"COMPLETED\" />\n  </component>\n</project>"
  },
  {
    "path": "crates/hyprland-plugin/plugin/.idea/dictionaries/project.xml",
    "content": "<component name=\"ProjectDictionaryState\">\n  <dictionary name=\"project\">\n    <words>\n      <w>combinded</w>\n    </words>\n  </dictionary>\n</component>"
  },
  {
    "path": "crates/hyprland-plugin/plugin/.idea/editor.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"BackendCodeEditorSettings\">\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CDeclarationWithImplicitIntType/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CommentTypo/@EntryIndexedValue\" value=\"DO_NOT_SHOW\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=ConstevalIfIsAlwaysConstant/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppAbstractClassWithoutSpecifier/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppAbstractFinalClass/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppAbstractVirtualFunctionCallInCtor/@EntryIndexedValue\" value=\"ERROR\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppAccessSpecifierWithNoDeclarations/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppAwaiterTypeIsNotClass/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppBooleanIncrementExpression/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppBoostFormatBadCode/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppBoostFormatLegacyCode/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppBoostFormatMixedArgs/@EntryIndexedValue\" value=\"ERROR\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppBoostFormatTooFewArgs/@EntryIndexedValue\" value=\"ERROR\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppBoostFormatTooManyArgs/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCStyleCast/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCVQualifierCanNotBeAppliedToReference/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassCanBeFinal/@EntryIndexedValue\" value=\"DO_NOT_SHOW\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassIsIncomplete/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassNeedsConstructorBecauseOfUninitializedMember/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassNeverUsed/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCompileTimeConstantCanBeReplacedWithBooleanConstant/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppConceptNeverUsed/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppConditionalExpressionCanBeSimplified/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppConstParameterInDeclaration/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppConstValueFunctionReturnType/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCoroutineCallResolveError/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAArrayIndexOutOfBounds/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAConstantConditions/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAConstantFunctionResult/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAConstantParameter/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFADeletedPointer/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAEndlessLoop/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAInfiniteRecursion/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAInvalidatedMemory/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFALocalValueEscapesFunction/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFALocalValueEscapesScope/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFALoopConditionNotUpdated/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAMemoryLeak/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFANotInitializedField/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFANullDereference/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFATimeOver/@EntryIndexedValue\" value=\"HINT\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAUnreachableCode/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAUnreachableFunctionCall/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAUnreadVariable/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAUnusedValue/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeclarationHidesLocal/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeclarationHidesUncapturedLocal/@EntryIndexedValue\" value=\"HINT\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeclarationSpecifierWithoutDeclarators/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeclaratorDisambiguatedAsFunction/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeclaratorNeverUsed/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeclaratorUsedBeforeInitialization/@EntryIndexedValue\" value=\"ERROR\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDefaultCaseNotHandledInSwitchStatement/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDefaultInitializationWithNoUserConstructor/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDefaultIsUsedAsIdentifier/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDefaultedSpecialMemberFunctionIsImplicitlyDeleted/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDefinitionsOrder/@EntryIndexedValue\" value=\"HINT\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeletingVoidPointer/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDependentTemplateWithoutTemplateKeyword/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDependentTypeWithoutTypenameKeyword/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeprecatedEntity/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeprecatedOverridenMethod/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeprecatedRegisterStorageClassSpecifier/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDereferenceOperatorLimitExceeded/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDiscardedPostfixOperatorResult/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDoxygenSyntaxError/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDoxygenUndocumentedParameter/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDoxygenUnresolvedReference/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEmptyDeclaration/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceCVQualifiersOrder/@EntryIndexedValue\" value=\"DO_NOT_SHOW\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceCVQualifiersPlacement/@EntryIndexedValue\" value=\"DO_NOT_SHOW\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceDoStatementBraces/@EntryIndexedValue\" value=\"DO_NOT_SHOW\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceForStatementBraces/@EntryIndexedValue\" value=\"DO_NOT_SHOW\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceFunctionDeclarationStyle/@EntryIndexedValue\" value=\"DO_NOT_SHOW\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceIfStatementBraces/@EntryIndexedValue\" value=\"DO_NOT_SHOW\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceNestedNamespacesStyle/@EntryIndexedValue\" value=\"HINT\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceOverridingDestructorStyle/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceOverridingFunctionStyle/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceTypeAliasCodeStyle/@EntryIndexedValue\" value=\"DO_NOT_SHOW\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceWhileStatementBraces/@EntryIndexedValue\" value=\"DO_NOT_SHOW\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEntityAssignedButNoRead/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEntityUsedOnlyInUnevaluatedContext/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnumeratorNeverUsed/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEqualOperandsInBinaryExpression/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEvaluationFailure/@EntryIndexedValue\" value=\"ERROR\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppExplicitSpecializationInNonNamespaceScope/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppExpressionWithoutSideEffects/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppFinalFunctionInFinalClass/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppFinalNonOverridingVirtualFunction/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppForLoopCanBeReplacedWithWhile/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppForwardEnumDeclarationWithoutUnderlyingType/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppFunctionDoesntReturnValue/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppFunctionIsNotImplemented/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppFunctionResultShouldBeUsed/@EntryIndexedValue\" value=\"HINT\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppFunctionalStyleCast/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppHeaderHasBeenAlreadyIncluded/@EntryIndexedValue\" value=\"HINT\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppHiddenFunction/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppHidingFunction/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppIdenticalOperandsInBinaryExpression/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppIfCanBeReplacedByConstexprIf/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppImplicitDefaultConstructorNotAvailable/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppIncompatiblePointerConversion/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppIncompleteSwitchStatement/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppInconsistentNaming/@EntryIndexedValue\" value=\"DO_NOT_SHOW\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppIntegralToPointerConversion/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppInvalidLineContinuation/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppJoinDeclarationAndAssignment/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLambdaCaptureNeverUsed/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLocalVariableMayBeConst/@EntryIndexedValue\" value=\"HINT\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLocalVariableMightNotBeInitialized/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLocalVariableWithNonTrivialDtorIsNeverUsed/@EntryIndexedValue\" value=\"DO_NOT_SHOW\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLongFloat/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMemberFunctionMayBeConst/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMemberFunctionMayBeStatic/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMemberInitializersOrder/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMismatchedClassTags/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMissingIncludeGuard/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMissingKeywordThrow/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppModulePartitionWithSeveralPartitionUnits/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtAddressOfClassRValue/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtBindingRValueToLvalueReference/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtCopyElisionInCopyInitDeclarator/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtDoubleUserConversionInCopyInit/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtNotInitializedStaticConstLocalVar/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtReinterpretCastFromNullptr/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMultiCharacterLiteral/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMultiCharacterWideLiteral/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMustBePublicVirtualToImplementInterface/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMutableSpecifierOnReferenceMember/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNoDiscardExpression/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNodiscardFunctionWithoutReturnValue/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNonExceptionSafeResourceAcquisition/@EntryIndexedValue\" value=\"HINT\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNonExplicitConversionOperator/@EntryIndexedValue\" value=\"HINT\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNonExplicitConvertingConstructor/@EntryIndexedValue\" value=\"HINT\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNonInlineFunctionDefinitionInHeaderFile/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNonInlineVariableDefinitionInHeaderFile/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNotAllPathsReturnValue/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppObjectMemberMightNotBeInitialized/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppOutParameterMustBeWritten/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppParameterMayBeConst/@EntryIndexedValue\" value=\"HINT\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppParameterMayBeConstPtrOrRef/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppParameterNamesMismatch/@EntryIndexedValue\" value=\"HINT\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppParameterNeverUsed/@EntryIndexedValue\" value=\"HINT\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPassValueParameterByConstReference/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPointerConversionDropsQualifiers/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPointerToIntegralConversion/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPolymorphicClassWithNonVirtualPublicDestructor/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPossiblyErroneousEmptyStatements/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPossiblyUninitializedMember/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPossiblyUnintendedObjectSlicing/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrecompiledHeaderIsNotIncluded/@EntryIndexedValue\" value=\"ERROR\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrecompiledHeaderNotFound/@EntryIndexedValue\" value=\"ERROR\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrintfBadFormat/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrintfExtraArg/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrintfMissedArg/@EntryIndexedValue\" value=\"ERROR\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrintfRiskyFormat/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrivateSpecialMemberFunctionIsNotImplemented/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRangeBasedForIncompatibleReference/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedefinitionOfDefaultArgumentInOverrideFunction/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantAccessSpecifier/@EntryIndexedValue\" value=\"HINT\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantBaseClassAccessSpecifier/@EntryIndexedValue\" value=\"HINT\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantBaseClassInitializer/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantBooleanExpressionArgument/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantCastExpression/@EntryIndexedValue\" value=\"HINT\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantComplexityInComparison/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantConditionalExpression/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantConstSpecifier/@EntryIndexedValue\" value=\"HINT\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantControlFlowJump/@EntryIndexedValue\" value=\"HINT\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantDereferencingAndTakingAddress/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantElaboratedTypeSpecifier/@EntryIndexedValue\" value=\"HINT\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantElseKeyword/@EntryIndexedValue\" value=\"HINT\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantElseKeywordInsideCompoundStatement/@EntryIndexedValue\" value=\"HINT\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantEmptyDeclaration/@EntryIndexedValue\" value=\"HINT\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantEmptyStatement/@EntryIndexedValue\" value=\"HINT\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantExportKeyword/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantFwdClassOrEnumSpecifier/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantInlineSpecifier/@EntryIndexedValue\" value=\"HINT\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantLambdaParameterList/@EntryIndexedValue\" value=\"HINT\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantMemberInitializer/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantNamespaceDefinition/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantParentheses/@EntryIndexedValue\" value=\"HINT\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantQualifier/@EntryIndexedValue\" value=\"HINT\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantQualifierADL/@EntryIndexedValue\" value=\"DO_NOT_SHOW\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantStaticSpecifierOnMemberAllocationFunction/@EntryIndexedValue\" value=\"HINT\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantStaticSpecifierOnThreadLocalLocalVariable/@EntryIndexedValue\" value=\"HINT\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantTemplateArguments/@EntryIndexedValue\" value=\"HINT\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantTemplateKeyword/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantTypenameKeyword/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantVoidArgumentList/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantZeroInitializerInAggregateInitialization/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppReinterpretCastFromVoidPtr/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRemoveRedundantBraces/@EntryIndexedValue\" value=\"DO_NOT_SHOW\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppReplaceMemsetWithZeroInitialization/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppReplaceTieWithStructuredBinding/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppReturnNoValueInNonVoidFunction/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppSmartPointerVsMakeFunction/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppSomeObjectMembersMightNotBeInitialized/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppSpecialFunctionWithoutNoexceptSpecification/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppStaticAssertFailure/@EntryIndexedValue\" value=\"ERROR\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppStaticDataMemberInUnnamedStruct/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppStaticSpecifierOnAnonymousNamespaceMember/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppStringLiteralToCharPointerConversion/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTabsAreDisallowed/@EntryIndexedValue\" value=\"DO_NOT_SHOW\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTemplateArgumentsCanBeDeduced/@EntryIndexedValue\" value=\"HINT\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTemplateParameterNeverUsed/@EntryIndexedValue\" value=\"HINT\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTemplateParameterShadowing/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppThrowExpressionCanBeReplacedWithRethrow/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTooWideScope/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTooWideScopeInitStatement/@EntryIndexedValue\" value=\"HINT\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTypeAliasNeverUsed/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUninitializedDependentBaseClass/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUninitializedNonStaticDataMember/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnionMemberOfReferenceType/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnmatchedPragmaEndRegionDirective/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnmatchedPragmaRegionDirective/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnnamedNamespaceInHeaderFile/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnnecessaryWhitespace/@EntryIndexedValue\" value=\"DO_NOT_SHOW\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnsignedZeroComparison/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnusedIncludeDirective/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseAlgorithmWithCount/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseAssociativeContains/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseAuto/@EntryIndexedValue\" value=\"HINT\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseAutoForNumeric/@EntryIndexedValue\" value=\"HINT\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseElementsView/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseEraseAlgorithm/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseFamiliarTemplateSyntaxForGenericLambdas/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseRangeAlgorithm/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseStdSize/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseStructuredBinding/@EntryIndexedValue\" value=\"HINT\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseTypeTraitAlias/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUserDefinedLiteralSuffixDoesNotStartWithUnderscore/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUsingResultOfAssignmentAsCondition/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppVariableCanBeMadeConstexpr/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppVirtualFunctionCallInsideCtor/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppVirtualFunctionInFinalClass/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppVolatileParameterInDeclaration/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppWarningDirective/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppWrongIncludesOrder/@EntryIndexedValue\" value=\"HINT\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppWrongSlashesInIncludeDirective/@EntryIndexedValue\" value=\"HINT\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppZeroConstantCanBeReplacedWithNullptr/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=CppZeroValuedExpressionUsedAsNullPointer/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=IdentifierTypo/@EntryIndexedValue\" value=\"DO_NOT_SHOW\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=IfStdIsConstantEvaluatedCanBeReplaced/@EntryIndexedValue\" value=\"SUGGESTION\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=StdIsConstantEvaluatedWillAlwaysEvaluateToConstant/@EntryIndexedValue\" value=\"WARNING\" type=\"string\" />\n    <option name=\"/Default/CodeInspection/Highlighting/InspectionSeverities/=StringLiteralTypo/@EntryIndexedValue\" value=\"DO_NOT_SHOW\" type=\"string\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppClangFormat/EnableClangFormatSupport/@EntryValue\" value=\"false\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_ARGUMENT/@EntryValue\" value=\"true\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_BINARY_EXPRESSIONS_CHAIN/@EntryValue\" value=\"true\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_CALLS_CHAIN/@EntryValue\" value=\"false\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_EXPRESSION/@EntryValue\" value=\"false\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_EXTENDS_LIST/@EntryValue\" value=\"true\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_FOR_STMT/@EntryValue\" value=\"true\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_PARAMETER/@EntryValue\" value=\"true\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_TYPE_ARGUMENT/@EntryValue\" value=\"false\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_TYPE_PARAMETER/@EntryValue\" value=\"false\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTIPLE_DECLARATION/@EntryValue\" value=\"false\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_TERNARY/@EntryValue\" value=\"ALIGN_ALL\" type=\"string\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/ANONYMOUS_METHOD_DECLARATION_BRACES/@EntryValue\" value=\"END_OF_LINE\" type=\"string\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/BLANK_LINES_AROUND_CLASS_DEFINITION/@EntryValue\" value=\"1\" type=\"int\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/BLANK_LINES_AROUND_DECLARATIONS/@EntryValue\" value=\"0\" type=\"int\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/BLANK_LINES_AROUND_FUNCTION_DECLARATION/@EntryValue\" value=\"1\" type=\"int\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/BLANK_LINES_AROUND_FUNCTION_DEFINITION/@EntryValue\" value=\"1\" type=\"int\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/BREAK_TEMPLATE_DECLARATION/@EntryValue\" value=\"LINE_BREAK\" type=\"string\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/CASE_BLOCK_BRACES/@EntryValue\" value=\"END_OF_LINE\" type=\"string\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/CONTINUOUS_LINE_INDENT/@EntryValue\" value=\"Double\" type=\"string\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/FREE_BLOCK_BRACES/@EntryValue\" value=\"END_OF_LINE\" type=\"string\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_ACCESS_SPECIFIERS_FROM_CLASS/@EntryValue\" value=\"false\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_CASE_FROM_SWITCH/@EntryValue\" value=\"true\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_CLASS_MEMBERS_FROM_ACCESS_SPECIFIERS/@EntryValue\" value=\"true\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_COMMENT/@EntryValue\" value=\"true\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_SIZE/@EntryValue\" value=\"4\" type=\"int\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_STYLE/@EntryValue\" value=\"Space\" type=\"string\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/INITIALIZER_BRACES/@EntryValue\" value=\"END_OF_LINE_NO_SPACE\" type=\"string\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/INT_ALIGN_EQ/@EntryValue\" value=\"false\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/INVOCABLE_DECLARATION_BRACES/@EntryValue\" value=\"END_OF_LINE\" type=\"string\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/KEEP_BLANK_LINES_IN_CODE/@EntryValue\" value=\"2\" type=\"int\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/KEEP_BLANK_LINES_IN_DECLARATIONS/@EntryValue\" value=\"2\" type=\"int\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/KEEP_USER_LINEBREAKS/@EntryValue\" value=\"true\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/LINE_BREAK_AFTER_COLON_IN_MEMBER_INITIALIZER_LISTS/@EntryValue\" value=\"ON_SINGLE_LINE\" type=\"string\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/MEMBER_INITIALIZER_LIST_STYLE/@EntryValue\" value=\"DO_NOT_CHANGE\" type=\"string\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/NAMESPACE_DECLARATION_BRACES/@EntryValue\" value=\"END_OF_LINE\" type=\"string\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/NAMESPACE_INDENTATION/@EntryValue\" value=\"All\" type=\"string\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/OTHER_BRACES/@EntryValue\" value=\"END_OF_LINE\" type=\"string\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/PLACE_CATCH_ON_NEW_LINE/@EntryValue\" value=\"false\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/PLACE_ELSE_ON_NEW_LINE/@EntryValue\" value=\"false\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/PLACE_NAMESPACE_DEFINITIONS_ON_SAME_LINE/@EntryValue\" value=\"false\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/PLACE_WHILE_ON_NEW_LINE/@EntryValue\" value=\"false\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/SIMPLE_BLOCK_STYLE/@EntryValue\" value=\"DO_NOT_CHANGE\" type=\"string\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_CAST_EXPRESSION_PARENTHESES/@EntryValue\" value=\"true\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_COLON_IN_BITFIELD_DECLARATOR/@EntryValue\" value=\"true\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_COMMA_IN_TEMPLATE_ARGS/@EntryValue\" value=\"true\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_COMMA_IN_TEMPLATE_PARAMS/@EntryValue\" value=\"true\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_EXTENDS_COLON/@EntryValue\" value=\"true\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_FOR_COLON/@EntryValue\" value=\"true\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_FOR_SEMICOLON/@EntryValue\" value=\"true\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_PTR_IN_DATA_MEMBER/@EntryValue\" value=\"false\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_PTR_IN_DATA_MEMBERS/@EntryValue\" value=\"false\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_PTR_IN_METHOD/@EntryValue\" value=\"false\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_PTR_IN_NESTED_DECLARATOR/@EntryValue\" value=\"false\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_REF_IN_DATA_MEMBER/@EntryValue\" value=\"false\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_REF_IN_DATA_MEMBERS/@EntryValue\" value=\"false\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_REF_IN_METHOD/@EntryValue\" value=\"false\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_UNARY_OPERATOR/@EntryValue\" value=\"false\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_COLON_IN_BITFIELD_DECLARATOR/@EntryValue\" value=\"false\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_EXTENDS_COLON/@EntryValue\" value=\"true\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_FOR_COLON/@EntryValue\" value=\"false\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_FOR_SEMICOLON/@EntryValue\" value=\"false\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_PTR_IN_ABSTRACT_DECL/@EntryValue\" value=\"true\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_PTR_IN_DATA_MEMBER/@EntryValue\" value=\"true\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_PTR_IN_DATA_MEMBERS/@EntryValue\" value=\"true\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_PTR_IN_METHOD/@EntryValue\" value=\"true\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_REF_IN_ABSTRACT_DECL/@EntryValue\" value=\"true\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_REF_IN_DATA_MEMBER/@EntryValue\" value=\"true\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_REF_IN_DATA_MEMBERS/@EntryValue\" value=\"true\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_REF_IN_METHOD/@EntryValue\" value=\"true\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_TEMPLATE_ARGS/@EntryValue\" value=\"false\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_TEMPLATE_PARAMS/@EntryValue\" value=\"false\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BETWEEN_CLOSING_ANGLE_BRACKETS_IN_TEMPLATE_ARGS/@EntryValue\" value=\"true\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_ARRAY_ACCESS_BRACKETS/@EntryValue\" value=\"false\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_CAST_EXPRESSION_PARENTHESES/@EntryValue\" value=\"false\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_DECLARATION_PARENTHESES/@EntryValue\" value=\"false\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_EMPTY_BLOCKS/@EntryValue\" value=\"false\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_EMPTY_INITIALIZER_BRACES/@EntryValue\" value=\"false\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_EMPTY_METHOD_PARENTHESES/@EntryValue\" value=\"false\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_EMPTY_TEMPLATE_PARAMS/@EntryValue\" value=\"false\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_INITIALIZER_BRACES/@EntryValue\" value=\"false\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_TEMPLATE_ARGS/@EntryValue\" value=\"false\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_TEMPLATE_PARAMS/@EntryValue\" value=\"false\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/SPECIAL_ELSE_IF_TREATMENT/@EntryValue\" value=\"true\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/TAB_WIDTH/@EntryValue\" value=\"4\" type=\"int\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/TYPE_DECLARATION_BRACES/@EntryValue\" value=\"END_OF_LINE\" type=\"string\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_AFTER_BINARY_OPSIGN/@EntryValue\" value=\"true\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_AFTER_DECLARATION_LPAR/@EntryValue\" value=\"false\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_AFTER_INVOCATION_LPAR/@EntryValue\" value=\"false\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_ARGUMENTS_STYLE/@EntryValue\" value=\"WRAP_IF_LONG\" type=\"string\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_BEFORE_DECLARATION_LPAR/@EntryValue\" value=\"false\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_BEFORE_DECLARATION_RPAR/@EntryValue\" value=\"false\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_BEFORE_INVOCATION_LPAR/@EntryValue\" value=\"false\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_BEFORE_INVOCATION_RPAR/@EntryValue\" value=\"false\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_BEFORE_TERNARY_OPSIGNS/@EntryValue\" value=\"true\" type=\"bool\" />\n    <option name=\"/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_PARAMETERS_STYLE/@EntryValue\" value=\"WRAP_IF_LONG\" type=\"string\" />\n    <option name=\"/Default/CodeStyle/EditorConfig/EnableClangFormatSupport/@EntryValue\" value=\"false\" type=\"bool\" />\n  </component>\n</project>"
  },
  {
    "path": "crates/hyprland-plugin/plugin/.idea/misc.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"CidrRootsConfiguration\">\n    <sourceRoots>\n      <file path=\"$PROJECT_DIR$/src\" />\n    </sourceRoots>\n    <excludeRoots>\n      <file path=\"$PROJECT_DIR$/out\" />\n    </excludeRoots>\n  </component>\n  <component name=\"ExternalStorageConfigurationManager\" enabled=\"true\" />\n  <component name=\"MakefileSettings\">\n    <option name=\"linkedExternalProjectsSettings\">\n      <MakefileProjectSettings>\n        <option name=\"externalProjectPath\" value=\"$PROJECT_DIR$\" />\n        <option name=\"modules\">\n          <set>\n            <option value=\"$PROJECT_DIR$\" />\n          </set>\n        </option>\n        <option name=\"version\" value=\"2\" />\n      </MakefileProjectSettings>\n    </option>\n  </component>\n  <component name=\"MakefileWorkspace\" PROJECT_DIR=\"$PROJECT_DIR$\" />\n</project>"
  },
  {
    "path": "crates/hyprland-plugin/plugin/.idea/vcs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"VcsDirectoryMappings\">\n    <mapping directory=\"$PROJECT_DIR$/../../..\" vcs=\"Git\" />\n  </component>\n</project>"
  },
  {
    "path": "crates/hyprland-plugin/plugin/.idea/workspace.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"AutoImportSettings\">\n    <option name=\"autoReloadType\" value=\"SELECTIVE\" />\n  </component>\n  <component name=\"BackendCodeEditorMiscSettings\">\n    <option name=\"/Default/Housekeeping/FeatureSuggestion/FeatureSuggestionManager/DisabledSuggesters/=SwitchToGoToActionSuggester/@EntryIndexedValue\" value=\"true\" type=\"bool\" />\n    <option name=\"/Default/Housekeeping/GlobalSettingsUpgraded/IsUpgraded/@EntryValue\" value=\"true\" type=\"bool\" />\n    <option name=\"/Default/Housekeeping/LiveTemplatesHousekeeping/HotspotSessionHintIsShown/@EntryValue\" value=\"true\" type=\"bool\" />\n    <option name=\"/Default/Housekeeping/OptionsDialog/SelectedPageId/@EntryValue\" value=\"CppIncludesOrderOptions\" type=\"string\" />\n    <option name=\"/Default/RiderDebugger/RiderRestoreDecompile/RestoreDecompileSetting/@EntryValue\" value=\"false\" type=\"bool\" />\n  </component>\n  <component name=\"CMakeProjectFlavorService\">\n    <option name=\"flavorId\" value=\"CMakePlainProjectFlavor\" />\n  </component>\n  <component name=\"CMakeRunConfigurationManager\">\n    <generated>\n      <config projectName=\"plugin\" targetName=\"build\" />\n      <config projectName=\"plugin\" targetName=\"combine\" />\n      <config projectName=\"plugin\" targetName=\"test-combined\" />\n      <config projectName=\"plugin\" targetName=\"all\" />\n      <config projectName=\"plugin\" targetName=\"copy-defs\" />\n      <config projectName=\"plugin\" targetName=\"build-combined\" />\n      <config projectName=\"plugin\" targetName=\"hyprshell.so\" />\n      <config projectName=\"plugin\" targetName=\"prepare-combined\" />\n      <config projectName=\"plugin\" targetName=\"test\" />\n    </generated>\n  </component>\n  <component name=\"CMakeSettings\">\n    <configurations>\n      <configuration PROFILE_NAME=\"Debug\" ENABLED=\"true\" CONFIG_NAME=\"Debug\" />\n    </configurations>\n  </component>\n  <component name=\"ChangeListManager\">\n    <list default=\"true\" id=\"841e5689-e8a3-4a78-b7a4-76d1dbd901c1\" name=\"Changes\" comment=\"fix: cancel key events when opening hyprshell overview and switch\">\n      <change beforePath=\"$PROJECT_DIR$/.idea/workspace.xml\" beforeDir=\"false\" afterPath=\"$PROJECT_DIR$/.idea/workspace.xml\" afterDir=\"false\" />\n      <change beforePath=\"$PROJECT_DIR$/Makefile\" beforeDir=\"false\" afterPath=\"$PROJECT_DIR$/Makefile\" afterDir=\"false\" />\n      <change beforePath=\"$PROJECT_DIR$/src/defs-test.h\" beforeDir=\"false\" afterPath=\"$PROJECT_DIR$/src/defs-test.h\" afterDir=\"false\" />\n      <change beforePath=\"$PROJECT_DIR$/src/defs.h\" beforeDir=\"false\" afterPath=\"$PROJECT_DIR$/src/defs.h\" afterDir=\"false\" />\n      <change beforePath=\"$PROJECT_DIR$/src/globals.h\" beforeDir=\"false\" afterPath=\"$PROJECT_DIR$/src/globals.h\" afterDir=\"false\" />\n      <change beforePath=\"$PROJECT_DIR$/src/handlers.h\" beforeDir=\"false\" afterPath=\"$PROJECT_DIR$/src/handlers.h\" afterDir=\"false\" />\n      <change beforePath=\"$PROJECT_DIR$/src/init.cpp\" beforeDir=\"false\" afterPath=\"$PROJECT_DIR$/src/init.cpp\" afterDir=\"false\" />\n      <change beforePath=\"$PROJECT_DIR$/src/key-press.cpp\" beforeDir=\"false\" afterPath=\"$PROJECT_DIR$/src/key-press.cpp\" afterDir=\"false\" />\n      <change beforePath=\"$PROJECT_DIR$/src/keyboard-focus.cpp\" beforeDir=\"false\" afterPath=\"$PROJECT_DIR$/src/keyboard-focus.cpp\" afterDir=\"false\" />\n      <change beforePath=\"$PROJECT_DIR$/src/layer-change.cpp\" beforeDir=\"false\" afterPath=\"$PROJECT_DIR$/src/layer-change.cpp\" afterDir=\"false\" />\n      <change beforePath=\"$PROJECT_DIR$/src/mouse-button.cpp\" beforeDir=\"false\" afterPath=\"$PROJECT_DIR$/src/mouse-button.cpp\" afterDir=\"false\" />\n    </list>\n    <option name=\"SHOW_DIALOG\" value=\"false\" />\n    <option name=\"HIGHLIGHT_CONFLICTS\" value=\"true\" />\n    <option name=\"HIGHLIGHT_NON_ACTIVE_CHANGELIST\" value=\"false\" />\n    <option name=\"LAST_RESOLUTION\" value=\"IGNORE\" />\n  </component>\n  <component name=\"ChangesViewManager\">\n    <option name=\"groupingKeys\">\n      <option value=\"directory\" />\n      <option value=\"repository\" />\n    </option>\n  </component>\n  <component name=\"ClangdSettings\">\n    <option name=\"formatViaClangd\" value=\"false\" />\n  </component>\n  <component name=\"ExternalProjectsData\">\n    <projectState path=\"$PROJECT_DIR$\">\n      <ProjectState />\n    </projectState>\n  </component>\n  <component name=\"Git.Settings\">\n    <option name=\"RECENT_GIT_ROOT_PATH\" value=\"$PROJECT_DIR$/../../..\" />\n  </component>\n  <component name=\"HighlightingSettingsPerFile\">\n    <setting file=\"file://$PROJECT_DIR$/src/defs-test.h\" root0=\"FORCE_HIGHLIGHTING\" />\n    <setting file=\"file://$PROJECT_DIR$/src/defs.h\" root0=\"FORCE_HIGHLIGHTING\" />\n    <setting file=\"file://$PROJECT_DIR$/src/globals.h\" root0=\"FORCE_HIGHLIGHTING\" />\n    <setting file=\"mock:///home/user/RustroverProjects/hyprshell/hyprland-plugin/plugin/src/handlers.h\" root0=\"SKIP_HIGHLIGHTING\" />\n    <setting file=\"file://$PROJECT_DIR$/src/handlers.h\" root0=\"FORCE_HIGHLIGHTING\" />\n    <setting file=\"file://$PROJECT_DIR$/src/init.cpp\" root0=\"FORCE_HIGHLIGHTING\" />\n    <setting file=\"mock:///home/user/RustroverProjects/hyprshell/hyprland-plugin/plugin/src/init.cpp\" root0=\"SKIP_HIGHLIGHTING\" />\n    <setting file=\"mock:///home/user/RustroverProjects/hyprshell/hyprland-plugin/plugin/src/key-press.cpp\" root0=\"SKIP_HIGHLIGHTING\" />\n    <setting file=\"file://$PROJECT_DIR$/src/key-press.cpp\" root0=\"FORCE_HIGHLIGHTING\" />\n    <setting file=\"file://$PROJECT_DIR$/src/keyboard-focus.cpp\" root0=\"FORCE_HIGHLIGHTING\" />\n    <setting file=\"file://$PROJECT_DIR$/src/layer-change.cpp\" root0=\"FORCE_HIGHLIGHTING\" />\n    <setting file=\"file://$PROJECT_DIR$/src/mouse-button.cpp\" root0=\"FORCE_HIGHLIGHTING\" />\n    <setting file=\"file://$PROJECT_DIR$/src/send.cpp\" root0=\"FORCE_HIGHLIGHTING\" />\n    <setting file=\"file://$PROJECT_DIR$/src/send.h\" root0=\"FORCE_HIGHLIGHTING\" />\n  </component>\n  <component name=\"MacroExpansionManager\">\n    <option name=\"directoryName\" value=\"lmhBF7ce\" />\n  </component>\n  <component name=\"MakefileLocalSettings\">\n    <option name=\"availableProjects\">\n      <map>\n        <entry>\n          <key>\n            <ExternalProjectPojo>\n              <option name=\"name\" value=\"plugin\" />\n              <option name=\"path\" value=\"$PROJECT_DIR$\" />\n            </ExternalProjectPojo>\n          </key>\n          <value>\n            <list>\n              <ExternalProjectPojo>\n                <option name=\"name\" value=\"plugin\" />\n                <option name=\"path\" value=\"$PROJECT_DIR$\" />\n              </ExternalProjectPojo>\n            </list>\n          </value>\n        </entry>\n      </map>\n    </option>\n    <option name=\"projectSyncType\">\n      <map>\n        <entry key=\"$PROJECT_DIR$\" value=\"RE_IMPORT\" />\n      </map>\n    </option>\n  </component>\n  <component name=\"ProjectApplicationVersion\">\n    <option name=\"ide\" value=\"CLion\" />\n    <option name=\"majorVersion\" value=\"2025\" />\n    <option name=\"minorVersion\" value=\"2\" />\n    <option name=\"productBranch\" value=\"Classic\" />\n  </component>\n  <component name=\"ProjectColorInfo\">{\n  &quot;associatedIndex&quot;: 4\n}</component>\n  <component name=\"ProjectId\" id=\"31hh8wRG1ll2gknzDoNDxs88kBO\" />\n  <component name=\"ProjectViewState\">\n    <option name=\"hideEmptyMiddlePackages\" value=\"true\" />\n    <option name=\"showLibraryContents\" value=\"true\" />\n  </component>\n  <component name=\"PropertiesComponent\"><![CDATA[{\n  \"keyToString\": {\n    \"Makefile Target.all.executor\": \"Run\",\n    \"Makefile Target.build.executor\": \"Run\",\n    \"ModuleVcsDetector.initialDetectionPerformed\": \"true\",\n    \"RunOnceActivity.RadMigrateCodeStyle\": \"true\",\n    \"RunOnceActivity.ShowReadmeOnStart\": \"true\",\n    \"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252\": \"true\",\n    \"RunOnceActivity.cidr.known.project.marker\": \"true\",\n    \"RunOnceActivity.git.unshallow\": \"true\",\n    \"RunOnceActivity.readMode.enableVisualFormatting\": \"true\",\n    \"RunOnceActivity.typescript.service.memoryLimit.init\": \"true\",\n    \"SHARE_PROJECT_CONFIGURATION_FILES\": \"true\",\n    \"cf.first.check.clang-format\": \"false\",\n    \"cidr.known.project.marker\": \"true\",\n    \"git-widget-placeholder\": \"hyprshell-conf-editor\",\n    \"last_opened_file_path\": \"/home/user/RustroverProjects/hyprshell/crates/hyprland-plugin/plugin\",\n    \"node.js.detected.package.eslint\": \"true\",\n    \"node.js.detected.package.tslint\": \"true\",\n    \"node.js.selected.package.eslint\": \"(autodetect)\",\n    \"node.js.selected.package.tslint\": \"(autodetect)\",\n    \"nodejs_package_manager_path\": \"npm\",\n    \"settings.editor.selected.configurable\": \"project.propVCSSupport.DirectoryMappings\",\n    \"vue.rearranger.settings.migration\": \"true\"\n  }\n}]]></component>\n  <component name=\"RunManager\" selected=\"Makefile Target.build-combined\">\n    <configuration default=\"true\" type=\"CLionExternalRunConfiguration\" factoryName=\"Application\" REDIRECT_INPUT=\"false\" ELEVATE=\"false\" USE_EXTERNAL_CONSOLE=\"false\" EMULATE_TERMINAL=\"false\" PASS_PARENT_ENVS_2=\"true\">\n      <method v=\"2\">\n        <option name=\"CLION.EXTERNAL.BUILD\" enabled=\"true\" />\n      </method>\n    </configuration>\n    <configuration name=\"all\" type=\"CLionNativeAppRunConfigurationType\" REDIRECT_INPUT=\"false\" ELEVATE=\"false\" USE_EXTERNAL_CONSOLE=\"false\" EMULATE_TERMINAL=\"false\" PASS_PARENT_ENVS_2=\"true\" PROJECT_NAME=\"plugin\" TARGET_NAME=\"all\" CONFIG_NAME=\"all\" version=\"1\">\n      <method v=\"2\">\n        <option name=\"CLION.COMPOUND.BUILD\" enabled=\"true\" />\n      </method>\n    </configuration>\n    <configuration name=\"all\" type=\"CLionNativeAppRunConfigurationType\" REDIRECT_INPUT=\"false\" ELEVATE=\"false\" USE_EXTERNAL_CONSOLE=\"false\" EMULATE_TERMINAL=\"false\" PASS_PARENT_ENVS_2=\"true\" PROJECT_NAME=\"plugin\" TARGET_NAME=\"all\" CONFIG_NAME=\"all\" version=\"1\">\n      <method v=\"2\">\n        <option name=\"CLION.COMPOUND.BUILD\" enabled=\"true\" />\n      </method>\n    </configuration>\n    <configuration name=\"all\" type=\"CLionNativeAppRunConfigurationType\" REDIRECT_INPUT=\"false\" ELEVATE=\"false\" USE_EXTERNAL_CONSOLE=\"false\" EMULATE_TERMINAL=\"false\" PASS_PARENT_ENVS_2=\"true\" PROJECT_NAME=\"plugin\" TARGET_NAME=\"all\" CONFIG_NAME=\"all\" version=\"1\">\n      <method v=\"2\">\n        <option name=\"CLION.COMPOUND.BUILD\" enabled=\"true\" />\n      </method>\n    </configuration>\n    <configuration name=\"all\" type=\"CLionNativeAppRunConfigurationType\" REDIRECT_INPUT=\"false\" ELEVATE=\"false\" USE_EXTERNAL_CONSOLE=\"false\" EMULATE_TERMINAL=\"false\" PASS_PARENT_ENVS_2=\"true\" PROJECT_NAME=\"plugin\" TARGET_NAME=\"all\" CONFIG_NAME=\"all\" version=\"1\">\n      <method v=\"2\">\n        <option name=\"CLION.COMPOUND.BUILD\" enabled=\"true\" />\n      </method>\n    </configuration>\n    <configuration name=\"build-combined\" type=\"CLionNativeAppRunConfigurationType\" REDIRECT_INPUT=\"false\" ELEVATE=\"false\" USE_EXTERNAL_CONSOLE=\"false\" EMULATE_TERMINAL=\"false\" PASS_PARENT_ENVS_2=\"true\" PROJECT_NAME=\"plugin\" TARGET_NAME=\"build-combined\" CONFIG_NAME=\"build-combined\" version=\"1\">\n      <method v=\"2\">\n        <option name=\"CLION.COMPOUND.BUILD\" enabled=\"true\" />\n      </method>\n    </configuration>\n    <configuration name=\"build-combined\" type=\"CLionNativeAppRunConfigurationType\" REDIRECT_INPUT=\"false\" ELEVATE=\"false\" USE_EXTERNAL_CONSOLE=\"false\" EMULATE_TERMINAL=\"false\" PASS_PARENT_ENVS_2=\"true\" PROJECT_NAME=\"plugin\" TARGET_NAME=\"build-combined\" CONFIG_NAME=\"build-combined\" version=\"1\">\n      <method v=\"2\">\n        <option name=\"CLION.COMPOUND.BUILD\" enabled=\"true\" />\n      </method>\n    </configuration>\n    <configuration name=\"build-combined\" type=\"CLionNativeAppRunConfigurationType\" REDIRECT_INPUT=\"false\" ELEVATE=\"false\" USE_EXTERNAL_CONSOLE=\"false\" EMULATE_TERMINAL=\"false\" PASS_PARENT_ENVS_2=\"true\" PROJECT_NAME=\"plugin\" TARGET_NAME=\"build-combined\" CONFIG_NAME=\"build-combined\" version=\"1\">\n      <method v=\"2\">\n        <option name=\"CLION.COMPOUND.BUILD\" enabled=\"true\" />\n      </method>\n    </configuration>\n    <configuration name=\"build-combined\" type=\"CLionNativeAppRunConfigurationType\" REDIRECT_INPUT=\"false\" ELEVATE=\"false\" USE_EXTERNAL_CONSOLE=\"false\" EMULATE_TERMINAL=\"false\" PASS_PARENT_ENVS_2=\"true\" PROJECT_NAME=\"plugin\" TARGET_NAME=\"build-combined\" CONFIG_NAME=\"build-combined\" version=\"1\">\n      <method v=\"2\">\n        <option name=\"CLION.COMPOUND.BUILD\" enabled=\"true\" />\n      </method>\n    </configuration>\n    <configuration name=\"build\" type=\"CLionNativeAppRunConfigurationType\" REDIRECT_INPUT=\"false\" ELEVATE=\"false\" USE_EXTERNAL_CONSOLE=\"false\" EMULATE_TERMINAL=\"false\" PASS_PARENT_ENVS_2=\"true\" PROJECT_NAME=\"plugin\" TARGET_NAME=\"build\" CONFIG_NAME=\"build\" version=\"1\">\n      <method v=\"2\">\n        <option name=\"CLION.COMPOUND.BUILD\" enabled=\"true\" />\n      </method>\n    </configuration>\n    <configuration name=\"build\" type=\"CLionNativeAppRunConfigurationType\" REDIRECT_INPUT=\"false\" ELEVATE=\"false\" USE_EXTERNAL_CONSOLE=\"false\" EMULATE_TERMINAL=\"false\" PASS_PARENT_ENVS_2=\"true\" PROJECT_NAME=\"plugin\" TARGET_NAME=\"build\" CONFIG_NAME=\"build\" version=\"1\">\n      <method v=\"2\">\n        <option name=\"CLION.COMPOUND.BUILD\" enabled=\"true\" />\n      </method>\n    </configuration>\n    <configuration name=\"build\" type=\"CLionNativeAppRunConfigurationType\" REDIRECT_INPUT=\"false\" ELEVATE=\"false\" USE_EXTERNAL_CONSOLE=\"false\" EMULATE_TERMINAL=\"false\" PASS_PARENT_ENVS_2=\"true\" PROJECT_NAME=\"plugin\" TARGET_NAME=\"build\" CONFIG_NAME=\"build\" version=\"1\">\n      <method v=\"2\">\n        <option name=\"CLION.COMPOUND.BUILD\" enabled=\"true\" />\n      </method>\n    </configuration>\n    <configuration name=\"build\" type=\"CLionNativeAppRunConfigurationType\" REDIRECT_INPUT=\"false\" ELEVATE=\"false\" USE_EXTERNAL_CONSOLE=\"false\" EMULATE_TERMINAL=\"false\" PASS_PARENT_ENVS_2=\"true\" PROJECT_NAME=\"plugin\" TARGET_NAME=\"build\" CONFIG_NAME=\"build\" version=\"1\">\n      <method v=\"2\">\n        <option name=\"CLION.COMPOUND.BUILD\" enabled=\"true\" />\n      </method>\n    </configuration>\n    <configuration name=\"combine\" type=\"CLionNativeAppRunConfigurationType\" REDIRECT_INPUT=\"false\" ELEVATE=\"false\" USE_EXTERNAL_CONSOLE=\"false\" EMULATE_TERMINAL=\"false\" PASS_PARENT_ENVS_2=\"true\" PROJECT_NAME=\"plugin\" TARGET_NAME=\"combine\" CONFIG_NAME=\"combine\" version=\"1\">\n      <method v=\"2\">\n        <option name=\"CLION.COMPOUND.BUILD\" enabled=\"true\" />\n      </method>\n    </configuration>\n    <configuration name=\"combine\" type=\"CLionNativeAppRunConfigurationType\" REDIRECT_INPUT=\"false\" ELEVATE=\"false\" USE_EXTERNAL_CONSOLE=\"false\" EMULATE_TERMINAL=\"false\" PASS_PARENT_ENVS_2=\"true\" PROJECT_NAME=\"plugin\" TARGET_NAME=\"combine\" CONFIG_NAME=\"combine\" version=\"1\">\n      <method v=\"2\">\n        <option name=\"CLION.COMPOUND.BUILD\" enabled=\"true\" />\n      </method>\n    </configuration>\n    <configuration name=\"combine\" type=\"CLionNativeAppRunConfigurationType\" REDIRECT_INPUT=\"false\" ELEVATE=\"false\" USE_EXTERNAL_CONSOLE=\"false\" EMULATE_TERMINAL=\"false\" PASS_PARENT_ENVS_2=\"true\" PROJECT_NAME=\"plugin\" TARGET_NAME=\"combine\" CONFIG_NAME=\"combine\" version=\"1\">\n      <method v=\"2\">\n        <option name=\"CLION.COMPOUND.BUILD\" enabled=\"true\" />\n      </method>\n    </configuration>\n    <configuration name=\"combine\" type=\"CLionNativeAppRunConfigurationType\" REDIRECT_INPUT=\"false\" ELEVATE=\"false\" USE_EXTERNAL_CONSOLE=\"false\" EMULATE_TERMINAL=\"false\" PASS_PARENT_ENVS_2=\"true\" PROJECT_NAME=\"plugin\" TARGET_NAME=\"combine\" CONFIG_NAME=\"combine\" version=\"1\">\n      <method v=\"2\">\n        <option name=\"CLION.COMPOUND.BUILD\" enabled=\"true\" />\n      </method>\n    </configuration>\n    <configuration name=\"copy-defs\" type=\"CLionNativeAppRunConfigurationType\" REDIRECT_INPUT=\"false\" ELEVATE=\"false\" USE_EXTERNAL_CONSOLE=\"false\" EMULATE_TERMINAL=\"false\" PASS_PARENT_ENVS_2=\"true\" PROJECT_NAME=\"plugin\" TARGET_NAME=\"copy-defs\" CONFIG_NAME=\"copy-defs\" version=\"1\">\n      <method v=\"2\">\n        <option name=\"CLION.COMPOUND.BUILD\" enabled=\"true\" />\n      </method>\n    </configuration>\n    <configuration name=\"copy-defs\" type=\"CLionNativeAppRunConfigurationType\" REDIRECT_INPUT=\"false\" ELEVATE=\"false\" USE_EXTERNAL_CONSOLE=\"false\" EMULATE_TERMINAL=\"false\" PASS_PARENT_ENVS_2=\"true\" PROJECT_NAME=\"plugin\" TARGET_NAME=\"copy-defs\" CONFIG_NAME=\"copy-defs\" version=\"1\">\n      <method v=\"2\">\n        <option name=\"CLION.COMPOUND.BUILD\" enabled=\"true\" />\n      </method>\n    </configuration>\n    <configuration name=\"copy-defs\" type=\"CLionNativeAppRunConfigurationType\" REDIRECT_INPUT=\"false\" ELEVATE=\"false\" USE_EXTERNAL_CONSOLE=\"false\" EMULATE_TERMINAL=\"false\" PASS_PARENT_ENVS_2=\"true\" PROJECT_NAME=\"plugin\" TARGET_NAME=\"copy-defs\" CONFIG_NAME=\"copy-defs\" version=\"1\">\n      <method v=\"2\">\n        <option name=\"CLION.COMPOUND.BUILD\" enabled=\"true\" />\n      </method>\n    </configuration>\n    <configuration name=\"copy-defs\" type=\"CLionNativeAppRunConfigurationType\" REDIRECT_INPUT=\"false\" ELEVATE=\"false\" USE_EXTERNAL_CONSOLE=\"false\" EMULATE_TERMINAL=\"false\" PASS_PARENT_ENVS_2=\"true\" PROJECT_NAME=\"plugin\" TARGET_NAME=\"copy-defs\" CONFIG_NAME=\"copy-defs\" version=\"1\">\n      <method v=\"2\">\n        <option name=\"CLION.COMPOUND.BUILD\" enabled=\"true\" />\n      </method>\n    </configuration>\n    <configuration name=\"hyprshell.so\" type=\"CLionNativeAppRunConfigurationType\" REDIRECT_INPUT=\"false\" ELEVATE=\"false\" USE_EXTERNAL_CONSOLE=\"false\" EMULATE_TERMINAL=\"false\" PASS_PARENT_ENVS_2=\"true\" PROJECT_NAME=\"plugin\" TARGET_NAME=\"hyprshell.so\" CONFIG_NAME=\"hyprshell.so\" version=\"1\">\n      <method v=\"2\">\n        <option name=\"CLION.COMPOUND.BUILD\" enabled=\"true\" />\n      </method>\n    </configuration>\n    <configuration name=\"hyprshell.so\" type=\"CLionNativeAppRunConfigurationType\" REDIRECT_INPUT=\"false\" ELEVATE=\"false\" USE_EXTERNAL_CONSOLE=\"false\" EMULATE_TERMINAL=\"false\" PASS_PARENT_ENVS_2=\"true\" PROJECT_NAME=\"plugin\" TARGET_NAME=\"hyprshell.so\" CONFIG_NAME=\"hyprshell.so\" version=\"1\">\n      <method v=\"2\">\n        <option name=\"CLION.COMPOUND.BUILD\" enabled=\"true\" />\n      </method>\n    </configuration>\n    <configuration name=\"prepare-combined\" type=\"CLionNativeAppRunConfigurationType\" REDIRECT_INPUT=\"false\" ELEVATE=\"false\" USE_EXTERNAL_CONSOLE=\"false\" EMULATE_TERMINAL=\"false\" PASS_PARENT_ENVS_2=\"true\" PROJECT_NAME=\"plugin\" TARGET_NAME=\"prepare-combined\" CONFIG_NAME=\"prepare-combined\" version=\"1\">\n      <method v=\"2\">\n        <option name=\"CLION.COMPOUND.BUILD\" enabled=\"true\" />\n      </method>\n    </configuration>\n    <configuration name=\"prepare-combined\" type=\"CLionNativeAppRunConfigurationType\" REDIRECT_INPUT=\"false\" ELEVATE=\"false\" USE_EXTERNAL_CONSOLE=\"false\" EMULATE_TERMINAL=\"false\" PASS_PARENT_ENVS_2=\"true\" PROJECT_NAME=\"plugin\" TARGET_NAME=\"prepare-combined\" CONFIG_NAME=\"prepare-combined\" version=\"1\">\n      <method v=\"2\">\n        <option name=\"CLION.COMPOUND.BUILD\" enabled=\"true\" />\n      </method>\n    </configuration>\n    <configuration name=\"prepare-combined\" type=\"CLionNativeAppRunConfigurationType\" REDIRECT_INPUT=\"false\" ELEVATE=\"false\" USE_EXTERNAL_CONSOLE=\"false\" EMULATE_TERMINAL=\"false\" PASS_PARENT_ENVS_2=\"true\" PROJECT_NAME=\"plugin\" TARGET_NAME=\"prepare-combined\" CONFIG_NAME=\"prepare-combined\" version=\"1\">\n      <method v=\"2\">\n        <option name=\"CLION.COMPOUND.BUILD\" enabled=\"true\" />\n      </method>\n    </configuration>\n    <configuration name=\"prepare-combined\" type=\"CLionNativeAppRunConfigurationType\" REDIRECT_INPUT=\"false\" ELEVATE=\"false\" USE_EXTERNAL_CONSOLE=\"false\" EMULATE_TERMINAL=\"false\" PASS_PARENT_ENVS_2=\"true\" PROJECT_NAME=\"plugin\" TARGET_NAME=\"prepare-combined\" CONFIG_NAME=\"prepare-combined\" version=\"1\">\n      <method v=\"2\">\n        <option name=\"CLION.COMPOUND.BUILD\" enabled=\"true\" />\n      </method>\n    </configuration>\n    <configuration name=\"test-combined\" type=\"CLionNativeAppRunConfigurationType\" REDIRECT_INPUT=\"false\" ELEVATE=\"false\" USE_EXTERNAL_CONSOLE=\"false\" EMULATE_TERMINAL=\"false\" PASS_PARENT_ENVS_2=\"true\" PROJECT_NAME=\"plugin\" TARGET_NAME=\"test-combined\" CONFIG_NAME=\"test-combined\" version=\"1\">\n      <method v=\"2\">\n        <option name=\"CLION.COMPOUND.BUILD\" enabled=\"true\" />\n      </method>\n    </configuration>\n    <configuration name=\"test-combined\" type=\"CLionNativeAppRunConfigurationType\" REDIRECT_INPUT=\"false\" ELEVATE=\"false\" USE_EXTERNAL_CONSOLE=\"false\" EMULATE_TERMINAL=\"false\" PASS_PARENT_ENVS_2=\"true\" PROJECT_NAME=\"plugin\" TARGET_NAME=\"test-combined\" CONFIG_NAME=\"test-combined\" version=\"1\">\n      <method v=\"2\">\n        <option name=\"CLION.COMPOUND.BUILD\" enabled=\"true\" />\n      </method>\n    </configuration>\n    <configuration name=\"test-combined\" type=\"CLionNativeAppRunConfigurationType\" REDIRECT_INPUT=\"false\" ELEVATE=\"false\" USE_EXTERNAL_CONSOLE=\"false\" EMULATE_TERMINAL=\"false\" PASS_PARENT_ENVS_2=\"true\" PROJECT_NAME=\"plugin\" TARGET_NAME=\"test-combined\" CONFIG_NAME=\"test-combined\" version=\"1\">\n      <method v=\"2\">\n        <option name=\"CLION.COMPOUND.BUILD\" enabled=\"true\" />\n      </method>\n    </configuration>\n    <configuration name=\"test\" type=\"CLionNativeAppRunConfigurationType\" REDIRECT_INPUT=\"false\" ELEVATE=\"false\" USE_EXTERNAL_CONSOLE=\"false\" EMULATE_TERMINAL=\"false\" PASS_PARENT_ENVS_2=\"true\" PROJECT_NAME=\"plugin\" TARGET_NAME=\"test\" CONFIG_NAME=\"test\" version=\"1\">\n      <method v=\"2\">\n        <option name=\"CLION.COMPOUND.BUILD\" enabled=\"true\" />\n      </method>\n    </configuration>\n    <configuration name=\"test\" type=\"CLionNativeAppRunConfigurationType\" REDIRECT_INPUT=\"false\" ELEVATE=\"false\" USE_EXTERNAL_CONSOLE=\"false\" EMULATE_TERMINAL=\"false\" PASS_PARENT_ENVS_2=\"true\" PROJECT_NAME=\"plugin\" TARGET_NAME=\"test\" CONFIG_NAME=\"test\" version=\"1\">\n      <method v=\"2\">\n        <option name=\"CLION.COMPOUND.BUILD\" enabled=\"true\" />\n      </method>\n    </configuration>\n    <configuration name=\"test\" type=\"CLionNativeAppRunConfigurationType\" REDIRECT_INPUT=\"false\" ELEVATE=\"false\" USE_EXTERNAL_CONSOLE=\"false\" EMULATE_TERMINAL=\"false\" PASS_PARENT_ENVS_2=\"true\" PROJECT_NAME=\"plugin\" TARGET_NAME=\"test\" CONFIG_NAME=\"test\" version=\"1\">\n      <method v=\"2\">\n        <option name=\"CLION.COMPOUND.BUILD\" enabled=\"true\" />\n      </method>\n    </configuration>\n    <configuration name=\"test\" type=\"CLionNativeAppRunConfigurationType\" REDIRECT_INPUT=\"false\" ELEVATE=\"false\" USE_EXTERNAL_CONSOLE=\"false\" EMULATE_TERMINAL=\"false\" PASS_PARENT_ENVS_2=\"true\" PROJECT_NAME=\"plugin\" TARGET_NAME=\"test\" CONFIG_NAME=\"test\" version=\"1\">\n      <method v=\"2\">\n        <option name=\"CLION.COMPOUND.BUILD\" enabled=\"true\" />\n      </method>\n    </configuration>\n    <configuration name=\"all\" type=\"MAKEFILE_TARGET_RUN_CONFIGURATION\" factoryName=\"Makefile\" temporary=\"true\">\n      <makefile filename=\"$PROJECT_DIR$/Makefile\" target=\"all\" workingDirectory=\"\" arguments=\"\">\n        <envs />\n      </makefile>\n      <method v=\"2\" />\n    </configuration>\n    <configuration name=\"all\" type=\"MAKEFILE_TARGET_RUN_CONFIGURATION\" factoryName=\"Makefile\" temporary=\"true\">\n      <makefile filename=\"$PROJECT_DIR$/Makefile\" target=\"all\" workingDirectory=\"\" arguments=\"\">\n        <envs />\n      </makefile>\n      <method v=\"2\" />\n    </configuration>\n    <configuration name=\"all\" type=\"MAKEFILE_TARGET_RUN_CONFIGURATION\" factoryName=\"Makefile\" temporary=\"true\">\n      <makefile filename=\"$PROJECT_DIR$/Makefile\" target=\"all\" workingDirectory=\"\" arguments=\"\">\n        <envs />\n      </makefile>\n      <method v=\"2\" />\n    </configuration>\n    <configuration name=\"all\" type=\"MAKEFILE_TARGET_RUN_CONFIGURATION\" factoryName=\"Makefile\" temporary=\"true\">\n      <makefile filename=\"$PROJECT_DIR$/Makefile\" target=\"all\" workingDirectory=\"\" arguments=\"\">\n        <envs />\n      </makefile>\n      <method v=\"2\" />\n    </configuration>\n    <configuration name=\"build-combined\" type=\"MAKEFILE_TARGET_RUN_CONFIGURATION\" factoryName=\"Makefile\" temporary=\"true\">\n      <makefile filename=\"$PROJECT_DIR$/Makefile\" target=\"build-combined\" workingDirectory=\"\" arguments=\"\">\n        <envs />\n      </makefile>\n      <method v=\"2\" />\n    </configuration>\n    <configuration name=\"build-combined\" type=\"MAKEFILE_TARGET_RUN_CONFIGURATION\" factoryName=\"Makefile\" temporary=\"true\">\n      <makefile filename=\"$PROJECT_DIR$/Makefile\" target=\"build-combined\" workingDirectory=\"\" arguments=\"\">\n        <envs />\n      </makefile>\n      <method v=\"2\" />\n    </configuration>\n    <configuration name=\"build\" type=\"MAKEFILE_TARGET_RUN_CONFIGURATION\" factoryName=\"Makefile\" temporary=\"true\">\n      <makefile filename=\"$PROJECT_DIR$/Makefile\" target=\"build\" workingDirectory=\"\" arguments=\"\">\n        <envs />\n      </makefile>\n      <method v=\"2\" />\n    </configuration>\n    <configuration name=\"build\" type=\"MAKEFILE_TARGET_RUN_CONFIGURATION\" factoryName=\"Makefile\" temporary=\"true\">\n      <makefile filename=\"$PROJECT_DIR$/Makefile\" target=\"build\" workingDirectory=\"\" arguments=\"\">\n        <envs />\n      </makefile>\n      <method v=\"2\" />\n    </configuration>\n    <configuration name=\"build\" type=\"MAKEFILE_TARGET_RUN_CONFIGURATION\" factoryName=\"Makefile\" temporary=\"true\">\n      <makefile filename=\"$PROJECT_DIR$/Makefile\" target=\"build\" workingDirectory=\"\" arguments=\"\">\n        <envs />\n      </makefile>\n      <method v=\"2\" />\n    </configuration>\n    <configuration name=\"build\" type=\"MAKEFILE_TARGET_RUN_CONFIGURATION\" factoryName=\"Makefile\" temporary=\"true\">\n      <makefile filename=\"$PROJECT_DIR$/Makefile\" target=\"build\" workingDirectory=\"\" arguments=\"\">\n        <envs />\n      </makefile>\n      <method v=\"2\" />\n    </configuration>\n    <configuration name=\"clean\" type=\"MAKEFILE_TARGET_RUN_CONFIGURATION\" factoryName=\"Makefile\" temporary=\"true\">\n      <makefile filename=\"$PROJECT_DIR$/Makefile\" target=\"clean\" workingDirectory=\"\" arguments=\"\">\n        <envs />\n      </makefile>\n      <method v=\"2\" />\n    </configuration>\n    <configuration name=\"clean\" type=\"MAKEFILE_TARGET_RUN_CONFIGURATION\" factoryName=\"Makefile\" temporary=\"true\">\n      <makefile filename=\"$PROJECT_DIR$/Makefile\" target=\"clean\" workingDirectory=\"\" arguments=\"\">\n        <envs />\n      </makefile>\n      <method v=\"2\" />\n    </configuration>\n    <list>\n      <item itemvalue=\"Makefile Target.build-combined\" />\n      <item itemvalue=\"Makefile Target.clean\" />\n      <item itemvalue=\"Makefile Target.all\" />\n      <item itemvalue=\"Makefile Target.build\" />\n      <item itemvalue=\"Native Application.all\" />\n      <item itemvalue=\"Native Application.build-combined\" />\n      <item itemvalue=\"Native Application.build\" />\n      <item itemvalue=\"Native Application.combine\" />\n      <item itemvalue=\"Native Application.copy-defs\" />\n      <item itemvalue=\"Native Application.prepare-combined\" />\n      <item itemvalue=\"Native Application.test-combined\" />\n      <item itemvalue=\"Native Application.hyprshell.so\" />\n      <item itemvalue=\"Native Application.test\" />\n    </list>\n    <recent_temporary>\n      <list>\n        <item itemvalue=\"Makefile Target.build-combined\" />\n        <item itemvalue=\"Makefile Target.clean\" />\n        <item itemvalue=\"Makefile Target.build\" />\n        <item itemvalue=\"Makefile Target.all\" />\n      </list>\n    </recent_temporary>\n  </component>\n  <component name=\"TaskManager\">\n    <task active=\"true\" id=\"Default\" summary=\"Default task\">\n      <changelist id=\"841e5689-e8a3-4a78-b7a4-76d1dbd901c1\" name=\"Changes\" comment=\"\" />\n      <created>1755982055609</created>\n      <option name=\"number\" value=\"Default\" />\n      <option name=\"presentableId\" value=\"Default\" />\n      <updated>1755982055609</updated>\n      <workItem from=\"1755982056750\" duration=\"6890000\" />\n      <workItem from=\"1756050154576\" duration=\"11299000\" />\n      <workItem from=\"1757002904532\" duration=\"1572000\" />\n      <workItem from=\"1757004581298\" duration=\"76000\" />\n      <workItem from=\"1757010082222\" duration=\"1146000\" />\n      <workItem from=\"1757327879065\" duration=\"789000\" />\n      <workItem from=\"1757544390560\" duration=\"56000\" />\n      <workItem from=\"1757544465273\" duration=\"145000\" />\n      <workItem from=\"1757544657889\" duration=\"7000\" />\n      <workItem from=\"1757544674626\" duration=\"5012000\" />\n      <workItem from=\"1766704524077\" duration=\"509000\" />\n      <workItem from=\"1767562310610\" duration=\"8026000\" />\n    </task>\n    <task id=\"LOCAL-00001\" summary=\"fix: cancel key events when opening hyprshell overview and switch\">\n      <option name=\"closed\" value=\"true\" />\n      <created>1757549403804</created>\n      <option name=\"number\" value=\"00001\" />\n      <option name=\"presentableId\" value=\"LOCAL-00001\" />\n      <option name=\"project\" value=\"LOCAL\" />\n      <updated>1757549403804</updated>\n    </task>\n    <option name=\"localTasksCounter\" value=\"2\" />\n    <servers />\n  </component>\n  <component name=\"TypeScriptGeneratedFilesManager\">\n    <option name=\"version\" value=\"3\" />\n  </component>\n  <component name=\"Vcs.Log.Tabs.Properties\">\n    <option name=\"TAB_STATES\">\n      <map>\n        <entry key=\"MAIN\">\n          <value>\n            <State />\n          </value>\n        </entry>\n      </map>\n    </option>\n  </component>\n  <component name=\"VcsManagerConfiguration\">\n    <ignored-roots>\n      <path value=\"$PROJECT_DIR$/../..\" />\n    </ignored-roots>\n    <MESSAGE value=\"fix: cancel key events when opening hyprshell overview and switch\" />\n    <option name=\"LAST_COMMIT_MESSAGE\" value=\"fix: cancel key events when opening hyprshell overview and switch\" />\n  </component>\n  <component name=\"XSLT-Support.FileAssociations.UIState\">\n    <expand />\n    <select />\n  </component>\n  <component name=\"github-copilot-workspace\">\n    <instructionFileLocations>\n      <option value=\".github/instructions\" />\n    </instructionFileLocations>\n    <promptFileLocations>\n      <option value=\".github/prompts\" />\n    </promptFileLocations>\n  </component>\n</project>"
  },
  {
    "path": "crates/hyprland-plugin/plugin/Makefile",
    "content": "SHELL := /bin/bash\n\n.PHONY: all clean combine prepare-combined copy-defs build build-combined test test-combined\n\nSRCS_DIR := ./src\nSRCS_FILES := key-press.cpp layer-change.cpp init.cpp exit.cpp main.cpp send.cpp mouse-button.cpp keyboard-focus.cpp\nSRCS := $(addprefix $(SRCS_DIR)/,$(SRCS_FILES))\n\nPREPAREDIR := out\nOUTNAME := hyprshell.so\nTARGET := $(OUTNAME)\n\nHEADERS := globals.h handlers.h defs.h send.h\n\nCXX := g++\nCXXFLAGS := -fPIC -std=c++2c -O2 -I/usr/include/pixman-1\nLDFLAGS := -shared --no-gnu-unique\n\nall: build\n\n# Build by compiling object files then linking into a shared object.\nOBJ := $(patsubst $(SRCS_DIR)/%.cpp,$(PREPAREDIR)/%.o,$(SRCS))\n\nbuild: $(TARGET)\n\n$(TARGET): $(OBJ)\n\t$(CXX) $(LDFLAGS) -o $@ $^\n\n$(PREPAREDIR)/%.o: $(SRCS_DIR)/%.cpp\n\t@mkdir -p $(dir $@)\n\t$(CXX) $(CXXFLAGS) -c $< -o $@\n\n# Prepare a single combined source file in $(PREPAREDIR)\nprepare-combined: clean combine copy-defs\n\ncombine:\n\t@mkdir -p $(PREPAREDIR)\n\t@rm -f $(PREPAREDIR)/all.cpp\n\t@for src in $(SRCS_FILES); do \\\n\t\tprintf \"\\n\\n// $$src\\n\" >> $(PREPAREDIR)/all.cpp; \\\n\t\tcat $(SRCS_DIR)/$$src >> $(PREPAREDIR)/all.cpp; \\\n\tdone\n\ncopy-defs:\n\t@mkdir -p $(PREPAREDIR)\n\t@for hdr in $(HEADERS); do \\\n\t\tcp $(SRCS_DIR)/$$hdr $(PREPAREDIR)/; \\\n\tdone\n\nbuild-combined: prepare-combined\n\t@cp $(SRCS_DIR)/defs-test.h $(PREPAREDIR)/defs-test.h 2>/dev/null || true\n\t$(CXX) $(CXXFLAGS) $(LDFLAGS) -o $(TARGET) $(PREPAREDIR)/all.cpp\n\ntest: build\n\tHyprland --config ./test-hyprland.conf\n\ntest-combined: build-combined\n\tHyprland --config ./test-hyprland.conf\n\nclean:\n\t@rm -f $(TARGET)\n\t@rm -rf $(PREPAREDIR)"
  },
  {
    "path": "crates/hyprland-plugin/plugin/README.md",
    "content": "# Plugin build at runtime of Hyprshell\n\nThe Makefile includes all necessary steps to develop.\n\nFor performance reasons all .cpp files are combined into one file, `make prepare-combined` will be run in build.rs\nbefore zipping the plugin and including it in the hyprshell binary.\n\n`make build` builds the plugin, it will be placed in `out/hyprshell.so`.\n\n`make test` builds the plugin and launches Hyprland with a custom config and the plugin loaded."
  },
  {
    "path": "crates/hyprland-plugin/plugin/src-52/defs-test.h",
    "content": "#pragma once\n\n#define HYPRSHELL_PLUGIN_NAME \"HYPSHELL PLUGIN TEST\"\n\n#define HYPRSHELL_PRINT_DEBUG 1\n#define HYPRSHELL_SOCKET_PATH \"/tmp/hyprland-plugin-socket\"\n\n#define HYPRSHELL_SWTICH_XKB_MOD_L XKB_KEY_Alt_L\n#define HYPRSHELL_SWTICH_XKB_MOD_R XKB_KEY_Alt_R\n#define HYPRSHELL_SWITCH_KEY \"tab\"\n#define HYPRSHELL_OVERVIEW_MOD \"Super\"\n#define HYPRSHELL_OVERVIEW_KEY \"super_l\"\n\n#define HYPRSHELL_CLOSE R\"(\"CloseSwitch\")\"\n#define HYPRSHELL_OPEN_OVERVIEW R\"(\"OpenOverview\")\"\n#define HYPRSHELL_OPEN_SWITCH R\"({\"OpenSwitch\":{\"reverse\":true}})\"\n#define HYPRSHELL_OPEN_SWITCH_REVERSE R\"({\"OpenSwitch\":{\"reverse\":false}})\"\n"
  },
  {
    "path": "crates/hyprland-plugin/plugin/src-52/defs.h",
    "content": "#pragma once\n\n#include <hyprland/src/plugins/PluginAPI.hpp>\n\nconst CHyprColor RED{1.0, 0.2, 0.2, 1.0};\nconst CHyprColor GREEN{0.2, 1.0, 0.2, 1.0};\n\nstruct PluginDescriptionInfo {\n    std::string name;\n    std::string description;\n    std::string author;\n    std::string version;\n};\n\n#define HYPRSHELL_PLUGIN_NAME \"$HYPRSHELL_PLUGIN_NAME$\"\n#define HYPRSHELL_PLUGIN_AUTHOR \"$HYPRSHELL_PLUGIN_AUTHOR$\"\n#define HYPRSHELL_PLUGIN_DESC \"$HYPRSHELL_PLUGIN_DESC$\"\n#define HYPRSHELL_PLUGIN_VERSION \"$HYPRSHELL_PLUGIN_VERSION$\"\n\n#define HYPRSHELL_PRINT_DEBUG $HYPRSHELL_PRINT_DEBUG$\n#define HYPRSHELL_PRINT_DEBUG_DEBUG 0\n#define HYPRSHELL_SOCKET_PATH \"$HYPRSHELL_SOCKET_PATH$\"\n\n#define HYPRSHELL_SWTICH_XKB_MOD_L $HYPRSHELL_SWTICH_XKB_MOD_L$\n#define HYPRSHELL_SWTICH_XKB_MOD_R $HYPRSHELL_SWTICH_XKB_MOD_R$\n#define HYPRSHELL_SWITCH_KEY \"$HYPRSHELL_SWITCH_KEY$\"\n\n#define HYPRSHELL_OVERVIEW_MOD \"$HYPRSHELL_OVERVIEW_MOD$\"\n#define HYPRSHELL_OVERVIEW_KEY \"$HYPRSHELL_OVERVIEW_KEY$\"\n\n#define HYPRSHELL_CLOSE R\"($HYPRSHELL_CLOSE$)\"\n#define HYPRSHELL_OPEN_OVERVIEW R\"($HYPRSHELL_OPEN_OVERVIEW$)\"\n#define HYPRSHELL_OPEN_SWITCH R\"($HYPRSHELL_OPEN_SWITCH$)\"\n#define HYPRSHELL_OPEN_SWITCH_REVERSE R\"($HYPRSHELL_OPEN_SWITCH_REVERSE$)\"\n\n// gets removed in the build process\n#include \"defs-test.h\"\n"
  },
  {
    "path": "crates/hyprland-plugin/plugin/src-52/exit.cpp",
    "content": "#include \"globals.h\"\n\nvoid exit() {\n    if constexpr (HYPRSHELL_PRINT_DEBUG == 1) {\n        HyprlandAPI::addNotification(PHANDLE, \"[Hyprshell Plugin] Plugin deactivated\", RED, 3000);\n    }\n}\n"
  },
  {
    "path": "crates/hyprland-plugin/plugin/src-52/globals.h",
    "content": "#pragma once\n#include <hyprland/src/devices/IPointer.hpp>\n#include <hyprland/src/desktop/view/LayerSurface.hpp>\n#include <hyprland/src/plugins/PluginAPI.hpp>\n\n\nvoid onKeyPress(const std::unordered_map<std::string, std::any> &data, SCallbackInfo &info);\n\nvoid onOpenLayerChange(const PHLLS &window, bool open);\n\nvoid onMouseButton(IPointer::SButtonEvent event);\n\nvoid onKeyboardFocus(const SP<CWLSurfaceResource> &surface);\n"
  },
  {
    "path": "crates/hyprland-plugin/plugin/src-52/handlers.h",
    "content": "#pragma once\n#include <hyprland/src/devices/IPointer.hpp>\n#include <hyprland/src/desktop/view/LayerSurface.hpp>\n#include <hyprland/src/event/EventBus.hpp>\n\n\nvoid onKeyPress(const IKeyboard::SKeyEvent &event, Event::SCallbackInfo &info);\n\nvoid onOpenLayerChange(const PHLLS &window, bool open);\n\nvoid onMouseButton(IPointer::SButtonEvent event, Event::SCallbackInfo &);\n\nvoid onKeyboardFocus(const SP<CWLSurfaceResource> &surface);\n"
  },
  {
    "path": "crates/hyprland-plugin/plugin/src-52/init.cpp",
    "content": "#include \"globals.h\"\n#include \"handlers.h\"\n#include \"defs.h\"\n\nPluginDescriptionInfo init(HANDLE handle) {\n    PHANDLE = handle;\n    // ALWAYS add this to your plugins. It will prevent random crashes coming from\n    // mismatched header versions.\n    const std::string HASH        = __hyprland_api_get_hash();\n    const std::string CLIENT_HASH = __hyprland_api_get_client_hash();\n    if (HASH != CLIENT_HASH) {\n        HyprlandAPI::addNotification(\n            PHANDLE,\n            \"[Hyprshell Plugin] Mismatched headers! Can't proceed. (Hyprland was updated but not restarted)\", RED,\n            5000);\n        HyprlandAPI::addNotification(PHANDLE, std::format(\"[Hyprshell Plugin] compositor hash: {}\", HASH), CHyprColor{1.0, 0.2, 0.2, 1.0}, 7000);\n        HyprlandAPI::addNotification(PHANDLE, std::format(\"[Hyprshell Plugin] client hash: {}\", CLIENT_HASH), CHyprColor{1.0, 0.2, 0.2, 1.0}, 7000);\n        throw std::runtime_error(\"[Hyprshell Plugin] Version mismatch\");\n    }\n\n    // ignore that this can return XKB_KEY_NoSymbol, it is only used to check if keysym equals\n    OVERVIEW_KEY = xkb_keysym_from_name(HYPRSHELL_OVERVIEW_KEY, XKB_KEYSYM_CASE_INSENSITIVE);\n    SWITCH_KEY = xkb_keysym_from_name(HYPRSHELL_SWITCH_KEY, XKB_KEYSYM_CASE_INSENSITIVE);\n    if constexpr (HYPRSHELL_PRINT_DEBUG == 1) {\n        const auto info = std::string(\"Config: \") +\n                          HYPRSHELL_OVERVIEW_KEY + \", \" +\n                          std::to_string(OVERVIEW_KEY) + \", \" +\n                          HYPRSHELL_OVERVIEW_MOD + \", \" +\n                          std::to_string(HYPRSHELL_SWTICH_XKB_MOD_L) + \", \" +\n                          std::to_string(HYPRSHELL_SWTICH_XKB_MOD_R) + \", \";\n        HyprlandAPI::addNotification(PHANDLE, \"[Hyprshell Plugin] Plugin started \" + info, GREEN, 8000);\n    }\n\n    // clang-format off\n    static auto P1 = HyprlandAPI::registerCallbackDynamic(PHANDLE, \"openLayer\",[&](void*, SCallbackInfo&, const std::any &data) { onOpenLayerChange(std::any_cast<PHLLS>(data), true); });\n    static auto P2 = HyprlandAPI::registerCallbackDynamic(PHANDLE, \"closeLayer\",[&](void*, SCallbackInfo&, const std::any &data) { onOpenLayerChange(std::any_cast<PHLLS>(data), false); });\n    static auto P3 = HyprlandAPI::registerCallbackDynamic(PHANDLE, \"keyPress\",[&](void*, SCallbackInfo& info, const std::any &data) { onKeyPress(std::any_cast<std::unordered_map<std::string, std::any>>(data), info); });\n    static auto P4 = HyprlandAPI::registerCallbackDynamic(PHANDLE, \"mouseButton\",[&](void*, SCallbackInfo&, const std::any &data) { onMouseButton(std::any_cast<IPointer::SButtonEvent>(data)); });\n    static auto P5 = HyprlandAPI::registerCallbackDynamic(PHANDLE, \"keyboardFocus\",[&](void*, SCallbackInfo&, const std::any &data) { onKeyboardFocus(std::any_cast<SP<CWLSurfaceResource>>(data)); });\n    // clang-format on\n\n    const std::string name = HYPRSHELL_PLUGIN_NAME;\n    const std::string author = HYPRSHELL_PLUGIN_AUTHOR;\n    const std::string description = HYPRSHELL_PLUGIN_DESC;\n    const std::string version = HYPRSHELL_PLUGIN_VERSION;\n    return {name, description, author, version};\n}\n"
  },
  {
    "path": "crates/hyprland-plugin/plugin/src-52/key-press.cpp",
    "content": "#include <strings.h>\n#include <hyprland/src/plugins/PluginAPI.hpp>\n#include <hyprland/src/devices/IKeyboard.hpp>\n#include <hyprland/src/managers/input/InputManager.hpp>\n\n#include \"globals.h\"\n#include \"defs.h\"\n#include \"send.h\"\n\n// modifier must pre pressed and released without any other keys pressed in between\nbool last_press_was_mod_press = false;\n\nvoid onKeyPress(const std::unordered_map<std::string, std::any> &data, SCallbackInfo &info) {\n    const auto keyboardIt = data.find(\"keyboard\");\n    const auto eventIt = data.find(\"event\");\n\n    if (keyboardIt != data.end() && eventIt != data.end()) {\n        const auto keyboard = std::any_cast<CSharedPointer<IKeyboard> >(keyboardIt->second);\n        if (g_pInputManager->shouldIgnoreVirtualKeyboard(keyboard)) {\n            return;\n        }\n        const auto event = std::any_cast<IKeyboard::SKeyEvent>(eventIt->second);\n        const auto state = keyboard->m_xkbState;\n        const uint32_t keycode = event.keycode + 8; // +8 because xkbcommon expects +8 from libinput\n        const bool release = event.state == WL_KEYBOARD_KEY_STATE_RELEASED;\n\n        // const bool shiftActive = xkb_state_mod_name_is_active(state, XKB_MOD_NAME_SHIFT, XKB_STATE_MODS_EFFECTIVE) == 1;\n        const bool ctrlActive = xkb_state_mod_name_is_active(state, XKB_MOD_NAME_CTRL, XKB_STATE_MODS_EFFECTIVE) == 1;\n        const bool superActive = xkb_state_mod_name_is_active(state, XKB_MOD_NAME_LOGO, XKB_STATE_MODS_EFFECTIVE) == 1;\n        const bool altActive = xkb_state_mod_name_is_active(state, XKB_MOD_NAME_ALT, XKB_STATE_MODS_EFFECTIVE) == 1;\n\n        const xkb_keysym_t keysym = xkb_state_key_get_one_sym(state, keycode);\n\n        if constexpr (HYPRSHELL_PRINT_DEBUG_DEBUG == 1) {\n            char buffer[20];\n            xkb_keysym_get_name(keysym, buffer, sizeof(buffer));\n            const auto bigString = std::string(\"Name: \") + buffer +\n                                   \" | KeySym: \" + std::to_string(keysym) +\n                                   // (shiftActive ? \" | Shift: Active\" : \"\") +\n                                   (ctrlActive ? \" | Control: Active\" : \"\") +\n                                   (superActive ? \" | Meta: Active\" : \"\") +\n                                   (altActive ? \" | Alt: Active\" : \"\") +\n                                   (release ? \" | State: Released\" : \" | State: Pressed\") +\n                                   (last_press_was_mod_press ? \" | Last press was mod press\" : \"\");\n            HyprlandAPI::addNotification(PHANDLE, \"[Hyprshell Plugin] \" + bigString, GREEN, 4000);\n        }\n\n        if (keysym == OVERVIEW_KEY) {\n            if constexpr (HYPRSHELL_PRINT_DEBUG == 1) {\n                HyprlandAPI::addNotification(\n                    PHANDLE, std::string(\"[Hyprshell Plugin] overview pressed??: \") + std::to_string(OVERVIEW_KEY),\n                    GREEN,\n                    2000);\n            }\n            if (OVERVIEW_KEY == XKB_KEY_Super_L || OVERVIEW_KEY == XKB_KEY_Super_R ||\n                OVERVIEW_KEY == XKB_KEY_Alt_L || OVERVIEW_KEY == XKB_KEY_Alt_R ||\n                OVERVIEW_KEY == XKB_KEY_Control_L || OVERVIEW_KEY == XKB_KEY_Control_R\n            ) {\n                if (((OVERVIEW_KEY == XKB_KEY_Super_L || OVERVIEW_KEY == XKB_KEY_Super_R) && superActive && !ctrlActive\n                     && !altActive) ||\n                    ((OVERVIEW_KEY == XKB_KEY_Alt_L || OVERVIEW_KEY == XKB_KEY_Alt_R) && altActive && !ctrlActive\n                     && !superActive) ||\n                    ((OVERVIEW_KEY == XKB_KEY_Control_L || OVERVIEW_KEY == XKB_KEY_Control_R) && ctrlActive && !\n                     superActive\n                     && !altActive)\n                ) {\n                    // open overview is only a modifier key\n                    if (release && last_press_was_mod_press && CHECK_NO_MOUSE_BUTTON_PRESSED) {\n                        if constexpr (HYPRSHELL_PRINT_DEBUG == 1) {\n                            HyprlandAPI::addNotification(PHANDLE, \"[Hyprshell Plugin] mod pressed\", GREEN, 2000);\n                        }\n                        info.cancelled = true;\n                        sendStringToHyprshellSocket(HYPRSHELL_OPEN_OVERVIEW);\n                    }\n                } else {\n                    // between pressing and releasing the mod key, there must be\n                    // no mouse click (dnd)\n                    // and no other key pressed\n                    last_press_was_mod_press = true;\n                    CHECK_NO_MOUSE_BUTTON_PRESSED = true;\n                }\n            } else {\n                // open overview is mod + key\n                if (!release && (\n                        (strcasecmp(HYPRSHELL_OVERVIEW_MOD, \"Alt\") == 0 && altActive && !superActive && !ctrlActive) ||\n                        (strcasecmp(HYPRSHELL_OVERVIEW_MOD, \"Super\") == 0 && superActive && !altActive && !ctrlActive) ||\n                        (strcasecmp(HYPRSHELL_OVERVIEW_MOD, \"Ctrl\") == 0 && ctrlActive && !superActive && !altActive))\n                ) {\n                    if constexpr (HYPRSHELL_PRINT_DEBUG == 1) {\n                        HyprlandAPI::addNotification(PHANDLE, \"[Hyprshell Plugin] mod + overview pressed\", GREEN, 2000);\n                    }\n                    info.cancelled = true;\n                    sendStringToHyprshellSocket(HYPRSHELL_OPEN_OVERVIEW);\n                }\n            }\n        } else {\n            // other key than modifier was pressed\n            last_press_was_mod_press = false;\n        }\n\n        // open switch mode\n        if (!release && !LAYER_VISIBLE) {\n            if (keysym == SWITCH_KEY) {\n                if ((HYPRSHELL_SWTICH_XKB_MOD_L == XKB_KEY_Alt_L && altActive && !superActive && !ctrlActive) ||\n                    (HYPRSHELL_SWTICH_XKB_MOD_L == XKB_KEY_Super_L && superActive && !altActive && !ctrlActive) ||\n                    (HYPRSHELL_SWTICH_XKB_MOD_L == XKB_KEY_Control_L && ctrlActive && !superActive && !altActive)\n                ) {\n                    if constexpr (HYPRSHELL_PRINT_DEBUG == 1) {\n                        HyprlandAPI::addNotification(PHANDLE, \"[Hyprshell Plugin] switch open (tab) pressed\", GREEN,\n                                                     2000);\n                    }\n                    info.cancelled = true;\n                    sendStringToHyprshellSocket(HYPRSHELL_OPEN_SWITCH);\n                }\n            }\n            if (keysym == XKB_KEY_ISO_Left_Tab) {\n                if ((HYPRSHELL_SWTICH_XKB_MOD_L == XKB_KEY_Alt_L && altActive) ||\n                    (HYPRSHELL_SWTICH_XKB_MOD_L == XKB_KEY_Super_L && superActive) ||\n                    (HYPRSHELL_SWTICH_XKB_MOD_L == XKB_KEY_Control_L && ctrlActive)\n                ) {\n                    if constexpr (HYPRSHELL_PRINT_DEBUG == 1) {\n                        HyprlandAPI::addNotification(PHANDLE, \"[Hyprshell Plugin] switch open (shift + tab) pressed\",\n                                                     GREEN, 2000);\n                    }\n                    info.cancelled = true;\n                    sendStringToHyprshellSocket(HYPRSHELL_OPEN_SWITCH_REVERSE);\n                }\n            }\n            if (keysym == XKB_KEY_grave || keysym == XKB_KEY_dead_grave) {\n                if ((HYPRSHELL_SWTICH_XKB_MOD_L == XKB_KEY_Alt_L && altActive) ||\n                    (HYPRSHELL_SWTICH_XKB_MOD_L == XKB_KEY_Super_L && superActive) ||\n                    (HYPRSHELL_SWTICH_XKB_MOD_L == XKB_KEY_Control_L && ctrlActive)\n                ) {\n                    if constexpr (HYPRSHELL_PRINT_DEBUG == 1) {\n                        HyprlandAPI::addNotification(PHANDLE, \"[Hyprshell Plugin] switch open (grave) pressed\", GREEN,\n                                                     2000);\n                    }\n                    info.cancelled = true;\n                    sendStringToHyprshellSocket(HYPRSHELL_OPEN_SWITCH_REVERSE);\n                }\n            }\n        }\n\n        // release switch mode\n        if (release && (keysym == HYPRSHELL_SWTICH_XKB_MOD_R || keysym == HYPRSHELL_SWTICH_XKB_MOD_L)) {\n            if constexpr (HYPRSHELL_PRINT_DEBUG == 1) {\n                HyprlandAPI::addNotification(PHANDLE, \"[Hyprshell Plugin] shift mode release pressed\", GREEN, 2000);\n            }\n            sendStringToHyprshellSocket(HYPRSHELL_CLOSE);\n        }\n    }\n}\n"
  },
  {
    "path": "crates/hyprland-plugin/plugin/src-52/keyboard-focus.cpp",
    "content": "#include \"globals.h\"\n#include <hyprland/src/desktop/view/LayerSurface.hpp>\n\nvoid onKeyboardFocus(const SP<CWLSurfaceResource> &surface) {\n    if (LAYER_VISIBLE) {\n        if constexpr (HYPRSHELL_PRINT_DEBUG == 1) {\n            HyprlandAPI::addNotification(PHANDLE, \"Focus change\", GREEN, 5000);\n        }\n        // TODO focus layer\n    }\n}\n"
  },
  {
    "path": "crates/hyprland-plugin/plugin/src-52/layer-change.cpp",
    "content": "#include <hyprland/src/plugins/PluginAPI.hpp>\n#include <hyprland/src/desktop/view/LayerSurface.hpp>\n\n#include \"globals.h\"\n\nvoid onOpenLayerChange(const PHLLS &window, const bool open) {\n    if (window->m_namespace.starts_with(\"hyprshell_\")) {\n        // if constexpr (HYPRSHELL_PRINT_DEBUG == 1) {\n        //     HyprlandAPI::addNotification(PHANDLE, \"Layer active: \" + std::to_string(open), GREEN, 5000);\n        // }\n        LAYER_VISIBLE = open;\n    }\n}\n"
  },
  {
    "path": "crates/hyprland-plugin/plugin/src-52/main.cpp",
    "content": "#include <hyprland/src/plugins/PluginAPI.hpp>\n#include \"globals.h\"\n\n// Do NOT change this function.\nAPICALL EXPORT std::string PLUGIN_API_VERSION() {\n    return HYPRLAND_API_VERSION;\n}\n\nAPICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {\n    auto [name, description, author, version] = init(handle);\n    return {name, description, author, version};\n}\n\nAPICALL EXPORT void PLUGIN_EXIT() {\n    return exit();\n}\n"
  },
  {
    "path": "crates/hyprland-plugin/plugin/src-52/mouse-button.cpp",
    "content": "#include <hyprland/src/devices/IPointer.hpp>\n\n#include \"globals.h\"\n\nvoid onMouseButton(const IPointer::SButtonEvent event) {\n    CHECK_NO_MOUSE_BUTTON_PRESSED = false;\n    // if constexpr (HYPRSHELL_PRINT_DEBUG == 1) {\n    //     HyprlandAPI::addNotification(PHANDLE, \"[Hyprshell Plugin] Mouse button pressed\", GREEN, 4000);\n    // }\n}\n"
  },
  {
    "path": "crates/hyprland-plugin/plugin/src-52/send.cpp",
    "content": "#include \"send.h\"\n\n#include <sys/socket.h>\n#include <sys/un.h>\n#include <cstring>\n\n#include \"defs.h\"\n\nvoid sendStringToHyprshellSocket(const std::string &message) {\n    const int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);\n    if (sockfd < 0) return;\n    sockaddr_un addr{};\n    addr.sun_family = AF_UNIX;\n    std::strncpy(addr.sun_path, HYPRSHELL_SOCKET_PATH, sizeof(addr.sun_path) - 1);\n    if (connect(sockfd, reinterpret_cast<sockaddr *>(&addr), sizeof(addr)) < 0) {\n        close(sockfd);\n        return;\n    }\n    send(sockfd, message.c_str(), message.size(), 0);\n    close(sockfd);\n}\n"
  },
  {
    "path": "crates/hyprland-plugin/plugin/src-52/send.h",
    "content": "#pragma once\n#include <string>\n\nvoid sendStringToHyprshellSocket(const std::string &message);\n"
  },
  {
    "path": "crates/hyprland-plugin/plugin/src-54/defs-test.h",
    "content": "#pragma once\n\n#define HYPRSHELL_PLUGIN_NAME \"HYPSHELL PLUGIN TEST\"\n\n#define HYPRSHELL_PRINT_DEBUG 1\n#define HYPRSHELL_SOCKET_PATH \"/tmp/hyprland-plugin-socket\"\n\n#define HYPRSHELL_SWTICH_XKB_MOD_L XKB_KEY_Alt_L\n#define HYPRSHELL_SWTICH_XKB_MOD_R XKB_KEY_Alt_R\n#define HYPRSHELL_SWITCH_KEY \"tab\"\n#define HYPRSHELL_OVERVIEW_MOD \"Super\"\n#define HYPRSHELL_OVERVIEW_KEY \"super_l\"\n\n#define HYPRSHELL_CLOSE R\"(\"CloseSwitch\")\"\n#define HYPRSHELL_OPEN_OVERVIEW R\"(\"OpenOverview\")\"\n#define HYPRSHELL_OPEN_SWITCH R\"({\"OpenSwitch\":{\"reverse\":true}})\"\n#define HYPRSHELL_OPEN_SWITCH_REVERSE R\"({\"OpenSwitch\":{\"reverse\":false}})\"\n"
  },
  {
    "path": "crates/hyprland-plugin/plugin/src-54/defs.h",
    "content": "#pragma once\n\n#include <hyprland/src/plugins/PluginAPI.hpp>\n\nconst CHyprColor RED{1.0, 0.2, 0.2, 1.0};\nconst CHyprColor GREEN{0.2, 1.0, 0.2, 1.0};\n\nstruct PluginDescriptionInfo {\n    std::string name;\n    std::string description;\n    std::string author;\n    std::string version;\n};\n\n#define HYPRSHELL_PLUGIN_NAME \"$HYPRSHELL_PLUGIN_NAME$\"\n#define HYPRSHELL_PLUGIN_AUTHOR \"$HYPRSHELL_PLUGIN_AUTHOR$\"\n#define HYPRSHELL_PLUGIN_DESC \"$HYPRSHELL_PLUGIN_DESC$\"\n#define HYPRSHELL_PLUGIN_VERSION \"$HYPRSHELL_PLUGIN_VERSION$\"\n\n#define HYPRSHELL_PRINT_DEBUG $HYPRSHELL_PRINT_DEBUG$\n#define HYPRSHELL_PRINT_DEBUG_DEBUG 0\n#define HYPRSHELL_SOCKET_PATH \"$HYPRSHELL_SOCKET_PATH$\"\n\n#define HYPRSHELL_SWTICH_XKB_MOD_L $HYPRSHELL_SWTICH_XKB_MOD_L$\n#define HYPRSHELL_SWTICH_XKB_MOD_R $HYPRSHELL_SWTICH_XKB_MOD_R$\n#define HYPRSHELL_SWITCH_KEY \"$HYPRSHELL_SWITCH_KEY$\"\n\n#define HYPRSHELL_OVERVIEW_MOD \"$HYPRSHELL_OVERVIEW_MOD$\"\n#define HYPRSHELL_OVERVIEW_KEY \"$HYPRSHELL_OVERVIEW_KEY$\"\n\n#define HYPRSHELL_CLOSE R\"($HYPRSHELL_CLOSE$)\"\n#define HYPRSHELL_OPEN_OVERVIEW R\"($HYPRSHELL_OPEN_OVERVIEW$)\"\n#define HYPRSHELL_OPEN_SWITCH R\"($HYPRSHELL_OPEN_SWITCH$)\"\n#define HYPRSHELL_OPEN_SWITCH_REVERSE R\"($HYPRSHELL_OPEN_SWITCH_REVERSE$)\"\n\n// gets removed in the build process\n#include \"defs-test.h\"\n"
  },
  {
    "path": "crates/hyprland-plugin/plugin/src-54/exit.cpp",
    "content": "#include \"globals.h\"\n\nvoid exit() {\n    if constexpr (HYPRSHELL_PRINT_DEBUG == 1) {\n        HyprlandAPI::addNotification(PHANDLE, \"[Hyprshell Plugin] Plugin deactivated\", RED, 3000);\n    }\n}\n"
  },
  {
    "path": "crates/hyprland-plugin/plugin/src-54/globals.h",
    "content": "#pragma once\n#include <hyprland/src/plugins/PluginAPI.hpp>\n#include <hyprland/src/devices/IKeyboard.hpp>\n\n#include \"defs.h\"\n\ninline void *PHANDLE = nullptr;\n\ninline bool LAYER_VISIBLE = false;\ninline bool CHECK_NO_MOUSE_BUTTON_PRESSED = false;\n\ninline xkb_keysym_t OVERVIEW_KEY;\ninline xkb_keysym_t SWITCH_KEY;\n\nPluginDescriptionInfo init(HANDLE handle);\n\nvoid exit();\n"
  },
  {
    "path": "crates/hyprland-plugin/plugin/src-54/handlers.h",
    "content": "#pragma once\n#include <hyprland/src/devices/IPointer.hpp>\n#include <hyprland/src/desktop/view/LayerSurface.hpp>\n#include <hyprland/src/event/EventBus.hpp>\n\n\nvoid onKeyPress(const IKeyboard::SKeyEvent &event, Event::SCallbackInfo &info);\n\nvoid onOpenLayerChange(const PHLLS &window, bool open);\n\nvoid onMouseButton(IPointer::SButtonEvent event, Event::SCallbackInfo &);\n\nvoid onKeyboardFocus(const SP<CWLSurfaceResource> &surface);\n"
  },
  {
    "path": "crates/hyprland-plugin/plugin/src-54/init.cpp",
    "content": "#include \"globals.h\"\n#include \"handlers.h\"\n#include \"defs.h\"\n\n#include <hyprland/src/event/EventBus.hpp>\n#include <hyprland/src/managers/input/InputManager.hpp>\n\nPluginDescriptionInfo init(HANDLE handle) {\n    PHANDLE = handle;\n    // ALWAYS add this to your plugins. It will prevent random crashes coming from\n    // mismatched header versions.\n    const std::string HASH = __hyprland_api_get_hash();\n    const std::string CLIENT_HASH = __hyprland_api_get_client_hash();\n    if (HASH != CLIENT_HASH) {\n        HyprlandAPI::addNotification(\n            PHANDLE,\n            \"[Hyprshell Plugin] Mismatched headers! Can't proceed. (Hyprland was updated but not restarted)\", RED,\n            5000);\n        HyprlandAPI::addNotification(PHANDLE, std::format(\"[Hyprshell Plugin] compositor hash: {}\", HASH),\n                                     CHyprColor{1.0, 0.2, 0.2, 1.0}, 7000);\n        HyprlandAPI::addNotification(PHANDLE, std::format(\"[Hyprshell Plugin] client hash: {}\", CLIENT_HASH),\n                                     CHyprColor{1.0, 0.2, 0.2, 1.0}, 7000);\n        throw std::runtime_error(\"[Hyprshell Plugin] Version mismatch\");\n    }\n\n    // ignore that this can return XKB_KEY_NoSymbol, it is only used to check if keysym equals\n    OVERVIEW_KEY = xkb_keysym_from_name(HYPRSHELL_OVERVIEW_KEY, XKB_KEYSYM_CASE_INSENSITIVE);\n    SWITCH_KEY = xkb_keysym_from_name(HYPRSHELL_SWITCH_KEY, XKB_KEYSYM_CASE_INSENSITIVE);\n    if constexpr (HYPRSHELL_PRINT_DEBUG == 1) {\n        const auto info = std::string(\"Config: \") +\n                          HYPRSHELL_OVERVIEW_KEY + \", \" +\n                          std::to_string(OVERVIEW_KEY) + \", \" +\n                          HYPRSHELL_OVERVIEW_MOD + \", \" +\n                          std::to_string(HYPRSHELL_SWTICH_XKB_MOD_L) + \", \" +\n                          std::to_string(HYPRSHELL_SWTICH_XKB_MOD_R) + \", \";\n        HyprlandAPI::addNotification(PHANDLE, \"[Hyprshell Plugin] Plugin started \" + info, GREEN, 8000);\n    }\n\n    // clang-format off\n    static auto P1 = Event::bus()->m_events.layer.opened.listen([&](const PHLLS &data) { onOpenLayerChange(data, true); });\n    static auto P2 = Event::bus()->m_events.layer.closed.listen([&](const PHLLS &data) { onOpenLayerChange(data, false); });\n    static auto P3 = Event::bus()->m_events.input.keyboard.key.listen(onKeyPress);\n    static auto P4 = Event::bus()->m_events.input.mouse.button.listen(onMouseButton);\n    static auto P5 = Event::bus()->m_events.input.keyboard.focus.listen(onKeyboardFocus);\n    // clang-format on\n\n    const std::string name = HYPRSHELL_PLUGIN_NAME;\n    const std::string author = HYPRSHELL_PLUGIN_AUTHOR;\n    const std::string description = HYPRSHELL_PLUGIN_DESC;\n    const std::string version = HYPRSHELL_PLUGIN_VERSION;\n    return {name, description, author, version};\n}\n"
  },
  {
    "path": "crates/hyprland-plugin/plugin/src-54/key-press.cpp",
    "content": "#include <strings.h>\n#include <hyprland/src/plugins/PluginAPI.hpp>\n#include <hyprland/src/devices/IKeyboard.hpp>\n#include <hyprland/src/event/EventBus.hpp>\n#include <hyprland/src/managers/input/InputManager.hpp>\n\n#include \"globals.h\"\n#include \"defs.h\"\n#include \"send.h\"\n\n// modifier must pre pressed and released without any other keys pressed in between\nbool last_press_was_mod_press = false;\n\n// last keyboard (a bit hacky)\nSP<IKeyboard> last_keyboard;\n\nvoid onKeyPress(const IKeyboard::SKeyEvent &event, Event::SCallbackInfo &info) {\n    const uint32_t keycode = event.keycode + 8; // +8 because xkbcommon expects +8 from libinput\n    const bool release = event.state == WL_KEYBOARD_KEY_STATE_RELEASED;\n\n    bool valid = false;\n    SP<IKeyboard> keyboard;\n    if (!release) {\n        // Get the correct keyboard from the input manager (I cant get keyboard from event any more)\n        for (const auto &kb: g_pInputManager->m_keyboards) {\n            if (kb->getPressed(event.keycode)) {\n                keyboard = kb;\n                last_keyboard = kb;\n                valid = true;\n                break;\n            }\n        }\n    } else {\n        keyboard = last_keyboard;\n        valid = true;\n    }\n\n    if (!valid) {\n        if constexpr (HYPRSHELL_PRINT_DEBUG == 1) {\n            HyprlandAPI::addNotification(PHANDLE, \"[Hyprshell Plugin] invalid keypress\", RED, 4000);\n        }\n        return;\n    }\n\n    if (g_pInputManager->shouldIgnoreVirtualKeyboard(keyboard)) {\n        return;\n    }\n    const auto state = keyboard->m_xkbState;\n\n    // const bool shiftActive = xkb_state_mod_name_is_active(state, XKB_MOD_NAME_SHIFT, XKB_STATE_MODS_EFFECTIVE) == 1;\n    const bool ctrlActive = xkb_state_mod_name_is_active(state, XKB_MOD_NAME_CTRL, XKB_STATE_MODS_EFFECTIVE) == 1;\n    const bool superActive = xkb_state_mod_name_is_active(state, XKB_MOD_NAME_LOGO, XKB_STATE_MODS_EFFECTIVE) == 1;\n    const bool altActive = xkb_state_mod_name_is_active(state, XKB_MOD_NAME_ALT, XKB_STATE_MODS_EFFECTIVE) == 1;\n\n    const xkb_keysym_t keysym = xkb_state_key_get_one_sym(state, keycode);\n\n    if constexpr (HYPRSHELL_PRINT_DEBUG_DEBUG == 1) {\n        char buffer[20];\n        xkb_keysym_get_name(keysym, buffer, sizeof(buffer));\n        const auto bigString = std::string(\"Name: \") + buffer +\n                               \" | KeySym: \" + std::to_string(keysym) +\n                               (ctrlActive ? \" | Control: Active\" : \"\") +\n                               (superActive ? \" | Meta: Active\" : \"\") +\n                               (altActive ? \" | Alt: Active\" : \"\") +\n                               (release ? \" | State: Released\" : \" | State: Pressed\") +\n                               (last_press_was_mod_press ? \" | Last press was mod press\" : \"\");\n        HyprlandAPI::addNotification(PHANDLE, \"[Hyprshell Plugin] \" + bigString, GREEN, 4000);\n    }\n\n    if (keysym == OVERVIEW_KEY) {\n        if constexpr (HYPRSHELL_PRINT_DEBUG == 1) {\n            HyprlandAPI::addNotification(\n                PHANDLE, std::string(\"[Hyprshell Plugin] overview pressed??: \") + std::to_string(OVERVIEW_KEY),\n                GREEN,\n                2000);\n        }\n        if (OVERVIEW_KEY == XKB_KEY_Super_L || OVERVIEW_KEY == XKB_KEY_Super_R ||\n            OVERVIEW_KEY == XKB_KEY_Alt_L || OVERVIEW_KEY == XKB_KEY_Alt_R ||\n            OVERVIEW_KEY == XKB_KEY_Control_L || OVERVIEW_KEY == XKB_KEY_Control_R\n        ) {\n            if (((OVERVIEW_KEY == XKB_KEY_Super_L || OVERVIEW_KEY == XKB_KEY_Super_R) && superActive && !ctrlActive\n                 && !altActive) ||\n                ((OVERVIEW_KEY == XKB_KEY_Alt_L || OVERVIEW_KEY == XKB_KEY_Alt_R) && altActive && !ctrlActive\n                 && !superActive) ||\n                ((OVERVIEW_KEY == XKB_KEY_Control_L || OVERVIEW_KEY == XKB_KEY_Control_R) && ctrlActive && !\n                 superActive\n                 && !altActive)\n            ) {\n                // open overview is only a modifier key\n                if (release && last_press_was_mod_press && CHECK_NO_MOUSE_BUTTON_PRESSED) {\n                    if constexpr (HYPRSHELL_PRINT_DEBUG == 1) {\n                        HyprlandAPI::addNotification(PHANDLE, \"[Hyprshell Plugin] mod pressed\", GREEN, 2000);\n                    }\n                    info.cancelled = true;\n                    sendStringToHyprshellSocket(HYPRSHELL_OPEN_OVERVIEW);\n                }\n            } else {\n                // between pressing and releasing the mod key, there must be\n                // no mouse click (dnd)\n                // and no other key pressed\n                last_press_was_mod_press = true;\n                CHECK_NO_MOUSE_BUTTON_PRESSED = true;\n            }\n        } else {\n            // open overview is mod + key\n            if (!release && (\n                    (strcasecmp(HYPRSHELL_OVERVIEW_MOD, \"Alt\") == 0 && altActive && !superActive && !ctrlActive) ||\n                    (strcasecmp(HYPRSHELL_OVERVIEW_MOD, \"Super\") == 0 && superActive && !altActive && !ctrlActive) ||\n                    (strcasecmp(HYPRSHELL_OVERVIEW_MOD, \"Ctrl\") == 0 && ctrlActive && !superActive && !altActive))\n            ) {\n                if constexpr (HYPRSHELL_PRINT_DEBUG == 1) {\n                    HyprlandAPI::addNotification(PHANDLE, \"[Hyprshell Plugin] mod + overview pressed\", GREEN, 2000);\n                }\n                info.cancelled = true;\n                sendStringToHyprshellSocket(HYPRSHELL_OPEN_OVERVIEW);\n            }\n        }\n    } else {\n        // other key than modifier was pressed\n        last_press_was_mod_press = false;\n    }\n\n    // open switch mode\n    if (!release && !LAYER_VISIBLE) {\n        if (keysym == SWITCH_KEY) {\n            if ((HYPRSHELL_SWTICH_XKB_MOD_L == XKB_KEY_Alt_L && altActive && !superActive && !ctrlActive) ||\n                (HYPRSHELL_SWTICH_XKB_MOD_L == XKB_KEY_Super_L && superActive && !altActive && !ctrlActive) ||\n                (HYPRSHELL_SWTICH_XKB_MOD_L == XKB_KEY_Control_L && ctrlActive && !superActive && !altActive)\n            ) {\n                if constexpr (HYPRSHELL_PRINT_DEBUG == 1) {\n                    HyprlandAPI::addNotification(PHANDLE, \"[Hyprshell Plugin] switch open (tab) pressed\", GREEN,\n                                                 2000);\n                }\n                info.cancelled = true;\n                sendStringToHyprshellSocket(HYPRSHELL_OPEN_SWITCH);\n            }\n        }\n        if (keysym == XKB_KEY_ISO_Left_Tab) {\n            if ((HYPRSHELL_SWTICH_XKB_MOD_L == XKB_KEY_Alt_L && altActive) ||\n                (HYPRSHELL_SWTICH_XKB_MOD_L == XKB_KEY_Super_L && superActive) ||\n                (HYPRSHELL_SWTICH_XKB_MOD_L == XKB_KEY_Control_L && ctrlActive)\n            ) {\n                if constexpr (HYPRSHELL_PRINT_DEBUG == 1) {\n                    HyprlandAPI::addNotification(PHANDLE, \"[Hyprshell Plugin] switch open (shift + tab) pressed\",\n                                                 GREEN, 2000);\n                }\n                info.cancelled = true;\n                sendStringToHyprshellSocket(HYPRSHELL_OPEN_SWITCH_REVERSE);\n            }\n        }\n        if (keysym == XKB_KEY_grave || keysym == XKB_KEY_dead_grave) {\n            if ((HYPRSHELL_SWTICH_XKB_MOD_L == XKB_KEY_Alt_L && altActive) ||\n                (HYPRSHELL_SWTICH_XKB_MOD_L == XKB_KEY_Super_L && superActive) ||\n                (HYPRSHELL_SWTICH_XKB_MOD_L == XKB_KEY_Control_L && ctrlActive)\n            ) {\n                if constexpr (HYPRSHELL_PRINT_DEBUG == 1) {\n                    HyprlandAPI::addNotification(PHANDLE, \"[Hyprshell Plugin] switch open (grave) pressed\", GREEN,\n                                                 2000);\n                }\n                info.cancelled = true;\n                sendStringToHyprshellSocket(HYPRSHELL_OPEN_SWITCH_REVERSE);\n            }\n        }\n    }\n\n    // release switch mode\n    if (release && (keysym == HYPRSHELL_SWTICH_XKB_MOD_R || keysym == HYPRSHELL_SWTICH_XKB_MOD_L)) {\n        if constexpr (HYPRSHELL_PRINT_DEBUG == 1) {\n            HyprlandAPI::addNotification(PHANDLE, \"[Hyprshell Plugin] shift mode release pressed\", GREEN, 2000);\n        }\n        sendStringToHyprshellSocket(HYPRSHELL_CLOSE);\n    }\n}\n"
  },
  {
    "path": "crates/hyprland-plugin/plugin/src-54/keyboard-focus.cpp",
    "content": "#include \"globals.h\"\n#include <hyprland/src/desktop/view/LayerSurface.hpp>\n\nvoid onKeyboardFocus(const SP<CWLSurfaceResource> &surface) {\n    if (LAYER_VISIBLE) {\n        if constexpr (HYPRSHELL_PRINT_DEBUG == 1) {\n            HyprlandAPI::addNotification(PHANDLE, \"Focus change\", GREEN, 5000);\n        }\n        // TODO focus layer\n    }\n}\n"
  },
  {
    "path": "crates/hyprland-plugin/plugin/src-54/layer-change.cpp",
    "content": "#include <hyprland/src/plugins/PluginAPI.hpp>\n#include <hyprland/src/desktop/view/LayerSurface.hpp>\n\n#include \"globals.h\"\n\nvoid onOpenLayerChange(const PHLLS &window, const bool open) {\n    if (window->m_namespace.starts_with(\"hyprshell_\")) {\n        // if constexpr (HYPRSHELL_PRINT_DEBUG == 1) {\n        //     HyprlandAPI::addNotification(PHANDLE, \"Layer active: \" + std::to_string(open), GREEN, 5000);\n        // }\n        LAYER_VISIBLE = open;\n    }\n}\n"
  },
  {
    "path": "crates/hyprland-plugin/plugin/src-54/main.cpp",
    "content": "#include <hyprland/src/plugins/PluginAPI.hpp>\n#include \"globals.h\"\n\n// Do NOT change this function.\nAPICALL EXPORT std::string PLUGIN_API_VERSION() {\n    return HYPRLAND_API_VERSION;\n}\n\nAPICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {\n    auto [name, description, author, version] = init(handle);\n    return {name, description, author, version};\n}\n\nAPICALL EXPORT void PLUGIN_EXIT() {\n    return exit();\n}\n"
  },
  {
    "path": "crates/hyprland-plugin/plugin/src-54/mouse-button.cpp",
    "content": "#include <hyprland/src/devices/IPointer.hpp>\n#include <hyprland/src/event/EventBus.hpp>\n\n#include \"globals.h\"\n\nvoid onMouseButton(IPointer::SButtonEvent event, Event::SCallbackInfo &info) {\n    CHECK_NO_MOUSE_BUTTON_PRESSED = false;\n    // if constexpr (HYPRSHELL_PRINT_DEBUG == 1) {\n    //     HyprlandAPI::addNotification(PHANDLE, \"[Hyprshell Plugin] Mouse button pressed\", GREEN, 4000);\n    // }\n}\n"
  },
  {
    "path": "crates/hyprland-plugin/plugin/src-54/send.cpp",
    "content": "#include \"send.h\"\n\n#include <sys/socket.h>\n#include <sys/un.h>\n#include <cstring>\n\n#include \"defs.h\"\n\nvoid sendStringToHyprshellSocket(const std::string &message) {\n    const int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);\n    if (sockfd < 0) return;\n    sockaddr_un addr{};\n    addr.sun_family = AF_UNIX;\n    std::strncpy(addr.sun_path, HYPRSHELL_SOCKET_PATH, sizeof(addr.sun_path) - 1);\n    if (connect(sockfd, reinterpret_cast<sockaddr *>(&addr), sizeof(addr)) < 0) {\n        close(sockfd);\n        return;\n    }\n    send(sockfd, message.c_str(), message.size(), 0);\n    close(sockfd);\n}\n"
  },
  {
    "path": "crates/hyprland-plugin/plugin/src-54/send.h",
    "content": "#pragma once\n#include <string>\n\nvoid sendStringToHyprshellSocket(const std::string &message);\n"
  },
  {
    "path": "crates/hyprland-plugin/plugin/test-hyprland.conf",
    "content": "monitor=,preferred,auto,1\n\nenv = XCURSOR_SIZE,24\nenv = HYPRCURSOR_SIZE,24\n\n# https://wiki.hyprland.org/Configuring/Variables/#animations\nanimations {\n    enabled = false,\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# See https://wiki.hyprland.org/Configuring/Keywords/\n$mainMod = CTRL\n\n# Example binds, see https://wiki.hyprland.org/Configuring/Binds/ for more\nbind = $mainMod, Return, exec, alacritty\nbind = $mainMod, Q, killactive,\nbind = $mainMod, P, pseudo, # dwindle\nbind = $mainMod, J, 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\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\n\n# Move/resize windows with mainMod + LMB/RMB and dragging\nbindm = $mainMod, mouse:272, movewindow\nbindm = $mainMod, mouse:273, resizewindow"
  },
  {
    "path": "crates/hyprland-plugin/src/build.rs",
    "content": "use crate::PLUGIN_OUTPUT_PATH;\nuse anyhow::{Context, bail};\nuse std::env;\nuse std::process::{Command, Stdio};\nuse tempfile::TempDir;\nuse tracing::{debug_span, info, trace, warn};\n\npub fn build(dir: &TempDir) -> anyhow::Result<()> {\n    let _span = debug_span!(\"build\", path =? dir.path()).entered();\n    trace!(\"PATH: {:?}\", env::var_os(\"PATH\"));\n    trace!(\"CPATH: {:?}\", env::var_os(\"CPATH\"));\n    let mut cmd = Command::new(\"gcc\");\n    cmd.current_dir(dir.path())\n        .args([\"-shared\", \"-fPIC\", \"--no-gnu-unique\", \"-std=c++2c\"])\n        .arg(\"-I/usr/include/pixman-1\") // fix for arch?\n        .arg(\"-O2\")\n        .arg(\"-o\")\n        .arg(PLUGIN_OUTPUT_PATH);\n\n    cmd.arg(\"all.cpp\");\n\n    trace!(\"Running build command: {cmd:?}\");\n    let out = cmd\n        .stdout(Stdio::piped())\n        .stderr(Stdio::piped())\n        .spawn()\n        .context(\"Failed to spawn build process\")?;\n    let output = out.wait_with_output();\n    match output {\n        Ok(output) => {\n            if output.status.success() {\n                Ok(())\n            } else {\n                info!(\"Build output (code: {:?})\", output.status.code());\n                for line in String::from_utf8(output.stderr).unwrap_or_default().lines() {\n                    warn!(\"{line}\");\n                }\n                bail!(\"Build failed with exit code: {:?}\", output.status.code());\n            }\n        }\n        Err(err) => {\n            bail!(\"Error from [{cmd:?}]: {err:?}\");\n        }\n    }\n}\n"
  },
  {
    "path": "crates/hyprland-plugin/src/configure.rs",
    "content": "use crate::{PLUGIN_AUTHOR, PLUGIN_DESC, PLUGIN_NAME, PLUGIN_VERSION};\nuse anyhow::Context;\nuse core_lib::binds::generate_transfer;\nuse core_lib::transfer::{OpenSwitch, TransferType};\nuse core_lib::util::get_daemon_socket_path_buff;\nuse std::fmt::Display;\nuse std::fs::OpenOptions;\nuse std::io::{Read, Write};\nuse tempfile::TempDir;\nuse tracing::debug_span;\n\npub struct PluginConfig {\n    pub xkb_key_switch_mod: Option<Box<str>>,\n    pub xkb_key_switch_key: Option<Box<str>>,\n    pub xkb_key_overview_mod: Option<Box<str>>,\n    pub xkb_key_overview_key: Option<Box<str>>,\n}\nimpl Display for PluginConfig {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(\n            f,\n            \"{}|{}|{}|{}\",\n            self.xkb_key_switch_mod.as_deref().unwrap_or(\"\"),\n            self.xkb_key_switch_key.as_deref().unwrap_or(\"\"),\n            self.xkb_key_overview_mod.as_deref().unwrap_or(\"\"),\n            self.xkb_key_overview_key.as_deref().unwrap_or(\"\"),\n        )\n    }\n}\n\npub fn configure(\n    dir: &TempDir,\n    config: &PluginConfig,\n    version: &semver::Version,\n) -> anyhow::Result<()> {\n    let _span = debug_span!(\"configure\", path =? dir.path()).entered();\n    let defs = dir.path().join(\"defs.h\");\n\n    let mut defs_file = OpenOptions::new()\n        .read(true)\n        .open(&defs)\n        .with_context(|| format!(\"unable to open defs file: {}\", defs.display()))?;\n    let mut buffer = String::new();\n    defs_file\n        .read_to_string(&mut buffer)\n        .context(\"unable to read defs file\")?;\n    let path = get_daemon_socket_path_buff()\n        .to_str()\n        .map(str::to_string)\n        .context(\"unable to get daemon socket path\")?;\n    for replace in [\n        (\"#include \\\"defs-test.h\\\"\", \"\"),\n        (\"$HYPRSHELL_PLUGIN_NAME$\", PLUGIN_NAME),\n        (\"$HYPRSHELL_PLUGIN_AUTHOR$\", PLUGIN_AUTHOR),\n        (\n            \"$HYPRSHELL_PLUGIN_DESC$\",\n            &format!(\"{PLUGIN_DESC} - {config}\"),\n        ),\n        (\n            \"$HYPRSHELL_PLUGIN_VERSION$\",\n            &format!(\"{PLUGIN_VERSION}-{version}\"),\n        ),\n        (\n            \"$HYPRSHELL_PRINT_DEBUG$\",\n            if cfg!(debug_assertions) { \"1\" } else { \"0\" },\n        ),\n        (\"$HYPRSHELL_SOCKET_PATH$\", &path),\n        (\n            \"$HYPRSHELL_SWTICH_XKB_MOD_L$\",\n            &config\n                .xkb_key_switch_mod\n                .as_deref()\n                .map_or_else(|| \"-1\".to_string(), |m| format!(\"{m}_L\")),\n        ),\n        (\n            \"$HYPRSHELL_SWTICH_XKB_MOD_R$\",\n            &config\n                .xkb_key_switch_mod\n                .as_deref()\n                .map_or_else(|| \"-1\".to_string(), |m| format!(\"{m}_R\")),\n        ),\n        (\n            \"$HYPRSHELL_OVERVIEW_MOD$\",\n            config.xkb_key_overview_mod.as_deref().unwrap_or(\"\"),\n        ),\n        (\n            \"$HYPRSHELL_OVERVIEW_KEY$\",\n            config.xkb_key_overview_key.as_deref().unwrap_or(\"\"),\n        ),\n        (\n            \"$HYPRSHELL_SWITCH_KEY$\",\n            config.xkb_key_switch_key.as_deref().unwrap_or(\"\"),\n        ),\n        (\n            \"$HYPRSHELL_OPEN_OVERVIEW$\",\n            &generate_transfer(&TransferType::OpenOverview),\n        ),\n        (\n            \"$HYPRSHELL_CLOSE$\",\n            &generate_transfer(&TransferType::CloseSwitch),\n        ),\n        (\n            \"$HYPRSHELL_OPEN_SWITCH$\",\n            &generate_transfer(&TransferType::OpenSwitch(OpenSwitch { reverse: false })),\n        ),\n        (\n            \"$HYPRSHELL_OPEN_SWITCH_REVERSE$\",\n            &generate_transfer(&TransferType::OpenSwitch(OpenSwitch { reverse: true })),\n        ),\n    ] {\n        buffer = buffer.replace(replace.0, replace.1);\n    }\n    buffer.push('\\n');\n    drop(defs_file);\n    let mut defs_file = OpenOptions::new()\n        .write(true)\n        .truncate(true)\n        .open(&defs)\n        .with_context(|| format!(\"unable to open defs file: {}\", defs.display()))?;\n    defs_file\n        .write_all(buffer.as_bytes())\n        .context(\"unable to write defs file\")?;\n    // tracing::trace!(\"Updated defs file: {defs:?}, content:\\n{buffer}\");\n    Ok(())\n}\n"
  },
  {
    "path": "crates/hyprland-plugin/src/extract.rs",
    "content": "use crate::ASSET_ZIP_52;\nuse crate::ASSET_ZIP_54;\nuse std::fs::File;\nuse std::io::{Cursor, copy};\nuse tempfile::TempDir;\nuse tracing::trace;\nuse zip::ZipArchive;\n\npub fn extract_plugin(version: &semver::Version) -> anyhow::Result<TempDir> {\n    let asset = if version >= &semver::Version::new(0, 54, 0) {\n        tracing::info!(\"Extracting plugin assets for version 54\");\n        ASSET_ZIP_54\n    } else {\n        tracing::info!(\"Extracting plugin assets for version 52\");\n        ASSET_ZIP_52\n    };\n    let tmp_dir = TempDir::with_suffix(env!(\"CARGO_PKG_NAME\")).expect(\"create tempdir failed\");\n    let mut archive = ZipArchive::new(Cursor::new(asset)).expect(\"failed to read zip\");\n\n    let mut counter = 0;\n    for i in 0..archive.len() {\n        let mut file = archive.by_index(i)?;\n        let out_path = tmp_dir.path().join(file.name());\n\n        if let Some(p) = out_path.parent() {\n            std::fs::create_dir_all(p)?;\n        }\n        let mut outfile = File::create(&out_path)?;\n        copy(&mut file, &mut outfile)?;\n        counter += 1;\n    }\n\n    trace!(\"extracted {} files\", counter);\n    Ok(tmp_dir)\n}\n"
  },
  {
    "path": "crates/hyprland-plugin/src/lib.rs",
    "content": "mod build;\nmod configure;\nmod extract;\nmod test;\n\nuse anyhow::Context;\nuse tracing::{debug_span, trace};\n\npub const PLUGIN_NAME: &str = env!(\"CARGO_PKG_NAME\");\npub const PLUGIN_AUTHOR: &str = env!(\"CARGO_PKG_AUTHORS\");\npub const PLUGIN_DESC: &str = env!(\"CARGO_PKG_DESCRIPTION\");\npub const PLUGIN_VERSION: &str = env!(\"CARGO_PKG_VERSION\");\npub const PLUGIN_OUTPUT_PATH: &str = \"/tmp/hyprshell.so\";\n\nstatic ASSET_ZIP_52: &[u8] = include_bytes!(concat!(env!(\"OUT_DIR\"), \"/52/plugin.zip\"));\nstatic ASSET_ZIP_54: &[u8] = include_bytes!(concat!(env!(\"OUT_DIR\"), \"/54/plugin.zip\"));\n\npub use configure::PluginConfig;\n\npub fn generate(config: &PluginConfig, version: &semver::Version) -> anyhow::Result<()> {\n    let _span = debug_span!(\"generate\").entered();\n\n    trace!(\"extracting plugin from zip\");\n    let dir = extract::extract_plugin(version).context(\"Failed to extract plugin\")?;\n    trace!(\"configuring defs file\");\n    configure::configure(&dir, config, version).context(\"unable to configure defs file\")?;\n    trace!(\"building plugin\");\n    build::build(&dir).context(\"Failed to build plugin\")?;\n    Ok(())\n}\n"
  },
  {
    "path": "crates/hyprland-plugin/src/test.rs",
    "content": "#[cfg(test)]\nmod tests {\n    use crate::{PluginConfig, build, configure, extract};\n    use tracing::info;\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn build_plugin() {\n        let test_config = PluginConfig {\n            xkb_key_switch_mod: Some(Box::from(\"XKB_KEY_Alt\")),\n            xkb_key_switch_key: Some(Box::from(\"tab\")),\n            xkb_key_overview_mod: Some(Box::from(\"XKB_KEY_Super\")),\n            xkb_key_overview_key: Some(Box::from(\"tab\")),\n        };\n\n        info!(\"extracting plugin from zip\");\n        let dir = extract::extract_plugin(&semver::Version::new(0, 54, 0))\n            .expect(\"Failed to extract plugin\");\n        info!(\"configuring defs file\");\n        configure::configure(&dir, &test_config, &semver::Version::new(0, 54, 0))\n            .expect(\"unable to configure defs file\");\n        info!(\"building plugin\");\n        build::build(&dir).expect(\"Failed to build plugin\");\n    }\n}\n"
  },
  {
    "path": "crates/launcher-lib/Cargo.toml",
    "content": "[package]\nname = \"hyprshell-launcher-lib\"\ndocumentation = \"https://docs.rs/hyprshell-launcher-lib\"\nversion = \"4.9.5\"\nedition.workspace = true\ndescription.workspace = true\nlicense.workspace = true\nauthors.workspace = true\nkeywords.workspace = true\nrepository.workspace = true\nrust-version.workspace = true\n\n[dependencies]\nanyhow.workspace = true\ntracing.workspace = true\nrelm4.workspace = true\ngtk4-layer-shell.workspace = true\ncore-lib.workspace = true\nexec-lib.workspace = true\nserde_json.workspace = true\nasync-channel.workspace = true\nconfig-lib.workspace = true\nrink-core = { version = \"0.8.0\", optional = true, features = [\"bundle-files\"] }\nchrono = \"0.4.41\"\n\n[features]\ncalc = [\"dep:rink-core\"]\n\n[lints]\nworkspace = true\n\n[dev-dependencies]\ntest-log.workspace = true\n"
  },
  {
    "path": "crates/launcher-lib/src/close.rs",
    "content": "use crate::plugins::PluginReturn;\nuse crate::{LauncherData, plugins};\nuse core_lib::transfer::Identifier;\nuse gtk4_layer_shell::{KeyboardMode, LayerShell};\nuse relm4::adw::gtk::prelude::*;\nuse relm4::adw::gtk::{ApplicationWindow, Button, Entry, glib};\nuse relm4::gtk;\nuse std::collections::HashMap;\nuse std::time::{Duration, Instant};\nuse tracing::{debug_span, trace, warn};\n\nconst ANIMATE_LAUNCH_MS: u64 = 500;\n\npub fn close_launcher_by_char(data: &mut LauncherData, char: Option<char>) {\n    let _span = debug_span!(\"close_launcher_by_char\").entered();\n    if let Some(char) = char {\n        data.window.set_keyboard_mode(KeyboardMode::None);\n        trace!(\"Closing launcher with char: {}\", char);\n        if let Some(iden) = match char {\n            '0'..='9' => char\n                .to_digit(10)\n                .and_then(|a| data.sorted_matches.get(a as usize)),\n            _ => data.static_matches.get(&char),\n        } {\n            close_launcher(data, iden);\n        } else {\n            warn!(\"No match found for char: {}\", char);\n            close_window(&data.entry, &data.window);\n        }\n    } else {\n        close_window(&data.entry, &data.window);\n    }\n}\n\npub fn close_launcher_by_iden(data: &mut LauncherData, iden: &Identifier) {\n    let _span = debug_span!(\"close_launcher_by_iden\").entered();\n    data.window.set_keyboard_mode(KeyboardMode::None);\n    trace!(\"Closing launcher with iden: {iden:?}\");\n\n    close_launcher(data, iden);\n}\n\nfn close_launcher(data: &LauncherData, iden: &Identifier) {\n    let span = debug_span!(\"close_launcher\");\n    let _span = span.enter();\n    let instant = Instant::now();\n\n    let PluginReturn { show_animation } = plugins::launch(\n        iden,\n        &data.entry.text(),\n        data.config.default_terminal.as_deref(),\n        &data.config.data_dir,\n    );\n    if show_animation {\n        trace!(\n            \"starting timeout({ANIMATE_LAUNCH_MS}ms) animation after {:?} time\",\n            instant.elapsed()\n        );\n        data.entry.set_editable(false);\n        show_launch(&data.results_items, &data.plugins_items, iden);\n        let window = data.window.clone();\n        let entry = data.entry.clone();\n        let span = span.clone();\n        glib::timeout_add_local_once(Duration::from_millis(ANIMATE_LAUNCH_MS), move || {\n            let _span = span.entered();\n            close_window(&entry, &window);\n            trace!(\"closed launcher after {:?} time\", instant.elapsed());\n        });\n    } else {\n        close_window(&data.entry, &data.window);\n    }\n}\n\nfn close_window(entry: &Entry, window: &ApplicationWindow) {\n    trace!(\"Hiding window (launcher) {:?}\", window.id());\n    window.set_visible(false);\n    entry.set_text(\"\");\n}\n\nfn show_launch(\n    results_items: &HashMap<Identifier, (gtk::Box, HashMap<Identifier, gtk::ListBoxRow>)>,\n    plugins_items: &HashMap<Identifier, Button>,\n    open_iden: &Identifier,\n) {\n    for (iden, child) in results_items {\n        if iden.plugin == open_iden.plugin && iden.data == open_iden.data {\n            for (iden_2, row) in &child.1 {\n                if iden_2.data_additional == open_iden.data_additional {\n                    row.add_css_class(\"launch\");\n                    return;\n                }\n            }\n            // only add if no child with additional data was found\n            child.0.add_css_class(\"launch\");\n            return;\n        }\n    }\n    for (iden, child) in plugins_items {\n        if iden.plugin == open_iden.plugin && iden.data == open_iden.data {\n            child.add_css_class(\"launch\");\n            return;\n        }\n    }\n}\n"
  },
  {
    "path": "crates/launcher-lib/src/create.rs",
    "content": "use crate::global::{LauncherConfig, LauncherData};\nuse crate::plugins;\nuse crate::plugins::get_static_options_chars;\nuse async_channel::Sender;\nuse config_lib::{Launcher, Modifier};\nuse core_lib::transfer::{CloseOverviewConfig, Direction, SwitchOverviewConfig, TransferType};\nuse core_lib::{LAUNCHER_NAMESPACE, WarnWithDetails};\nuse gtk4_layer_shell::{Edge, Layer, LayerShell};\nuse relm4::adw::gtk::gdk::{Key, ModifierType};\nuse relm4::adw::gtk::glib::{ControlFlow, Propagation};\nuse relm4::adw::gtk::prelude::*;\nuse relm4::adw::gtk::{\n    Application, ApplicationWindow, Entry, EventControllerKey, ListBox, PropagationPhase,\n    SelectionMode,\n};\nuse relm4::adw::gtk::{Orientation, glib};\nuse relm4::gtk;\nuse std::collections::HashMap;\nuse std::path::{Path, PathBuf};\nuse tracing::{debug, debug_span, trace};\n\npub fn create_windows_overview_launcher_window(\n    app: &Application,\n    launcher: &Launcher,\n    data_dir: &Path,\n    event_sender: &Sender<TransferType>,\n) -> anyhow::Result<LauncherData> {\n    let _span = debug_span!(\"create_windows_overview_launcher_window\").entered();\n\n    let main_vbox = ListBox::builder()\n        .css_classes([\"launcher\"])\n        .width_request(i32::try_from(launcher.width)?)\n        .selection_mode(SelectionMode::None)\n        .build();\n\n    let entry = Entry::builder().css_classes([\"launcher-input\"]).build();\n    let event_sender_2 = event_sender.clone();\n    entry.connect_changed(move |e| {\n        launcher_entry_text_change(e.text().to_string(), &event_sender_2);\n    });\n    main_vbox.append(&entry);\n\n    let results = gtk::Box::builder()\n        .orientation(Orientation::Vertical)\n        .css_classes([\"launcher-results\"])\n        .spacing(3)\n        .build();\n    main_vbox.append(&results);\n\n    let plugin_box = gtk::Box::builder()\n        .orientation(Orientation::Horizontal)\n        .css_classes([\"launcher-plugins\"])\n        .spacing(4)\n        .build();\n    main_vbox.append(&plugin_box);\n\n    let window = ApplicationWindow::builder()\n        .css_classes([\"window\"])\n        .application(app)\n        .child(&main_vbox)\n        .default_height(10)\n        .default_width(10)\n        .build();\n    window.init_layer_shell();\n    window.set_namespace(Some(LAUNCHER_NAMESPACE));\n    window.set_layer(Layer::Top);\n    window.set_anchor(Edge::Top, true);\n    window.set_margin(Edge::Top, 0);\n    window.set_exclusive_zone(-1);\n\n    let event_controller = EventControllerKey::new();\n    let plugin_keys = get_static_options_chars(&launcher.plugins);\n    let event_sender_4 = event_sender.clone();\n    let entry_2 = entry.clone();\n    let results_2 = results.clone();\n    let launch_modifier = launcher.launch_modifier;\n    event_controller.connect_key_pressed(move |_, key, _, modt| {\n        trace!(\"input: {key:?}\");\n        handle_key(\n            &entry_2,\n            key,\n            // open_modifier,\n            modt,\n            &plugin_keys,\n            launch_modifier,\n            &results_2,\n            &event_sender_4,\n        )\n    });\n    event_controller.set_propagation_phase(PropagationPhase::Capture);\n    entry.add_controller(event_controller);\n    let entry_2 = entry.clone();\n    let window_2 = window.clone();\n    glib::timeout_add_local(std::time::Duration::from_millis(200), move || {\n        if window_2.is_visible() {\n            entry_2.grab_focus_without_selecting(); // ensure that the entry is always focused\n        }\n        ControlFlow::Continue\n    });\n\n    debug!(\"Created launcher window ({})\", window.id());\n\n    launcher_entry_text_change(String::new(), event_sender);\n    plugins::init_calc_context();\n\n    Ok(LauncherData {\n        config: LauncherConfig {\n            default_terminal: launcher.default_terminal.clone(),\n            max_items: launcher.max_items,\n            launch_modifier: launcher.launch_modifier,\n            show_when_empty: launcher.show_when_empty,\n            width: launcher.width,\n            data_dir: PathBuf::from(data_dir).into_boxed_path(),\n            plugins: launcher.plugins.clone(),\n        },\n        window,\n        entry,\n        results_box: results,\n        results_items: HashMap::new(),\n        plugins_box: plugin_box,\n        plugins_items: HashMap::new(),\n        sorted_matches: vec![],\n        static_matches: HashMap::new(),\n    })\n}\n\nfn launcher_entry_text_change(text: String, event_sender: &Sender<TransferType>) {\n    event_sender\n        .send_blocking(TransferType::Type(text))\n        .warn_details(\"unable to send\");\n}\n\n#[allow(clippy::too_many_arguments, clippy::too_many_lines)]\nfn handle_key(\n    entry: &Entry,\n    key: Key,\n    modt: ModifierType,\n    plugin_keys: &[Key],\n    launch_modifier: Modifier,\n    results: &gtk::Box,\n    event_sender: &Sender<TransferType>,\n) -> Propagation {\n    let launch_mod = match launch_modifier {\n        Modifier::Ctrl => modt == ModifierType::CONTROL_MASK,\n        Modifier::Alt => modt == ModifierType::ALT_MASK,\n        Modifier::Super => modt == ModifierType::SUPER_MASK,\n        Modifier::None => false,\n    };\n    // tracing::trace!(\n    //     \"key: {}{:?}, mods: {:?}, launch_mod: {}, launch_modifier: {}\",\n    //     key,\n    //     key,\n    //     modt,\n    //     launch_mod,\n    //     launch_modifier\n    // );\n    if launch_mod && plugin_keys.contains(&key) {\n        if let Some(ch) = key.name().unwrap_or_default().to_string().pop() {\n            event_sender\n                .send_blocking(TransferType::CloseOverview(\n                    CloseOverviewConfig::LauncherPress(ch),\n                ))\n                .warn_details(\"unable to send\");\n        }\n        return Propagation::Stop;\n    }\n\n    match (launch_mod, key) {\n        (_, Key::Escape) => {\n            event_sender\n                .send_blocking(TransferType::Exit)\n                .warn_details(\"unable to send\");\n            Propagation::Stop\n        }\n        (_, Key::Tab) => {\n            event_sender\n                .send_blocking(TransferType::SwitchOverview(SwitchOverviewConfig {\n                    workspace: false,\n                    direction: Direction::Right,\n                }))\n                .warn_details(\"unable to send\");\n            Propagation::Stop\n        }\n        (_, Key::ISO_Left_Tab | Key::grave | Key::dead_grave) => {\n            event_sender\n                .send_blocking(TransferType::SwitchOverview(SwitchOverviewConfig {\n                    workspace: false,\n                    direction: Direction::Left,\n                }))\n                .warn_details(\"unable to send\");\n            Propagation::Stop\n        }\n        (true, Key::h) => {\n            event_sender\n                .send_blocking(TransferType::SwitchOverview(SwitchOverviewConfig {\n                    workspace: true,\n                    direction: Direction::Left,\n                }))\n                .warn_details(\"unable to send\");\n            Propagation::Stop\n        }\n        (true, Key::l) => {\n            event_sender\n                .send_blocking(TransferType::SwitchOverview(SwitchOverviewConfig {\n                    workspace: true,\n                    direction: Direction::Right,\n                }))\n                .warn_details(\"unable to send\");\n            Propagation::Stop\n        }\n        (_, Key::Left) => {\n            if !entry.text().is_empty() {\n                // allow to use in text in launcher\n                return Propagation::Proceed;\n            }\n            event_sender\n                .send_blocking(TransferType::SwitchOverview(SwitchOverviewConfig {\n                    workspace: true,\n                    direction: Direction::Left,\n                }))\n                .warn_details(\"unable to send\");\n            Propagation::Stop\n        }\n        (_, Key::Right) => {\n            if !entry.text().is_empty() {\n                // allow to use in text in launcher\n                return Propagation::Proceed;\n            }\n            event_sender\n                .send_blocking(TransferType::SwitchOverview(SwitchOverviewConfig {\n                    workspace: true,\n                    direction: Direction::Right,\n                }))\n                .warn_details(\"unable to send\");\n            Propagation::Stop\n        }\n        (_, Key::Up) | (true, Key::k) => {\n            event_sender\n                .send_blocking(TransferType::SwitchOverview(SwitchOverviewConfig {\n                    workspace: true,\n                    direction: Direction::Up,\n                }))\n                .warn_details(\"unable to send\");\n            Propagation::Stop\n        }\n        (_, Key::Down) | (true, Key::j) => {\n            event_sender\n                .send_blocking(TransferType::SwitchOverview(SwitchOverviewConfig {\n                    workspace: true,\n                    direction: Direction::Down,\n                }))\n                .warn_details(\"unable to send\");\n            Propagation::Stop\n        }\n        (_, Key::Return) => {\n            if results.first_child().is_some() {\n                event_sender\n                    .send_blocking(TransferType::CloseOverview(CloseOverviewConfig::None))\n                    .warn_details(\"unable to send\");\n            }\n            Propagation::Stop\n        }\n        (true, Key::_1) => {\n            if results.observe_children().into_iter().len() > 1 {\n                event_sender\n                    .send_blocking(TransferType::CloseOverview(\n                        CloseOverviewConfig::LauncherPress('1'),\n                    ))\n                    .warn_details(\"unable to send\");\n            }\n            Propagation::Stop\n        }\n        (true, Key::_2) => {\n            if results.observe_children().into_iter().len() > 2 {\n                event_sender\n                    .send_blocking(TransferType::CloseOverview(\n                        CloseOverviewConfig::LauncherPress('2'),\n                    ))\n                    .warn_details(\"unable to send\");\n            }\n            Propagation::Stop\n        }\n        (true, Key::_3) => {\n            if results.observe_children().into_iter().len() > 3 {\n                event_sender\n                    .send_blocking(TransferType::CloseOverview(\n                        CloseOverviewConfig::LauncherPress('3'),\n                    ))\n                    .warn_details(\"unable to send\");\n            }\n            Propagation::Stop\n        }\n        (true, Key::_4) => {\n            if results.observe_children().into_iter().len() > 4 {\n                event_sender\n                    .send_blocking(TransferType::CloseOverview(\n                        CloseOverviewConfig::LauncherPress('4'),\n                    ))\n                    .warn_details(\"unable to send\");\n            }\n            Propagation::Stop\n        }\n        (true, Key::_5) => {\n            if results.observe_children().into_iter().len() > 5 {\n                event_sender\n                    .send_blocking(TransferType::CloseOverview(\n                        CloseOverviewConfig::LauncherPress('5'),\n                    ))\n                    .warn_details(\"unable to send\");\n            }\n            Propagation::Stop\n        }\n        (true, Key::_6) => {\n            if results.observe_children().into_iter().len() > 6 {\n                event_sender\n                    .send_blocking(TransferType::CloseOverview(\n                        CloseOverviewConfig::LauncherPress('6'),\n                    ))\n                    .warn_details(\"unable to send\");\n            }\n            Propagation::Stop\n        }\n        (true, Key::_7) => {\n            if results.observe_children().into_iter().len() > 7 {\n                event_sender\n                    .send_blocking(TransferType::CloseOverview(\n                        CloseOverviewConfig::LauncherPress('7'),\n                    ))\n                    .warn_details(\"unable to send\");\n            }\n            Propagation::Stop\n        }\n        (true, Key::_8) => {\n            if results.observe_children().into_iter().len() > 8 {\n                event_sender\n                    .send_blocking(TransferType::CloseOverview(\n                        CloseOverviewConfig::LauncherPress('8'),\n                    ))\n                    .warn_details(\"unable to send\");\n            }\n            Propagation::Stop\n        }\n        (true, Key::_9) => {\n            if results.observe_children().into_iter().len() > 9 {\n                event_sender\n                    .send_blocking(TransferType::CloseOverview(\n                        CloseOverviewConfig::LauncherPress('9'),\n                    ))\n                    .warn_details(\"unable to send\");\n            }\n            Propagation::Stop\n        }\n        _ => Propagation::Proceed,\n    }\n}\n"
  },
  {
    "path": "crates/launcher-lib/src/css.rs",
    "content": "use anyhow::Context;\nuse relm4::adw::gtk::gdk::Display;\nuse relm4::adw::gtk::{\n    CssProvider, STYLE_PROVIDER_PRIORITY_USER, style_context_add_provider_for_display,\n};\n\npub fn get_css() -> anyhow::Result<()> {\n    let provider_app = CssProvider::new();\n    provider_app.load_from_string(include_str!(\"styles.css\"));\n    style_context_add_provider_for_display(\n        &Display::default().context(\"Could not connect to a display.\")?,\n        &provider_app,\n        STYLE_PROVIDER_PRIORITY_USER,\n    );\n    Ok(())\n}\n"
  },
  {
    "path": "crates/launcher-lib/src/debug.rs",
    "content": "#![allow(clippy::print_stderr, clippy::print_stdout)]\n\nuse crate::plugins::get_sortable_launch_options;\nuse crate::reload_applications_desktop_entries_map;\nuse config_lib::Plugins;\nuse core_lib::WarnWithDetails;\nuse core_lib::default::reload_default_files;\nuse std::path::Path;\nuse tracing::debug;\n\npub fn get_matches(plugins: &Plugins, text: &str, all_items: bool, max_items: u8, data_dir: &Path) {\n    reload_default_files().warn_details(\"Failed to reload default files\");\n    reload_applications_desktop_entries_map()\n        .warn_details(\"Failed to reload applications desktop entries map\");\n    debug!(\"text: {text}\");\n    let options = get_sortable_launch_options(plugins, text, data_dir);\n    println!(\"{} options returned\", options.len());\n    let options = if all_items {\n        options\n    } else {\n        debug!(\"shorting options to {max_items}\");\n        options.into_iter().take(max_items as usize).collect()\n    };\n    for option in options {\n        println!(\n            \"{}: {}; {} desktop actions\",\n            option.name,\n            option.score,\n            option.details_menu.len()\n        );\n        debug!(\"{option:?}\");\n    }\n}\n"
  },
  {
    "path": "crates/launcher-lib/src/global.rs",
    "content": "use config_lib::{Modifier, Plugins};\nuse core_lib::transfer::Identifier;\nuse relm4::gtk;\nuse std::collections::HashMap;\nuse std::path::Path;\n\n#[derive(Debug)]\npub struct LauncherData {\n    pub config: LauncherConfig,\n    pub window: gtk::ApplicationWindow,\n    pub entry: gtk::Entry,\n    pub results_box: gtk::Box,\n    pub results_items: HashMap<Identifier, (gtk::Box, HashMap<Identifier, gtk::ListBoxRow>)>,\n    pub plugins_box: gtk::Box,\n    pub plugins_items: HashMap<Identifier, gtk::Button>,\n    pub sorted_matches: Vec<Identifier>,\n    pub static_matches: HashMap<char, Identifier>,\n}\n\n#[derive(Debug)]\npub struct LauncherConfig {\n    pub default_terminal: Option<Box<str>>,\n    pub max_items: u8,\n    pub launch_modifier: Modifier,\n    pub show_when_empty: bool,\n    pub width: u32,\n    pub data_dir: Box<Path>,\n    pub plugins: Plugins,\n}\n"
  },
  {
    "path": "crates/launcher-lib/src/lib.rs",
    "content": "mod close;\nmod create;\nmod css;\npub mod debug;\nmod global;\nmod open;\nmod plugins;\nmod stop;\nmod update;\n\npub use close::{close_launcher_by_char, close_launcher_by_iden};\npub use create::create_windows_overview_launcher_window;\npub use css::get_css;\npub use global::LauncherData;\npub use open::open_launcher;\npub use plugins::{get_applications_stored_runs, reload_applications_desktop_entries_map};\npub use stop::stop_launcher;\npub use update::update_launcher;\n"
  },
  {
    "path": "crates/launcher-lib/src/open.rs",
    "content": "use crate::LauncherData;\nuse gtk4_layer_shell::{KeyboardMode, LayerShell};\nuse relm4::adw::gtk::glib;\nuse relm4::adw::gtk::prelude::*;\nuse tracing::{debug_span, trace};\n\npub fn open_launcher(data: &LauncherData) {\n    let _span = debug_span!(\"open_launcher\").entered();\n    // check if already open\n    if data.window.get_visible() {\n        return;\n    }\n\n    trace!(\"Showing window {:?}\", data.window.id());\n    data.window.set_monitor(None);\n    data.window.set_visible(true);\n\n    trace!(\"Resetting launcher data\");\n    data.entry.set_editable(true);\n    data.entry.set_text(\"\");\n    data.window.set_keyboard_mode(KeyboardMode::Exclusive);\n    let window = data.window.clone();\n    glib::timeout_add_local(std::time::Duration::from_millis(300), move || {\n        window.set_keyboard_mode(KeyboardMode::OnDemand);\n        glib::ControlFlow::Break\n    });\n\n    data.window.grab_focus();\n    data.entry.grab_focus();\n}\n"
  },
  {
    "path": "crates/launcher-lib/src/plugins/actions.rs",
    "content": "use crate::plugins::{Identifier, PluginNames, PluginReturn, SortableLaunchOption};\nuse config_lib::actions::ToAction;\nuse config_lib::{ActionsPluginActionCustom, ActionsPluginConfig};\nuse core_lib::WarnWithDetails;\nuse tracing::{info, trace};\n\npub fn get_actions_options(\n    matches: &mut Vec<SortableLaunchOption>,\n    text: &str,\n    config: &ActionsPluginConfig,\n) {\n    if text.is_empty() {\n        return;\n    }\n    let lower_text = text.to_ascii_lowercase();\n\n    let actions = config\n        .actions\n        .iter()\n        .cloned()\n        .map(ToAction::to_action)\n        .collect::<Vec<ActionsPluginActionCustom>>();\n\n    for action in actions {\n        if action.names.iter().any(|name| {\n            // searching ki should show kill, searching kill kitty should show kill\n            name.to_ascii_lowercase().starts_with(&lower_text)\n                || lower_text.starts_with(&name.to_ascii_lowercase())\n        }) || text.eq(\"actions\")\n        {\n            let name = action\n                .names\n                .iter()\n                .find(|name| {\n                    name.to_ascii_lowercase().starts_with(&lower_text)\n                        || lower_text.starts_with(&name.to_ascii_lowercase())\n                })\n                .expect(\"cant happen we already searched\");\n            let name_lower = name.to_ascii_lowercase();\n            let mut command = action.command.clone();\n            let mut grayed = false;\n            if command.contains(\"{}\") {\n                trace!(\n                    \"Action command contains '{{}}', replacing <{text}> with stripped ({name_lower}) text\"\n                );\n                let stripped_text = {\n                    let trimmed = text.trim_start_matches(&*name_lower).trim();\n                    if trimmed.len() == text.len() {\n                        \"\"\n                    } else {\n                        trimmed\n                    }\n                };\n                if stripped_text.is_empty() {\n                    grayed = true;\n                }\n                command = Box::from(command.replace(\"{}\", stripped_text));\n            }\n            trace!(\"Added action option: {}\", command);\n            matches.push(SortableLaunchOption {\n                icon: Some(action.icon),\n                name: name.clone(),\n                details: action.details,\n                details_long: Some(command.clone()),\n                score: 30,\n                grayed,\n                iden: Identifier::data(PluginNames::Actions, command),\n                details_menu: vec![],\n            });\n        }\n    }\n}\n\npub fn run_action(data: Option<&str>) -> PluginReturn {\n    if let Some(data) = data {\n        if cfg!(debug_assertions) && std::env::var(\"HYPRSHELL_RUN_ACTIONS_IN_DEBUG\").is_err() {\n            info!(\"Not running action: {} (debug mode)\", data);\n        } else {\n            info!(\"Running action: {}\", data);\n            exec_lib::run::run_program(data, None, false, None)\n                .warn_details(\"Failed to run command\");\n        }\n    }\n\n    PluginReturn {\n        show_animation: true,\n    }\n}\n"
  },
  {
    "path": "crates/launcher-lib/src/plugins/applications/data.rs",
    "content": "use anyhow::Context;\nuse chrono::{DateTime, Datelike, Utc};\nuse serde_json::from_reader;\nuse std::collections::HashMap;\nuse std::fs::OpenOptions;\nuse std::path::{Path, PathBuf};\nuse tracing::{debug, trace, warn};\n\npub fn save_run(desktop_file: &Path, data_dir: &Path) -> anyhow::Result<()> {\n    let file = get_current_week(data_dir);\n    let mut data = if file.exists() {\n        let file = OpenOptions::new()\n            .read(true)\n            .open(&file)\n            .context(\"Failed to open data file\")?;\n        from_reader(file).unwrap_or_else(|_| serde_json::json!({}))\n    } else {\n        // create the file and folder\n        std::fs::create_dir_all(\n            file.parent()\n                .context(\"failed to create directory for cache file\")?,\n        )\n        .context(\"Failed to create data directory\")?;\n        serde_json::json!({})\n    };\n\n    data[&*desktop_file.to_string_lossy()] = serde_json::json!(\n        data.get(&*desktop_file.to_string_lossy())\n            .map_or(1, |v| v.as_i64().unwrap_or(0) + 1)\n    );\n\n    trace!(\"Cache saved to {file:?} (added {desktop_file:?})\");\n    let file = OpenOptions::new()\n        .write(true)\n        .create(true)\n        .truncate(true)\n        .open(&file)\n        .context(\"Failed to open data file for writing\")?;\n    serde_json::to_writer_pretty(file, &data).context(\"Failed to write to data file\")?;\n    Ok(())\n}\n\nfn get_current_week(data_dir: &Path) -> PathBuf {\n    PathBuf::from(data_dir)\n        .join(\"runs\")\n        .join(get_name_from_timestamp(0))\n}\n\nfn get_all_weeks(run_cache_weeks: u8, data_dir: &Path) -> Vec<Box<Path>> {\n    let mut weeks = Vec::new();\n    for week in 0..run_cache_weeks {\n        let path = PathBuf::from(data_dir)\n            .join(\"runs\")\n            .join(get_name_from_timestamp(week));\n        weeks.push(path.into_boxed_path());\n    }\n    weeks\n}\n\nfn get_name_from_timestamp(week: u8) -> Box<Path> {\n    let timestamp = Utc::now().timestamp() - (i64::from(week) * 7 * 24 * 60 * 60);\n    let datetime = DateTime::from_timestamp(timestamp, 0).expect(\"Invalid timestamp\");\n    Box::from(Path::new(&format!(\n        \"{}_{}.json\",\n        datetime.year(),\n        datetime.iso_week().week()\n    )))\n}\n\npub fn get_stored_runs(run_cache_weeks: u8, data_dir: &Path) -> HashMap<Box<Path>, u64> {\n    let mut runs = HashMap::new();\n\n    for week in get_all_weeks(run_cache_weeks, data_dir) {\n        let cache_data = if week.exists() {\n            match OpenOptions::new().read(true).open(&week) {\n                Ok(file) => from_reader(file).unwrap_or_else(|err| {\n                    warn!(\"Failed to open cache file: {week:?}\");\n                    debug!(\"Error: {err:?}\");\n                    serde_json::json!({})\n                }),\n                Err(err) => {\n                    warn!(\"Failed to open cache file: {week:?}\");\n                    debug!(\"Error: {err:?}\");\n                    serde_json::json!({})\n                }\n            }\n        } else {\n            serde_json::json!({})\n        };\n        if let Some(obj) = cache_data.as_object() {\n            for (path, runs_count) in obj {\n                runs.entry(PathBuf::from(path).into_boxed_path())\n                    .and_modify(|e| *e += runs_count.as_u64().unwrap_or(0))\n                    .or_insert_with(|| runs_count.as_u64().unwrap_or(0));\n            }\n        } else {\n            warn!(\"Cache data is not an object\");\n        }\n    }\n    runs\n}\n"
  },
  {
    "path": "crates/launcher-lib/src/plugins/applications/map.rs",
    "content": "use anyhow::Context;\nuse core_lib::default::get_all_desktop_files;\nuse core_lib::util::{ExecType, analyse_exec};\nuse std::path::Path;\nuse std::sync::{OnceLock, RwLock, RwLockReadGuard};\nuse tracing::{debug_span, trace, warn};\n\n#[derive(Debug, Clone)]\npub struct DesktopEntry {\n    pub name: Box<str>,\n    pub icon: Option<Box<Path>>,\n    pub keywords: Vec<Box<str>>,\n    pub exec_search: Box<str>,\n    pub exec: Box<str>,\n    pub exec_path: Option<Box<Path>>,\n    /// if launcher text exactly matches this it will be shown (use for flatpak / appimage / ...)\n    pub type_search: &'static str,\n    pub terminal: bool,\n    pub source: Box<Path>,\n    pub other: Vec<DesktopAction>,\n}\n\n#[derive(Debug, Clone)]\npub struct DesktopAction {\n    pub id: Box<str>,\n    pub name: Box<str>,\n    pub exec: Box<str>,\n}\n\nfn get_desktop_file_map() -> &'static RwLock<Vec<DesktopEntry>> {\n    static MAP_LOCK: OnceLock<RwLock<Vec<DesktopEntry>>> = OnceLock::new();\n    MAP_LOCK.get_or_init(|| RwLock::new(Vec::new()))\n}\n\npub fn get_all_desktop_entries<'a>() -> RwLockReadGuard<'a, Vec<DesktopEntry>> {\n    get_desktop_file_map()\n        .read()\n        .expect(\"Failed to lock desktop files mutex\")\n}\n\npub fn reload_desktop_entries_map() -> anyhow::Result<()> {\n    let _span = debug_span!(\"reload_desktop_entries_map\").entered();\n\n    let mut map = get_desktop_file_map()\n        .write()\n        .map_err(|_| anyhow::anyhow!(\"Failed to lock desktop file map\"))?;\n    map.clear();\n    for (entry, ini) in get_all_desktop_files()\n        .context(\"unable to get all desktop files\")?\n        .iter()\n    {\n        if let Some(section) = ini.get_section(\"Desktop Entry\") {\n            let r#type = section.get_first(\"Type\");\n            let no_display = section.get_first_as_boolean(\"NoDisplay\");\n            if r#type.as_deref() == Some(\"Application\") && no_display.is_none_or(|n| !n) {\n                let name = section.get_first(\"Name\");\n                let exec = section.get_first(\"Exec\");\n                let icon = section.get_first_as_path(\"Icon\");\n                let exec_path = section.get_first_as_path(\"Path\");\n                let terminal = section.get_first_as_boolean(\"Terminal\").unwrap_or(false);\n                let keywords = section.get_all(\"Keywords\").unwrap_or_else(Vec::new);\n\n                if let (Some(name), Some(exec)) = (name, exec) {\n                    let mut exec = String::from(exec);\n                    for replacement in [\"%f\", \"%F\", \"%u\", \"%U\"] {\n                        exec = exec.replace(replacement, \"\");\n                    }\n                    let (exec_search, type_search) = match analyse_exec(&exec) {\n                        ExecType::Flatpak(flatpak_identifier, _) => (flatpak_identifier, \"flatpak\"),\n                        ExecType::PWA(_, _) => (Box::from(\"\"), \"pwa\"),\n                        ExecType::FlatpakPWA(flatpak_identifier, _) => {\n                            (flatpak_identifier, \"flatpak-pwa\")\n                        }\n                        ExecType::AppImage(app_image_identifier, _) => {\n                            (app_image_identifier, \"appimage\")\n                        }\n                        ExecType::Absolute(exec_name, _) | ExecType::Relative(exec_name) => {\n                            (exec_name, \"\")\n                        }\n                    };\n\n                    let other = ini\n                        .sections()\n                        .iter()\n                        .filter_map(|(name, section)| {\n                            if name.starts_with(\"Desktop Action \") {\n                                let exec = section.get_first(\"Exec\")?;\n                                let name_hr = section.get_first(\"Name\")?;\n                                Some(DesktopAction {\n                                    id: name.clone(),\n                                    name: name_hr,\n                                    exec,\n                                })\n                            } else {\n                                None\n                            }\n                        })\n                        .collect();\n\n                    map.push(DesktopEntry {\n                        name,\n                        icon,\n                        keywords,\n                        exec_search,\n                        type_search,\n                        exec_path,\n                        terminal,\n                        exec: exec.into_boxed_str(),\n                        source: entry.path().into_boxed_path(),\n                        other,\n                    });\n                }\n            }\n        } else {\n            warn!(\n                \"Failed to find section 'Desktop Entry' in file: {}\",\n                entry.path().display()\n            );\n        }\n    }\n    drop(map);\n    trace!(\"filled launcher desktop file map\");\n    Ok(())\n}\n"
  },
  {
    "path": "crates/launcher-lib/src/plugins/applications/mod.rs",
    "content": "mod data;\nmod map;\nmod plugin;\n\npub use data::get_stored_runs;\npub use map::reload_desktop_entries_map;\npub use plugin::get_sortable_options;\npub use plugin::launch_option;\n"
  },
  {
    "path": "crates/launcher-lib/src/plugins/applications/plugin.rs",
    "content": "use crate::plugins::applications::data::{get_stored_runs, save_run};\nuse crate::plugins::applications::map::{DesktopEntry, get_all_desktop_entries};\nuse crate::plugins::{\n    DetailsMenuItem, Identifier, PluginNames, PluginReturn, SortableLaunchOption,\n};\nuse core_lib::WarnWithDetails;\nuse core_lib::util::{ExecType, analyse_exec};\nuse exec_lib::run::run_program;\nuse std::collections::HashMap;\nuse std::path::Path;\nuse tracing::{trace, warn};\n\n#[derive(Debug, Clone, Copy)]\nenum MatchType {\n    AppType = 1,\n    Keyword = 4,\n    ExecName = 10,\n    ExecExact = 15,\n    Name = 16,\n    Exact = 21,\n}\n\nimpl SortableLaunchOption {\n    fn from_desktop_entry(\n        entry: &DesktopEntry,\n        r#match: MatchType,\n        runs: &HashMap<Box<Path>, u64>,\n        show_execs: bool,\n        show_actions_submenu: bool,\n    ) -> Self {\n        let (details, details_long) = if show_execs {\n            match analyse_exec(&entry.exec) {\n                ExecType::Flatpak(a, b) => (format!(\"[Flatpak] {a}\").into_boxed_str(), Some(b)),\n                ExecType::PWA(a, b) => (format!(\"[PWA] {a}\").into_boxed_str(), Some(b)),\n                ExecType::FlatpakPWA(a, b) => {\n                    (format!(\"[Flatpak-PWA] {a}\").into_boxed_str(), Some(b))\n                }\n                ExecType::AppImage(a, b) => (format!(\"[AppImage] {a}\").into_boxed_str(), Some(b)),\n                ExecType::Absolute(a, b) => (a, Some(b)),\n                ExecType::Relative(a) => (a, None),\n            }\n        } else {\n            (Box::from(\"\"), None)\n        };\n\n        let runs = runs.get(&entry.source).unwrap_or(&0);\n        Self {\n            name: entry.name.clone(),\n            icon: entry.icon.clone(),\n            details,\n            details_long,\n            score: r#match as u64 + runs,\n            iden: Identifier::data(\n                PluginNames::Applications,\n                Box::from(entry.source.to_string_lossy()),\n            ),\n            grayed: false,\n            details_menu: if show_actions_submenu {\n                entry\n                    .other\n                    .iter()\n                    .map(|action| DetailsMenuItem {\n                        text: action.name.clone(),\n                        exec: action.exec.clone(),\n                        iden: Identifier::data_additional(\n                            PluginNames::Applications,\n                            Box::from(entry.source.to_string_lossy()),\n                            action.id.clone(),\n                        ),\n                    })\n                    .collect()\n            } else {\n                vec![]\n            },\n        }\n    }\n}\n\npub fn get_sortable_options(\n    matches: &mut Vec<SortableLaunchOption>,\n    text: &str,\n    run_cache_weeks: u8,\n    show_execs: bool,\n    show_actions_submenu: bool,\n    data_dir: &Path,\n) {\n    let entries = get_all_desktop_entries();\n    let runs = get_stored_runs(run_cache_weeks, data_dir);\n\n    let mut count = 0;\n    if text.is_empty() {\n        for entry in entries.iter() {\n            matches.push(SortableLaunchOption::from_desktop_entry(\n                entry,\n                MatchType::Exact,\n                &runs,\n                show_execs,\n                show_actions_submenu,\n            ));\n            count += 1;\n        }\n        trace!(\"Added {count} applications to matches\");\n        return;\n    }\n\n    let lower_text = text.to_ascii_lowercase();\n    for entry in entries.iter() {\n        let opt = if entry.name.to_ascii_lowercase().contains(&lower_text) {\n            if entry.name.to_ascii_lowercase().starts_with(&lower_text) {\n                Some(SortableLaunchOption::from_desktop_entry(\n                    entry,\n                    MatchType::Exact,\n                    &runs,\n                    show_execs,\n                    show_actions_submenu,\n                ))\n            } else {\n                Some(SortableLaunchOption::from_desktop_entry(\n                    entry,\n                    MatchType::Name,\n                    &runs,\n                    show_execs,\n                    show_actions_submenu,\n                ))\n            }\n        } else if entry.exec_search.to_ascii_lowercase().contains(&lower_text) {\n            if entry\n                .exec_search\n                .to_ascii_lowercase()\n                .starts_with(&lower_text)\n            {\n                Some(SortableLaunchOption::from_desktop_entry(\n                    entry,\n                    MatchType::ExecExact,\n                    &runs,\n                    show_execs,\n                    show_actions_submenu,\n                ))\n            } else {\n                Some(SortableLaunchOption::from_desktop_entry(\n                    entry,\n                    MatchType::ExecName,\n                    &runs,\n                    show_execs,\n                    show_actions_submenu,\n                ))\n            }\n        } else if entry\n            .keywords\n            .iter()\n            .any(|k| k.to_ascii_lowercase().starts_with(&lower_text))\n        {\n            Some(SortableLaunchOption::from_desktop_entry(\n                entry,\n                MatchType::Keyword,\n                &runs,\n                show_execs,\n                show_actions_submenu,\n            ))\n        } else if entry.type_search.eq(&lower_text) {\n            Some(SortableLaunchOption::from_desktop_entry(\n                entry,\n                MatchType::AppType,\n                &runs,\n                show_execs,\n                show_actions_submenu,\n            ))\n        } else {\n            None\n        };\n\n        // push only if not already in matches\n        if let Some(opt) = opt\n            && !matches.iter().any(|m| {\n                m.name == opt.name && m.details == opt.details && m.details_long == opt.details_long\n            })\n        {\n            matches.push(opt);\n            count += 1;\n        }\n    }\n    drop(entries);\n    trace!(\"Added {count} applications to matches\");\n}\npub fn launch_option(\n    data: Option<&str>,\n    data_additional: Option<&str>,\n    default_terminal: Option<&str>,\n    data_dir: &Path,\n) -> PluginReturn {\n    let entries = get_all_desktop_entries();\n    if let Some(data) = data {\n        let entry = entries\n            .iter()\n            .find(|entry| data == entry.source.to_string_lossy());\n        if let Some(entry) = entry {\n            let exec = if let Some(section) = data_additional.as_ref() {\n                // find desktop action\n                if let Some(action) = entry.other.iter().find(|a| (*a.id).eq(&**section)) {\n                    action.exec.clone()\n                } else {\n                    warn!(\n                        \"Failed to find action {:?} in entry {:?}\",\n                        &section, entry.name\n                    );\n                    return PluginReturn {\n                        show_animation: false,\n                    };\n                }\n            } else {\n                entry.exec.clone()\n            };\n            run_program(\n                &exec,\n                entry.exec_path.as_deref(),\n                entry.terminal,\n                default_terminal,\n            )\n            .warn_details(\"Failed to run program\");\n            trace!(\"Saving run: {:?}\", entry.source);\n            save_run(&entry.source, data_dir).warn_details(\"Failed to cache run\");\n            return PluginReturn {\n                show_animation: true,\n            };\n        }\n        warn!(\"Failed to find entry for {data:?}|{data_additional:?}\");\n    }\n    drop(entries);\n    PluginReturn {\n        show_animation: false,\n    }\n}\n"
  },
  {
    "path": "crates/launcher-lib/src/plugins/calc.rs",
    "content": "use crate::plugins::{Identifier, PluginNames, SortableLaunchOption};\nuse core_lib::WarnWithDetails;\nuse relm4::adw::gtk::gdk::Display;\nuse relm4::adw::gtk::prelude::DisplayExt;\nuse std::path::Path;\nuse std::sync::{OnceLock, RwLock};\nuse tracing::{debug, trace};\n\nfn get_context() -> Option<&'static RwLock<rink_core::Context>> {\n    static MAP_LOCK: OnceLock<Option<RwLock<rink_core::Context>>> = OnceLock::new();\n    MAP_LOCK\n        .get_or_init(|| {\n            rink_core::simple_context()\n                .warn_details(\"unable to create calc context\")\n                .map(RwLock::new)\n        })\n        .as_ref()\n}\n\npub fn init_context() {\n    get_context();\n}\n\npub fn get_calc_options(matches: &mut Vec<SortableLaunchOption>, text: &str) {\n    if text.is_empty() {\n        return;\n    }\n\n    let Some(context_lock) = get_context() else {\n        return;\n    };\n    let Ok(mut context) = context_lock.write() else {\n        return;\n    };\n    let eval = rink_core::one_line(&mut context, text);\n    // let mut context: calc::Context<f64> = calc::Context::default();\n    // let eval = context.evaluate(text);\n\n    if let Ok(eval) = eval {\n        trace!(\"Eval: {eval:?}\");\n        let (title, desc) = parse_result(eval);\n        trace!(\"Added calc option: {title}, {desc:?}\");\n        matches.push(SortableLaunchOption {\n            icon: Some(Box::from(Path::new(\"accessories-calculator\"))),\n            name: title.clone(),\n            details: Box::from(\"Copy to clipboard\"),\n            details_long: desc,\n            score: 0,\n            grayed: false,\n            iden: Identifier::data(PluginNames::Calc, title),\n            details_menu: vec![],\n        });\n    } else {\n        trace!(\"No option added: expression error: {eval:?}\");\n    }\n}\n\npub fn copy_result(data: Option<&str>) -> bool {\n    if let Some(data) = data\n        && let Some(clipboard) = Display::default().map(|display| display.clipboard())\n    {\n        debug!(\"Copying result to clipboard: {}\", data);\n        clipboard.set_text(data.as_ref());\n    }\n    false\n}\n\n// remove `(....) 23.23`\n// remove `approx. 34.34`\n// remove `23/233, 0.09871244`\n#[allow(clippy::map_unwrap_or)]\nfn parse_result(result: String) -> (Box<str>, Option<Box<str>>) {\n    if result.contains(\"approx. \") {\n        return parse_result(result.replace(\"approx. \", \"\"));\n    }\n    if result.contains(\", \") {\n        return result\n            .split_once(\", \")\n            .map(|(desc, title)| {\n                let a = parse_result(title.to_string());\n                let des = a.1.map(|s| format!(\"{s} \")).unwrap_or_default();\n                (a.0, Some(Box::from(format!(\"{des}{desc}\"))))\n            })\n            .unwrap_or_else(|| (result.into_boxed_str(), None));\n    }\n    if result.contains(\" (\") {\n        return result\n            .split_once(\" (\")\n            .map(|(title, desc)| {\n                (\n                    Box::from(title),\n                    Some(Box::from(desc.trim_end_matches(')'))),\n                )\n            })\n            .unwrap_or_else(|| (result.into_boxed_str(), None));\n    }\n\n    (result.into_boxed_str(), None)\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::plugins::calc::parse_result;\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_parse_result_approx_with_dimensions() {\n        let result = parse_result(\"approx. 0.5217391 (dimensionless)\".to_string());\n        assert_eq!(result.0.as_ref(), \"0.5217391\");\n        assert_eq!(result.1.as_deref(), Some(\"dimensionless\"));\n    }\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_parse_result_approx_with_dimensions_and_fraction() {\n        let result = parse_result(\"12/23, approx. 0.5217391 (dimensionless)\".to_string());\n        assert_eq!(result.0.as_ref(), \"0.5217391\");\n        assert_eq!(result.1.as_deref(), Some(\"dimensionless 12/23\"));\n    }\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_parse_result_approx() {\n        let result = parse_result(\"approx. 42\".to_string());\n        assert_eq!(result.0.as_ref(), \"42\");\n        assert_eq!(result.1, None);\n    }\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_parse_result_fraction() {\n        let result = parse_result(\"1/2, 0.5\".to_string());\n        assert_eq!(result.0.as_ref(), \"0.5\");\n        assert_eq!(result.1.as_deref(), Some(\"1/2\"));\n    }\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_parse_result_with_parentheses() {\n        let result = parse_result(\"42 (answer)\".to_string());\n        assert_eq!(result.0.as_ref(), \"42\");\n        assert_eq!(result.1.as_deref(), Some(\"answer\"));\n    }\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_parse_result_simple() {\n        let result = parse_result(\"42\".to_string());\n        assert_eq!(result.0.as_ref(), \"42\");\n        assert_eq!(result.1, None);\n    }\n}\n"
  },
  {
    "path": "crates/launcher-lib/src/plugins/mod.rs",
    "content": "use config_lib::Plugins;\nuse relm4::adw::gtk::gdk::Key;\nuse std::path::Path;\nuse tracing::debug_span;\n\nmod actions;\nmod applications;\n#[cfg(feature = \"calc\")]\nmod calc;\nmod path;\nmod search;\nmod shell;\nmod terminal;\n\npub use applications::get_stored_runs as get_applications_stored_runs;\npub use applications::reload_desktop_entries_map as reload_applications_desktop_entries_map;\nuse core_lib::transfer::{Identifier, PluginNames};\n\n#[cfg(feature = \"calc\")]\npub use calc::init_context as init_calc_context;\n#[cfg(not(feature = \"calc\"))]\npub const fn init_calc_context() {}\n\n#[derive(Debug)]\npub struct SortableLaunchOption {\n    pub name: Box<str>,\n    pub icon: Option<Box<Path>>,\n    pub details: Box<str>,\n    pub details_long: Option<Box<str>>,\n    /// Higher is better\n    pub score: u64,\n    pub grayed: bool,\n    pub iden: Identifier,\n    pub details_menu: Vec<DetailsMenuItem>,\n}\n\n#[derive(Debug)]\npub struct DetailsMenuItem {\n    pub text: Box<str>,\n    pub exec: Box<str>,\n    pub iden: Identifier,\n}\n\n#[derive(Debug)]\npub struct StaticLaunchOption {\n    pub text: Box<str>,\n    pub details: Box<str>,\n    pub icon: Option<Box<Path>>,\n    pub key: char,\n    pub iden: Identifier,\n}\n\npub fn get_sortable_launch_options(\n    plugins: &Plugins,\n    text: &str,\n    data_dir: &Path,\n) -> Vec<SortableLaunchOption> {\n    let mut matches = Vec::new();\n\n    if let Some(config) = plugins.applications.as_ref() {\n        debug_span!(\"applications\").in_scope(|| {\n            applications::get_sortable_options(\n                &mut matches,\n                text,\n                config.run_cache_weeks,\n                config.show_execs,\n                config.show_actions_submenu,\n                data_dir,\n            );\n        });\n    }\n    if plugins.calc.is_some() {\n        #[cfg(feature = \"calc\")]\n        debug_span!(\"calc\").in_scope(|| {\n            calc::get_calc_options(&mut matches, text);\n        });\n        #[cfg(not(feature = \"calc\"))]\n        tracing::warn!(\"calc plugin is not enabled\");\n    }\n    if plugins.path.is_some() {\n        debug_span!(\"path\").in_scope(|| path::get_path_options(&mut matches, text));\n    }\n    if let Some(config) = plugins.actions.as_ref() {\n        debug_span!(\"actions\")\n            .in_scope(|| actions::get_actions_options(&mut matches, text, config));\n    }\n\n    // sort in reverse\n    matches.sort_by(|a, b| b.score.cmp(&a.score));\n    // trace!(\"matches: {:?}\", matches);\n    matches\n}\n\npub fn get_static_launch_options(\n    plugins: &Plugins,\n    default_terminal: Option<&str>,\n) -> Vec<StaticLaunchOption> {\n    let mut matches = Vec::new();\n\n    if plugins.shell.is_some() {\n        debug_span!(\"shell\").in_scope(|| {\n            shell::get_static_options(&mut matches);\n        });\n    }\n    if plugins.terminal.is_some() {\n        debug_span!(\"terminal\").in_scope(|| {\n            terminal::get_static_options(&mut matches, default_terminal);\n        });\n    }\n    if let Some(websearch) = plugins.websearch.as_ref() {\n        debug_span!(\"search\").in_scope(|| {\n            search::get_static_options(&mut matches, &websearch.engines);\n        });\n    }\n\n    matches\n}\n\npub struct PluginReturn {\n    pub show_animation: bool,\n}\n\npub fn launch(\n    iden: &Identifier,\n    text: &str,\n    default_terminal: Option<&str>,\n    data_dir: &Path,\n) -> PluginReturn {\n    let _span = debug_span!(\"launch_plugin\").entered();\n\n    match iden.plugin {\n        PluginNames::Applications => debug_span!(\"applications\").in_scope(|| {\n            applications::launch_option(\n                iden.data.as_deref(),\n                iden.data_additional.as_deref(),\n                default_terminal,\n                data_dir,\n            )\n        }),\n        PluginNames::Shell => {\n            debug_span!(\"shell\").in_scope(|| shell::launch_option(text, default_terminal))\n        }\n        PluginNames::Terminal => {\n            debug_span!(\"terminal\").in_scope(|| terminal::launch_option(text, default_terminal))\n        }\n        PluginNames::WebSearch => {\n            debug_span!(\"search\").in_scope(|| search::launch_option(iden.data.as_deref(), text))\n        }\n        PluginNames::Path => debug_span!(\"path\").in_scope(|| path::launch_option(text)),\n        PluginNames::Calc => {\n            #[cfg(feature = \"calc\")]\n            debug_span!(\"calc\").in_scope(|| calc::copy_result(iden.data.as_deref()));\n            #[cfg(not(feature = \"calc\"))]\n            tracing::warn!(\"calc plugin is not enabled\");\n            PluginReturn {\n                show_animation: false,\n            }\n        }\n        PluginNames::Actions => {\n            debug_span!(\"actions\").in_scope(|| actions::run_action(iden.data.as_deref()))\n        }\n    }\n}\n\npub fn get_static_options_chars(plugins: &Plugins) -> Vec<Key> {\n    let mut chars = Vec::new();\n\n    if plugins.shell.is_some() {\n        chars.append(&mut shell::get_chars());\n    }\n    if plugins.terminal.is_some() {\n        chars.append(&mut terminal::get_chars());\n    }\n    if let Some(websearch) = plugins.websearch.as_ref() {\n        chars.append(&mut search::get_chars(&websearch.engines));\n    }\n\n    chars\n}\n"
  },
  {
    "path": "crates/launcher-lib/src/plugins/path.rs",
    "content": "use crate::plugins::{Identifier, PluginNames, PluginReturn, SortableLaunchOption};\nuse core_lib::WarnWithDetails;\nuse core_lib::default::get_default_desktop_file;\nuse exec_lib::run::run_program;\nuse std::env;\nuse std::path::Path;\nuse tracing::{debug, trace, warn};\n\npub fn get_path_options(matches: &mut Vec<SortableLaunchOption>, text: &str) {\n    if text.starts_with('/') || text.starts_with('~') {\n        // starting the file manager from bash works with ~,\n        // checking if a file exists doesn't work with ~ as it is not expanded without a shell\n        let text = if text.starts_with('~') {\n            text.replacen('~', &env::var(\"HOME\").unwrap_or_default(), 1)\n        } else {\n            text.to_string()\n        };\n        let exists = Path::new(&text).exists();\n        let file_manager = get_file_manager_info();\n        matches.push(SortableLaunchOption {\n            icon: file_manager.icon.clone(),\n            name: format!(\"Open in {}\", file_manager.name).into_boxed_str(),\n            details: Box::from(\"\"),\n            details_long: None,\n            score: 100,\n            grayed: !exists,\n            iden: Identifier::plugin(PluginNames::Path),\n            details_menu: vec![],\n        });\n    }\n}\n\npub fn launch_option(text: &str) -> PluginReturn {\n    if text.is_empty() {\n        debug!(\"No text to search for\");\n        return PluginReturn {\n            show_animation: false,\n        };\n    }\n\n    debug!(\"Opening folder: {}\", text);\n    let file_manager = get_file_manager_info();\n    let cmdline = if [\"%u\", \"%U\", \"%f\", \"%F\"]\n        .iter()\n        .any(|repl| file_manager.exec.contains(repl))\n    {\n        let mut exec = file_manager.exec.to_string();\n        for repl in [\"%u\", \"%U\", \"%f\", \"%F\"] {\n            exec = exec.replace(repl, text);\n        }\n        exec\n    } else {\n        format!(\"{} {}\", file_manager.exec, text)\n    };\n    debug!(\"Launching file-manger: {}\", cmdline);\n    run_program(&cmdline, None, false, None).warn_details(\"Failed to run program\");\n    PluginReturn {\n        show_animation: true,\n    }\n}\n\npub struct FilemanagerData {\n    pub exec: Box<str>,\n    pub name: Box<str>,\n    pub icon: Option<Box<Path>>,\n}\n\npub(super) fn get_file_manager_info() -> FilemanagerData {\n    get_default_desktop_file(\"inode/directory\", |(entry, ini)| {\n        if let Some(section) = ini.get_section(\"Desktop Entry\") {\n            let exec = section.get_first(\"Exec\");\n            let icon = section.get_first_as_path(\"Icon\");\n            let name = section.get_first(\"Name\").unwrap_or_default();\n            trace!(\"Found exec: {exec:?}, icon: {icon:?}\");\n            if let Some(exec) = exec {\n                trace!(\n                    \"Found default file-manager file: {} with exec: {exec}\",\n                    entry.path().display()\n                );\n                return Some(Some(FilemanagerData { exec, name, icon }));\n            }\n        }\n        None\n    })\n    .flatten()\n    .unwrap_or_else(|| {\n        warn!(\"No default browser found! (using firefox and gdbus to open)\");\n        FilemanagerData {\n            exec: Box::from(r\"nautilus --new-window %U\"),\n            icon: Some(Box::from(Path::new(\"org.gnome.Nautilus\"))),\n            name: Box::from(r\"Nautilus\"),\n        }\n    })\n}\n"
  },
  {
    "path": "crates/launcher-lib/src/plugins/search.rs",
    "content": "use crate::plugins::{Identifier, PluginNames, PluginReturn, StaticLaunchOption};\nuse config_lib::SearchEngine;\nuse core_lib::WarnWithDetails;\nuse core_lib::default::get_default_desktop_file;\nuse exec_lib::run::run_program;\nuse exec_lib::switch::switch_client_by_initial_class;\nuse relm4::adw::gtk::gdk::Key;\nuse std::path::Path;\nuse tracing::{debug, trace, warn};\n\npub fn get_static_options(matches: &mut Vec<StaticLaunchOption>, config: &[SearchEngine]) {\n    let browser = get_browser_info();\n    let icon = browser.icon.clone();\n    drop(browser);\n    let mut count = 0;\n    for engine in config {\n        if engine.key.is_whitespace() {\n            warn!(\"Plugin {} has no valid key set\", engine.name);\n        } else {\n            matches.push(StaticLaunchOption {\n                text: engine.name.clone(),\n                details: format!(\"Search with {}\", engine.name).into_boxed_str(),\n                icon: icon.clone(),\n                key: engine.key,\n                iden: Identifier::data(PluginNames::WebSearch, engine.url.clone()),\n            });\n            count += 1;\n        }\n    }\n    trace!(\"Added {count} static web search options\");\n}\n\npub fn launch_option(iden: Option<&str>, text: &str) -> PluginReturn {\n    if text.is_empty() {\n        debug!(\"No text to search for\");\n        return PluginReturn {\n            show_animation: false,\n        };\n    }\n    if let Some(iden) = iden {\n        let url = iden.replace(\"{}\", text);\n        debug!(\"Launching URL: {}\", url);\n        let browser = get_browser_info();\n        let cmdline = if [\"%u\", \"%U\", \"%f\", \"%F\"]\n            .iter()\n            .any(|repl| browser.exec.contains(repl))\n        {\n            let mut exec = browser.exec.to_string();\n            for repl in [\"%u\", \"%U\", \"%f\", \"%F\"] {\n                exec = exec.replace(repl, &format!(\"'{url}'\"));\n            }\n            exec\n        } else {\n            format!(\"{} '{}'\", browser.exec, url)\n        };\n        debug!(\"Launching browser: {}\", cmdline);\n        run_program(&cmdline, None, false, None).warn_details(\"Failed to run program\");\n\n        // try to focus browser\n        if let Some(class) = &browser.startup_wm_class {\n            debug!(\"trying to focus browser with class: {}\", class);\n            switch_client_by_initial_class(class).warn_details(\"unable to focus browser\");\n        } else {\n            trace!(\"not class to browser available\");\n        }\n    }\n    PluginReturn {\n        show_animation: true,\n    }\n}\n\npub fn get_chars(config: &[SearchEngine]) -> Vec<Key> {\n    config\n        .iter()\n        .filter_map(|engine| convert_to_key(engine.key))\n        .collect()\n}\n\npub struct BrowserData {\n    pub exec: Box<str>,\n    pub startup_wm_class: Option<Box<str>>,\n    pub icon: Option<Box<Path>>,\n}\n\npub(super) fn get_browser_info() -> BrowserData {\n    let a = get_default_desktop_file(\"x-scheme-handler/https\", |(entry, ini)| {\n        if let Some(section) = ini.get_section(\"Desktop Entry\") {\n            let exec = section.get_first(\"Exec\");\n            let startup_wm_class = section.get_first(\"StartupWMClass\");\n            let icon = section.get_first_as_path(\"Icon\");\n            if let Some(exec) = exec {\n                trace!(\n                    \"Found default browser file: {} with exec: {exec}, icon: {icon:?} and startup_wm_class: {startup_wm_class:?}\",\n                    entry.path().display()\n                );\n                return Some(BrowserData {\n                    exec,\n                    startup_wm_class,\n                    icon,\n                });\n            }\n        }\n        None\n    });\n    a.unwrap_or_else(|| {\n        warn!(\"No default browser found! (using firefox and gdbus to open)\");\n        BrowserData {\n            exec: Box::from(\n                r#\"gdbus call --session --dest=\"org.freedesktop.portal.Desktop\" --object-path=/org/freedesktop/portal/desktop --method=org.freedesktop.portal.OpenURI.OpenURI '' '%u' '{}'\"#,\n            ),\n            startup_wm_class: Some(Box::from(\"firefox\")),\n            icon: Some(Box::from(Path::new(\"firefox\"))),\n        }\n    })\n}\n\npub const fn convert_to_key(char: char) -> Option<Key> {\n    match char {\n        'a' => Some(Key::a),\n        'b' => Some(Key::b),\n        'c' => Some(Key::c),\n        'd' => Some(Key::d),\n        'e' => Some(Key::e),\n        'f' => Some(Key::f),\n        'g' => Some(Key::g),\n        'h' => Some(Key::h),\n        'i' => Some(Key::i),\n        'j' => Some(Key::j),\n        'k' => Some(Key::k),\n        'l' => Some(Key::l),\n        'm' => Some(Key::m),\n        'n' => Some(Key::n),\n        'o' => Some(Key::o),\n        'p' => Some(Key::p),\n        'q' => Some(Key::q),\n        'r' => Some(Key::r),\n        's' => Some(Key::s),\n        't' => Some(Key::t),\n        'u' => Some(Key::u),\n        'v' => Some(Key::v),\n        'w' => Some(Key::w),\n        'x' => Some(Key::x),\n        'y' => Some(Key::y),\n        'z' => Some(Key::z),\n        _ => None,\n    }\n}\n"
  },
  {
    "path": "crates/launcher-lib/src/plugins/shell.rs",
    "content": "use crate::plugins::{Identifier, PluginNames, PluginReturn, StaticLaunchOption};\nuse core_lib::WarnWithDetails;\nuse exec_lib::run::run_program;\nuse relm4::adw::gtk::gdk::Key;\nuse std::path::PathBuf;\nuse tracing::{debug, trace};\n\npub fn get_static_options(matches: &mut Vec<StaticLaunchOption>) {\n    matches.push(StaticLaunchOption {\n        iden: Identifier::plugin(PluginNames::Shell),\n        key: 'r',\n        text: Box::from(\"Shell\"),\n        details: Box::from(\"Run a command in a shell\"),\n        icon: Some(PathBuf::from(\"bash\").into_boxed_path()),\n    });\n    trace!(\"Added static shell option\");\n}\n\npub fn launch_option(text: &str, default_terminal: Option<&str>) -> PluginReturn {\n    if text.is_empty() {\n        debug!(\"No text to run in shell\");\n        return PluginReturn {\n            show_animation: false,\n        };\n    }\n    run_program(text, None, false, default_terminal).warn_details(\"Failed to run program\");\n    PluginReturn {\n        show_animation: true,\n    }\n}\n\npub fn get_chars() -> Vec<Key> {\n    vec![Key::r]\n}\n"
  },
  {
    "path": "crates/launcher-lib/src/plugins/terminal.rs",
    "content": "use crate::plugins::{Identifier, PluginNames, PluginReturn, StaticLaunchOption};\nuse core_lib::WarnWithDetails;\nuse exec_lib::run::run_program;\nuse relm4::adw::gtk::gdk::Key;\nuse std::path::PathBuf;\nuse tracing::{debug, trace};\n\npub fn get_static_options(matches: &mut Vec<StaticLaunchOption>, default_terminal: Option<&str>) {\n    matches.push(StaticLaunchOption {\n        iden: Identifier::plugin(PluginNames::Terminal),\n        key: 't',\n        text: Box::from(\"Terminal\"),\n        details: Box::from(\"Run a command in a terminal\"),\n        icon: Some(\n            PathBuf::from(default_terminal.map_or(\"system-run\", |term| match term {\n                // random fix for alacritty icon\n                \"alacritty\" => \"Alacritty\",\n                other => other,\n            }))\n            .into_boxed_path(),\n        ),\n    });\n    trace!(\"Added static terminal option\");\n}\n\npub fn launch_option(text: &str, default_terminal: Option<&str>) -> PluginReturn {\n    if text.is_empty() {\n        debug!(\"No text to run in terminal\");\n        return PluginReturn {\n            show_animation: false,\n        };\n    }\n    run_program(\n        // exec shell to prevent needing 2 exits\n        // echo to make the shell look better and show the executed command\n        &format!(\"$SHELL -c \\\"echo '> {text}';{text};echo;exec $SHELL\\\"\"),\n        None,\n        true,\n        default_terminal,\n    )\n    .warn_details(\"Failed to run program\");\n    PluginReturn {\n        show_animation: true,\n    }\n}\n\npub fn get_chars() -> Vec<Key> {\n    vec![Key::t]\n}\n"
  },
  {
    "path": "crates/launcher-lib/src/stop.rs",
    "content": "use crate::LauncherData;\nuse relm4::adw::gtk::prelude::*;\nuse tracing::{debug_span, trace};\n\npub fn stop_launcher(data: &LauncherData) {\n    let _span = debug_span!(\"stop_launcher\").entered();\n\n    trace!(\"Closing window {:?}\", data.window.id());\n    data.window.close();\n}\n"
  },
  {
    "path": "crates/launcher-lib/src/styles.css",
    "content": ".launcher {\n    padding: var(--window-padding, 2px);\n\n    box-shadow: 0 0 16px 5px var(--bg-window-color, rgba(20, 30, 40, 0.9));\n\n    border-radius: var(--border-radius, 12px);\n    background: var(--bg-window-color, rgba(20, 20, 20, 0.9));\n    border: var(--border-size, 3px) var(--border-style, solid) var(--border-color, rgba(90, 90, 120, 0.4));\n}\n\n.launcher-input {\n    font-size: 1.5rem;\n    margin: 6px;\n    padding: 4px 8px;\n    background: var(--bg-color, rgba(20, 20, 20, 0.9));\n}\n\n.launcher-results {\n    margin: 1px 4px;\n}\n\n.launcher-item {\n    padding: 2px;\n\n    border-radius: calc(1.2 * var(--border-radius, 12px));\n    background: var(--bg-color, rgba(20, 20, 20, 0.9));\n}\n\n.launcher-item-inner {\n    padding: 0 6px;\n    font-size: 1.5rem;\n\n    border-radius: var(--border-radius, 12px);\n    background: var(--bg-color, rgba(20, 20, 20, 0.9));\n}\n\n.launcher-item-inner:hover {\n    background: var(--bg-color-hover, rgba(40, 40, 50, 1));\n}\n\n.launcher-exec {\n    font-size: 0.8rem;\n    font-family: monospace;\n}\n\n.launcher-other-menu-button {\n    padding: 2px 4px;\n    border-radius: var(--border-radius, 12px);\n    background: var(--bg-color, rgba(20, 20, 20, 0.9));\n    border: 1px var(--border-style, solid) var(--border-color, rgba(90, 90, 120, 0.4));\n}\n\n.launcher-other-menu-button:hover {\n    background: var(--bg-color-hover, rgba(40, 40, 50, 1));\n}\n\n.launcher-other-menu {\n    background: transparent;\n    margin: 0;\n    padding: 0;\n\n    border-radius: var(--border-radius, 12px);\n    background: var(--bg-color, rgba(20, 20, 20, 0.9));\n    border: calc(var(--border-size, 3px) / 1.5) var(--border-style, solid) var(--border-color, rgba(90, 90, 120, 0.4));\n}\n\n.launcher-other-menu-item {\n    padding: 2px;\n\n    border-radius: calc(var(--border-radius, 12px) / 2);\n    background: var(--bg-color, rgba(20, 20, 20, 0.9));\n}\n\n.launcher-other-menu-item-inner {\n    padding: 2px 5px;\n    font-size: 1.1rem;\n\n    border-radius: calc(var(--border-radius, 12px) / 2);\n    background: var(--bg-color, rgba(20, 20, 20, 0.9));\n}\n\n.launcher-other-menu-item-inner:hover {\n    background: var(--bg-color-hover, rgba(40, 40, 50, 1));\n}\n\n.launcher-key {\n    font-size: 1.2rem;\n    font-family: monospace;\n}\n\n.launcher-plugins {\n    padding: 4px 2px 2px;\n\n    border-top: calc(var(--border-size, 3px) / 1.5) var(--border-style, solid) var(--border-color, rgba(90, 90, 120, 0.4));\n}\n\n.launcher-plugin {\n    padding: 2px;\n\n    border-radius: calc(1.5 * var(--border-radius, 12px));\n    background: var(--bg-color, rgba(20, 20, 20, 0.9));\n}\n\n.launcher-plugin-inner {\n    padding: 6px;\n\n    border-radius: calc(1.5 * var(--border-radius, 12px));\n    background: var(--bg-color, rgba(20, 20, 20, 0.9));\n}\n\n.launcher-plugin-key {\n    font-size: 0.9rem;\n    font-family: monospace;\n}\n\n.launcher-plugin-inner:hover {\n    background: var(--bg-color-hover, rgba(40, 40, 50, 1));\n}\n\n.launch {\n    animation: background-moving 450ms cubic-bezier(0.25, 0.8, 0.4, 0.95) normal forwards;\n}\n\n@keyframes background-moving {\n    0% {\n        background: linear-gradient(90deg, var(--border-color-active, rgba(239, 9, 9, 0.9)) 0%, transparent 0%);\n    }\n    100% {\n        background: linear-gradient(90deg, var(--border-color-active, rgba(239, 9, 9, 0.9)) 100%, transparent 100%);\n    }\n}"
  },
  {
    "path": "crates/launcher-lib/src/update.rs",
    "content": "use crate::LauncherData;\nuse crate::plugins::{\n    SortableLaunchOption, StaticLaunchOption, get_sortable_launch_options,\n    get_static_launch_options,\n};\nuse async_channel::Sender;\nuse config_lib::Modifier;\nuse core_lib::transfer::{CloseOverviewConfig, Identifier, TransferType};\nuse core_lib::{WarnWithDetails, default};\nuse relm4::adw::gtk::gdk::Cursor;\nuse relm4::adw::gtk::pango::EllipsizeMode;\nuse relm4::adw::gtk::prelude::*;\nuse relm4::adw::gtk::{\n    Align, Button, IconSize, Image, Label, ListBox, ListBoxRow, Orientation, Overflow, Popover,\n    SelectionMode, glib,\n};\nuse relm4::gtk;\nuse std::collections::HashMap;\nuse std::path::Path;\nuse tracing::{debug, debug_span, warn};\n\npub fn update_launcher(data: &mut LauncherData, text: &str, event_sender: &Sender<TransferType>) {\n    let _span = debug_span!(\"update_launcher\").entered();\n\n    while let Some(child) = data.results_box.first_child() {\n        data.results_box.remove(&child);\n    }\n    while let Some(child) = data.plugins_box.first_child() {\n        data.plugins_box.remove(&child);\n    }\n    data.sorted_matches.clear();\n    data.static_matches.clear();\n    if !data.config.show_when_empty && text.is_empty() {\n        return;\n    }\n\n    let sortable_launch_options =\n        get_sortable_launch_options(&data.config.plugins, text, &data.config.data_dir);\n    let mut items = data.config.max_items.min(9);\n    for (index, opt) in sortable_launch_options.into_iter().enumerate() {\n        if items == 0 {\n            break;\n        }\n        items -= 1;\n        let (row, details) = create_entry(\n            &opt,\n            match index {\n                0 => \"Return\".to_string(),\n                i if i <= 9 => format!(\"{}+{i}\", data.config.launch_modifier),\n                _ => String::new(),\n            },\n            event_sender.clone(),\n        );\n        data.results_box.append(&row);\n        data.results_items.insert(opt.iden.clone(), (row, details));\n        data.sorted_matches.push(opt.iden);\n    }\n\n    let static_launch_options = get_static_launch_options(\n        &data.config.plugins,\n        data.config.default_terminal.as_deref(),\n    );\n    for opt in static_launch_options {\n        let button = create_static_plugin_box(\n            &opt,\n            text,\n            data.config.launch_modifier,\n            event_sender.clone(),\n        );\n        data.plugins_box.append(&button);\n        data.plugins_items.insert(opt.iden.clone(), button);\n        data.static_matches.insert(opt.key, opt.iden);\n    }\n}\n\nfn create_static_plugin_box(\n    opt: &StaticLaunchOption,\n    text: &str,\n    launch_modifier: Modifier,\n    event_sender: Sender<TransferType>,\n) -> Button {\n    let hbox = gtk::Box::builder()\n        .orientation(Orientation::Horizontal)\n        .css_classes([\"launcher-plugin-inner\"])\n        .spacing(10)\n        .build();\n\n    if let Some(icon) = opt.icon.clone() {\n        // tracing::trace!(\"icon: {icon:?}\");\n        let icon = Image::builder()\n            .icon_size(IconSize::Large)\n            .icon_name(icon.to_string_lossy())\n            .build();\n        hbox.append(&icon);\n    }\n\n    let vbox = gtk::Box::builder()\n        .orientation(Orientation::Vertical)\n        .spacing(2)\n        .build();\n\n    let title = Label::builder()\n        .halign(Align::Center)\n        .valign(Align::Start)\n        .label(opt.text.clone())\n        .css_classes([\"underline\"])\n        .tooltip_text(opt.details.clone())\n        .build();\n    vbox.append(&title);\n\n    let exec = Label::builder()\n        .halign(Align::Center)\n        .valign(Align::End)\n        .ellipsize(EllipsizeMode::End)\n        .css_classes([\"launcher-plugin-key\"])\n        .label(format!(\"{launch_modifier} + {}\", opt.key))\n        .build();\n    vbox.append(&exec);\n\n    hbox.append(&vbox);\n\n    let button = Button::builder()\n        .child(&hbox)\n        .css_classes([\"launcher-plugin\"])\n        .build();\n    button.set_cursor(Cursor::from_name(\"pointer\", None).as_ref());\n    if text.is_empty() {\n        title.add_css_class(\"text-grayed\");\n        exec.add_css_class(\"text-grayed\");\n    }\n    click_plugin(&button, opt.iden.clone(), event_sender);\n    button\n}\n\n#[allow(clippy::too_many_lines)]\nfn create_entry(\n    opt: &SortableLaunchOption,\n    key: impl Into<glib::GString>,\n    event_sender: Sender<TransferType>,\n) -> (gtk::Box, HashMap<Identifier, ListBoxRow>) {\n    let hbox = gtk::Box::builder()\n        .css_classes([\"launcher-item-inner\"])\n        .orientation(Orientation::Horizontal)\n        .height_request(45)\n        .spacing(8)\n        .hexpand(true)\n        .vexpand(true)\n        .build();\n\n    let icon = Image::builder().icon_size(IconSize::Large).build();\n    if let Some(icon_path) = &opt.icon {\n        if icon_path.is_absolute() {\n            if let Some(icon_name) = icon_path.file_stem() {\n                if default::theme_has_icon_name(&icon_name.to_string_lossy()) {\n                    icon.set_icon_name(Some(&icon_name.to_string_lossy()));\n                } else {\n                    icon.set_from_file(Some(Path::new(&*icon_path.clone())));\n                }\n            } else {\n                warn!(\"invalid icon name: {icon_path:?}\");\n            }\n        } else {\n            // use filename as some files are named org.gnome.file\n            // trace!(\n            //     \"using name: {:?}\",\n            //     icon_path.file_name().and_then(|name| name.to_str())\n            // );\n            icon.set_icon_name(icon_path.file_name().and_then(|name| name.to_str()));\n        }\n    }\n    hbox.append(&icon);\n\n    let title = Label::builder()\n        .halign(Align::Start)\n        .valign(Align::Center)\n        .label(opt.name.clone())\n        .build();\n    hbox.append(&title);\n\n    let exec = Label::builder()\n        .halign(Align::Start)\n        .valign(Align::Center)\n        .hexpand(true)\n        .css_classes([\"launcher-exec\"])\n        .ellipsize(EllipsizeMode::End)\n        .label(opt.details.clone())\n        .build();\n    if let Some(details_long) = &opt.details_long {\n        exec.set_tooltip_text(Some(details_long));\n        exec.add_css_class(\"underline\");\n    }\n    hbox.append(&exec);\n\n    let mut details_list = HashMap::new();\n    if !opt.details_menu.is_empty() {\n        let button = Button::builder()\n            .css_classes([\"launcher-other-menu-button\"])\n            .icon_name(\"open-menu-symbolic\")\n            .halign(Align::End)\n            .valign(Align::Center)\n            .build();\n        let menu = Popover::builder()\n            .css_classes([\"launcher-other-menu\"])\n            .has_arrow(false)\n            .can_focus(false)\n            .can_target(true)\n            .focus_on_click(false)\n            .overflow(Overflow::Hidden)\n            .build();\n        let menu_list_box = ListBox::builder()\n            .selection_mode(SelectionMode::None)\n            .build();\n\n        for item in &opt.details_menu {\n            let menu_item_text = Label::builder()\n                .css_classes([\"underline\"])\n                .label(format!(\"{}: {}\", opt.name, item.text))\n                .build();\n            let menu_item_button = Button::builder()\n                .css_classes([\"launcher-other-menu-item-inner\"])\n                .child(&menu_item_text)\n                .tooltip_text(item.exec.clone())\n                .build();\n            let menu_item = ListBoxRow::builder()\n                .css_classes([\"launcher-other-menu-item\"])\n                .child(&menu_item_button)\n                .build();\n            menu_item.set_cursor(Cursor::from_name(\"pointer\", None).as_ref());\n            click_details_entry(&menu_item_button, item.iden.clone(), event_sender.clone());\n            menu_list_box.append(&menu_item);\n            details_list.insert(item.iden.clone(), menu_item);\n        }\n        menu.set_parent(&button);\n        menu.set_child(Some(&menu_list_box));\n        button.connect_clicked(move |_button| {\n            menu.popup();\n        });\n        hbox.append(&button);\n    }\n\n    let index_label = Label::builder()\n        .halign(Align::End)\n        .valign(Align::Center)\n        .css_classes([\"launcher-key\"])\n        .label(key)\n        .build();\n    hbox.append(&index_label);\n\n    let outer_box = gtk::Box::builder().css_classes([\"launcher-item\"]).build();\n    outer_box.append(&hbox);\n    if opt.grayed {\n        outer_box.add_css_class(\"monochrome\");\n    }\n\n    outer_box.set_cursor(Cursor::from_name(\"pointer\", None).as_ref());\n    click_entry(&outer_box, opt.iden.clone(), event_sender);\n    (outer_box, details_list)\n}\n\nfn click_plugin(button: &Button, iden: Identifier, event_sender: Sender<TransferType>) {\n    button.connect_clicked(move |_| {\n        debug!(\"Exiting on click of launcher entry\");\n        event_sender\n            .send_blocking(TransferType::CloseOverview(\n                CloseOverviewConfig::LauncherClick(iden.clone()),\n            ))\n            .warn_details(\"unable to send\");\n    });\n}\n\nfn click_entry(button: &gtk::Box, iden: Identifier, event_sender: Sender<TransferType>) {\n    let gesture = gtk::GestureClick::new();\n    gesture.connect_released(move |_, _, _, _| {\n        debug!(\"Exiting on click of launcher entry\");\n        event_sender\n            .send_blocking(TransferType::CloseOverview(\n                CloseOverviewConfig::LauncherClick(iden.clone()),\n            ))\n            .warn_details(\"unable to send\");\n    });\n    button.add_controller(gesture);\n}\n\nfn click_details_entry(button: &Button, iden: Identifier, event_sender: Sender<TransferType>) {\n    button.connect_clicked(move |_| {\n        debug!(\"Exiting on click of launcher details entry\");\n        event_sender\n            .send_blocking(TransferType::CloseOverview(\n                CloseOverviewConfig::LauncherClick(iden.clone()),\n            ))\n            .warn_details(\"unable to send\");\n    });\n}\n"
  },
  {
    "path": "crates/windows-lib/Cargo.toml",
    "content": "[package]\nname = \"hyprshell-windows-lib\"\ndocumentation = \"https://docs.rs/hyprshell-windows-lib\"\nversion = \"4.9.5\"\nedition.workspace = true\ndescription.workspace = true\nlicense.workspace = true\nauthors.workspace = true\nkeywords.workspace = true\nrepository.workspace = true\nrust-version.workspace = true\n\n[dependencies]\nanyhow.workspace = true\ntracing.workspace = true\nrelm4.workspace = true\ngtk4-layer-shell.workspace = true\ncore-lib.workspace = true\nexec-lib.workspace = true\nasync-channel.workspace = true\nconfig-lib.workspace = true\nregex = { version = \"1.11.1\" }\n\n[lints]\nworkspace = true\n\n[dev-dependencies]\ntest-log.workspace = true\n"
  },
  {
    "path": "crates/windows-lib/src/css.rs",
    "content": "use anyhow::Context;\nuse relm4::adw::gtk::gdk::Display;\nuse relm4::adw::gtk::{\n    CssProvider, STYLE_PROVIDER_PRIORITY_USER, style_context_add_provider_for_display,\n};\n\npub fn get_css() -> anyhow::Result<()> {\n    let provider_app = CssProvider::new();\n    provider_app.load_from_string(include_str!(\"styles.css\"));\n    style_context_add_provider_for_display(\n        &Display::default().context(\"Could not connect to a display.\")?,\n        &provider_app,\n        STYLE_PROVIDER_PRIORITY_USER,\n    );\n    Ok(())\n}\n"
  },
  {
    "path": "crates/windows-lib/src/data.rs",
    "content": "use crate::sort::{\n    sort_clients_by_position, sort_clients_by_recent, sort_monitor_by_x,\n    sort_workspaces_by_position, sort_workspaces_by_recent,\n};\nuse core_lib::{Active, ClientData, ClientId, FindByFirst, HyprlandData, MonitorData, MonitorId};\nuse exec_lib::collect::collect_hypr_data;\nuse tracing::{debug_span, trace, warn};\n\n#[allow(clippy::struct_excessive_bools)]\n#[derive(Debug, Clone, Default)]\npub struct SortConfig {\n    pub sort_recent: bool,\n    pub filter_current_workspace: bool,\n    pub filter_current_monitor: bool,\n    pub filter_same_class: bool,\n}\n\npub fn collect_data(config: &SortConfig) -> anyhow::Result<(HyprlandData, Active)> {\n    let _span = debug_span!(\"collect_data\").entered();\n\n    let (\n        mut client_data,\n        mut workspace_data,\n        mut monitor_data,\n        active_client,\n        active_ws,\n        active_monitor,\n    ) = collect_hypr_data()?;\n    client_data = update_client_position(client_data, &monitor_data);\n    sort_monitor_by_x(&mut monitor_data);\n    if config.sort_recent {\n        sort_clients_by_recent(&mut client_data);\n        sort_workspaces_by_recent(&mut workspace_data, &client_data); // ! must be after sort_clients_by_recent\n    } else {\n        sort_workspaces_by_position(&mut workspace_data, &monitor_data); // ! must be before sort_clients_by_position\n        client_data = sort_clients_by_position(client_data, &workspace_data, &monitor_data);\n    }\n\n    trace!(\n        \"active_client: {active_client:?}; active_ws: {active_ws:?}; active_monitor: {active_monitor:?}\"\n    );\n\n    // iterate over all clients and set active to false if the client is not on the active workspace or monitor\n    for (_, client) in &mut client_data {\n        client.enabled = (!config.filter_same_class\n            || active_client\n                .as_ref()\n                .is_none_or(|active| client.class == *active.0))\n            && (!config.filter_current_workspace || client.workspace == active_ws)\n            && (!config.filter_current_monitor || client.monitor == active_monitor);\n    }\n    for (id, ws) in &mut workspace_data {\n        ws.any_client_enabled = client_data\n            .iter()\n            .filter(|(_, c)| c.workspace.eq(id))\n            .all(|(_, c)| c.enabled);\n    }\n\n    trace!(\"client_data: {client_data:?}\");\n    trace!(\"workspace_data: {workspace_data:?}\");\n    trace!(\"monitor_data: {monitor_data:?}\");\n\n    Ok((\n        HyprlandData {\n            clients: client_data,\n            workspaces: workspace_data,\n            monitors: monitor_data,\n        },\n        Active {\n            client: active_client.map(|c| c.1),\n            workspace: active_ws,\n            monitor: active_monitor,\n        },\n    ))\n}\n\n/// removes offset by monitor from clients\npub fn update_client_position(\n    clients: Vec<(ClientId, ClientData)>,\n    monitor_data: &[(MonitorId, MonitorData)],\n) -> Vec<(ClientId, ClientData)> {\n    clients\n        .into_iter()\n        .filter_map(|(a, mut c)| {\n            let md = monitor_data.find_by_first(&c.monitor).or_else(|| {\n                warn!(\"Monitor {:?} not found: {c:?}\", c.monitor);\n                None\n            });\n\n            if let Some(md) = md {\n                c.x -= md.x as i16;\n                c.y -= md.y as i16;\n                Some((a, c))\n            } else {\n                None\n            }\n        })\n        .collect()\n}\n"
  },
  {
    "path": "crates/windows-lib/src/desktop_map.rs",
    "content": "use anyhow::Context;\nuse core_lib::default::get_all_desktop_files;\nuse std::collections::HashMap;\nuse std::path::Path;\nuse std::sync::{OnceLock, RwLock};\nuse tracing::{debug_span, trace, warn};\n\n#[derive(Debug, Clone, Hash, Eq, PartialEq)]\npub enum Source {\n    DesktopFileName,\n    DesktopFileStartupWmClass,\n    DesktopFileExecName,\n    ByPidExec,\n}\ntype IconPathMap = HashMap<(Box<str>, Source), (Box<Path>, Box<Path>)>;\n\nfn get_icon_path_map() -> &'static RwLock<IconPathMap> {\n    static MAP_LOCK: OnceLock<RwLock<IconPathMap>> = OnceLock::new();\n    MAP_LOCK.get_or_init(|| RwLock::new(HashMap::new()))\n}\n\npub fn reload_class_to_icon_map() -> anyhow::Result<()> {\n    let _span = debug_span!(\"reload_class_to_icon_map\").entered();\n    let mut map = get_icon_path_map()\n        .write()\n        .map_err(|_| anyhow::anyhow!(\"Failed to lock icon path map\"))?;\n\n    for (entry, ini) in get_all_desktop_files()\n        .context(\"unable to get desktop files\")?\n        .iter()\n    {\n        if let Some(section) = ini.get_section(\"Desktop Entry\") {\n            let name = section.get_first(\"Name\");\n            let icon = section.get_first_as_path(\"Icon\");\n            let startup_wm_class = section.get_first(\"StartupWMClass\");\n            let exec_name = section.get_first(\"Exec\").and_then(extract_exec_name);\n            if let (Some(name), Some(icon)) = (name, icon.clone()) {\n                map.insert(\n                    (Box::from(name.to_lowercase()), Source::DesktopFileName),\n                    (icon, entry.path().into_boxed_path()),\n                );\n            }\n            if let (Some(startup_wm_class), Some(icon)) = (startup_wm_class, icon.clone()) {\n                map.insert(\n                    (\n                        Box::from(startup_wm_class.to_lowercase()),\n                        Source::DesktopFileStartupWmClass,\n                    ),\n                    (icon, entry.path().into_boxed_path()),\n                );\n            }\n            if let (Some(exec_name), Some(icon)) = (exec_name, icon) {\n                map.insert(\n                    (\n                        Box::from(exec_name.to_lowercase()),\n                        Source::DesktopFileExecName,\n                    ),\n                    (icon, entry.path().into_boxed_path()),\n                );\n            }\n        } else {\n            warn!(\n                \"Failed to find section 'Desktop Entry' in file: {}\",\n                entry.path().display()\n            );\n        }\n    }\n    drop(map);\n    trace!(\"filled class to icon map\");\n    Ok(())\n}\n\nfn extract_exec_name(line: Box<str>) -> Option<String> {\n    // is a flatpak and isn't a PWA\n    // (PWAs work out of the box by using the class being equal to the icon-name)\n    // else chromium/chrome/etc would be detected as program icon which is not desired\n    let exec =\n        if line.contains(\"flatpak\") && line.contains(\"--command\") && !line.contains(\"--app-id\") {\n            // trim all text until --command\n            line.split(\"--command=\")\n                .last()\n                .map(Box::from)\n                .unwrap_or(line)\n        } else {\n            line\n        };\n    exec.split_whitespace()\n        .next()\n        .and_then(|l| l.split('/').next_back())\n        .map(|n| n.replace('\"', \"\"))\n}\n\npub fn add_path_for_icon_by_pid_exec(class: &str, path: Box<Path>) -> anyhow::Result<()> {\n    let mut map = get_icon_path_map()\n        .write()\n        .map_err(|_| anyhow::anyhow!(\"Failed to lock icon path map\"))?;\n\n    map.insert(\n        (Box::from(class.to_ascii_lowercase()), Source::ByPidExec),\n        (path, Box::from(Path::new(\"\"))),\n    );\n    drop(map);\n    Ok(())\n}\n\npub fn get_icon_name_by_name_from_desktop_files(\n    name: &str,\n) -> Option<(Box<Path>, Box<Path>, Source)> {\n    let Ok(map) = get_icon_path_map().read() else {\n        warn!(\"Failed to lock icon path map\");\n        return None;\n    };\n    // prio: name by pid-exec, desktop file name, startup wm class, exec name\n    map.get(&(Box::from(name.to_ascii_lowercase()), Source::ByPidExec))\n        .map(|s| (s.0.clone(), s.1.clone(), Source::ByPidExec))\n        .or_else(|| {\n            map.get(&(\n                Box::from(name.to_ascii_lowercase()),\n                Source::DesktopFileName,\n            ))\n            .map(|s| (s.0.clone(), s.1.clone(), Source::DesktopFileName))\n        })\n        .or_else(|| {\n            map.get(&(\n                Box::from(name.to_ascii_lowercase()),\n                Source::DesktopFileStartupWmClass,\n            ))\n            .map(|s| (s.0.clone(), s.1.clone(), Source::DesktopFileStartupWmClass))\n        })\n        .or_else(|| {\n            map.get(&(\n                Box::from(name.to_ascii_lowercase()),\n                Source::DesktopFileExecName,\n            ))\n            .map(|s| (s.0.clone(), s.1.clone(), Source::DesktopFileExecName))\n        })\n}\n"
  },
  {
    "path": "crates/windows-lib/src/global.rs",
    "content": "use core_lib::{Active, ClientId, HyprlandData, MonitorId, WorkspaceId};\nuse relm4::adw::gtk::{ApplicationWindow, Button, FlowBox};\nuse relm4::gtk;\nuse std::collections::HashMap;\n\n#[derive(Debug)]\npub struct WindowsOverviewData {\n    pub config: WindowsOverviewConfig,\n    pub window_list: HashMap<ApplicationWindow, WindowsOverviewMonitorData>,\n    pub active: Active,\n    pub initial_active: Active,\n    pub hypr_data: HyprlandData,\n}\n\n#[allow(clippy::struct_excessive_bools)]\n#[derive(Debug)]\npub struct WindowsOverviewConfig {\n    pub items_per_row: u8,\n    pub scale: f64,\n    pub filter_current_workspace: bool,\n    pub filter_current_monitor: bool,\n    pub filter_same_class: bool,\n}\n\n#[derive(Debug)]\npub struct WindowsSwitchData {\n    pub config: WindowsSwitchConfig,\n    pub window: ApplicationWindow,\n    pub main_flow: FlowBox,\n    pub workspaces: HashMap<WorkspaceId, Button>,\n    pub clients: HashMap<ClientId, Button>,\n    pub active: Active,\n    pub hypr_data: HyprlandData,\n}\n\n#[allow(clippy::struct_excessive_bools)]\n#[derive(Debug)]\npub struct WindowsSwitchConfig {\n    pub items_per_row: u8,\n    pub scale: f64,\n    pub filter_current_workspace: bool,\n    pub filter_current_monitor: bool,\n    pub filter_same_class: bool,\n    pub switch_workspaces: bool,\n}\n\n#[derive(Debug)]\npub struct WindowsOverviewMonitorData {\n    pub id: MonitorId,\n    pub workspaces_flow: FlowBox,\n    pub workspaces: HashMap<WorkspaceId, gtk::Box>,\n    pub clients: HashMap<ClientId, Button>,\n}\n\nimpl WindowsOverviewMonitorData {\n    pub fn new(id: MonitorId, workspaces_flow: FlowBox) -> Self {\n        Self {\n            id,\n            workspaces_flow,\n            workspaces: HashMap::new(),\n            clients: HashMap::new(),\n        }\n    }\n}\n"
  },
  {
    "path": "crates/windows-lib/src/icon.rs",
    "content": "use crate::desktop_map::{add_path_for_icon_by_pid_exec, get_icon_name_by_name_from_desktop_files};\nuse core_lib::{WarnWithDetails, default};\nuse relm4::adw::gtk::Image;\nuse std::fs;\nuse std::path::Path;\nuse tracing::{debug_span, trace, warn};\n\npub fn set_icon(class: &str, pid: i32, image: &Image) {\n    let image = image.clone();\n    let _span = debug_span!(\"icon\", class = class).entered();\n\n    if load_icon_from_cache(class, &image).is_some() {\n        return;\n    }\n\n    if let Ok(cmdline) = fs::read_to_string(format!(\"/proc/{pid}/cmdline\")) {\n        // convert x00 to space\n        trace!(\"No Icon found for {class}, using Icon by cmdline {cmdline} by PID ({pid})\");\n        let cmd = cmdline\n            .split('\\x00')\n            .next()\n            .unwrap_or_default()\n            .split('/')\n            .next_back()\n            .unwrap_or_default()\n            .split(' ')\n            .next()\n            .unwrap_or_default();\n        if cmd.is_empty() {\n            warn!(\"Failed to read cmdline for PID {}\", pid);\n        } else {\n            trace!(\"Icon by cmdline {cmd} for {class} by PID ({pid})\");\n            if let Some(icon_path) = load_icon_from_cache(cmd, &image) {\n                // add the icon path back into cache\n                // to directly link class name to icon without checking pid again\n                add_path_for_icon_by_pid_exec(class, icon_path)\n                    .warn_details(\"Failed to add icon path to cache\");\n            }\n        }\n    } else {\n        warn!(\"Failed to read cmdline for PID {}\", pid);\n    }\n}\n\n// check if the icon is in theme and apply it\nfn load_icon_from_cache(name: &str, pic: &Image) -> Option<Box<Path>> {\n    let name_lower = name.to_ascii_lowercase();\n\n    if default::theme_has_icon_name(name) {\n        pic.set_icon_name(Some(name));\n        Some(Box::from(Path::new(name)))\n    } else if default::theme_has_icon_name(&name_lower) {\n        pic.set_icon_name(Some(&name_lower));\n        Some(Box::from(Path::new(&name_lower)))\n    } else {\n        // check if icon is in desktop file cache and apply it\n        if let Some((icon_path, path, source)) = get_icon_name_by_name_from_desktop_files(name) {\n            trace!(\n                \"Found icon for {name} / {icon_path:?} in cache from source: {source:?} at {path:?}\"\n            );\n            if icon_path.is_absolute() {\n                pic.set_from_file(Some(Path::new(&*icon_path)));\n            } else {\n                pic.set_icon_name(icon_path.file_name().and_then(|name| name.to_str()));\n            }\n            Some(icon_path)\n        } else {\n            trace!(\"Icon for {name} not found in theme or cache, using `application-x-executable`\");\n            pic.set_icon_name(Some(\"application-x-executable\"));\n            None\n        }\n    }\n}\n"
  },
  {
    "path": "crates/windows-lib/src/keybinds.rs",
    "content": "use config_lib::Windows;\nuse core_lib::binds::{ExecBind, generate_transfer_socat};\nuse core_lib::transfer::{OpenSwitch, TransferType};\n\n#[must_use]\npub fn generate_open_keybinds(windows: &Windows) -> Vec<ExecBind> {\n    let mut binds = Vec::new();\n    if let Some(overview) = &windows.overview {\n        binds.push(ExecBind {\n            mods: vec![overview.modifier.to_str()],\n            key: overview.key.clone(),\n            exec: generate_transfer_socat(&TransferType::OpenOverview).into_boxed_str(),\n        });\n    }\n    if let Some(switch) = &windows.switch {\n        binds.push(ExecBind {\n            mods: vec![switch.modifier.to_str()],\n            key: Box::from(\"tab\"),\n            exec: generate_transfer_socat(&TransferType::OpenSwitch(OpenSwitch { reverse: false }))\n                .into_boxed_str(),\n        });\n        binds.push(ExecBind {\n            mods: vec![switch.modifier.to_str()],\n            key: Box::from(\"grave\"),\n            exec: generate_transfer_socat(&TransferType::OpenSwitch(OpenSwitch { reverse: true }))\n                .into_boxed_str(),\n        });\n        binds.push(ExecBind {\n            mods: vec![switch.modifier.to_str(), \"shift\"],\n            key: Box::from(\"tab\"),\n            exec: generate_transfer_socat(&TransferType::OpenSwitch(OpenSwitch { reverse: true }))\n                .into_boxed_str(),\n        });\n    }\n\n    binds\n}\n"
  },
  {
    "path": "crates/windows-lib/src/lib.rs",
    "content": "mod css;\nmod data;\nmod desktop_map;\nmod global;\nmod icon;\nmod keybinds;\nmod next;\nmod overview;\nmod sort;\nmod switch;\n\npub use css::get_css;\npub use desktop_map::{get_icon_name_by_name_from_desktop_files, reload_class_to_icon_map};\npub use global::{WindowsOverviewData, WindowsSwitchData};\npub use keybinds::generate_open_keybinds;\npub use overview::{\n    close_overview, create_windows_overview_window, open_overview, overview_already_hidden,\n    overview_already_open, stop_overview, update_overview,\n};\npub use switch::{\n    close_switch, create_windows_switch_window, open_switch, stop_switch, switch_already_hidden,\n    switch_already_open, update_switch,\n};\n"
  },
  {
    "path": "crates/windows-lib/src/next.rs",
    "content": "use core_lib::transfer::Direction;\nuse core_lib::{\n    Active, ClientData, ClientId, GetFirstOrLast, HyprlandData, RevIf, WorkspaceData, WorkspaceId,\n};\nuse std::cmp::{max, min};\nuse tracing::{debug, instrument, trace, trace_span, warn};\n\npub fn find_next_workspace(\n    direction: &Direction,\n    wrap: bool,\n    hypr_data: &HyprlandData,\n    active: Active,\n    workspaces_per_row: u8,\n) -> Active {\n    let _span = trace_span!(\"find_next_workspace\", direction = ?direction, wrap = wrap, active = ?active, workspaces_per_row = workspaces_per_row).entered();\n\n    if hypr_data.workspaces.is_empty() {\n        debug!(\"No workspaces available, returning None\");\n        return active;\n    }\n    let filtered = hypr_data\n        .workspaces\n        .iter()\n        .filter(|(_, data)| data.any_client_enabled)\n        .collect::<Vec<_>>();\n    if filtered.len() == 1 {\n        trace!(\"Only one workspaces available, returning current workspaces\");\n        let workspaces = &hypr_data.workspaces[0];\n        return Active {\n            client: None,\n            workspace: workspaces.0,\n            monitor: workspaces.1.monitor,\n        };\n    }\n    let current = filtered\n        .iter()\n        .position(|(id, _)| *id == active.workspace)\n        .unwrap_or_else(|| {\n            warn!(\"Active workspace not found in workspaces, returning first workspace\");\n            0\n        });\n\n    let index = find_next_grid(direction, wrap, filtered.len(), current, workspaces_per_row);\n    #[allow(clippy::map_unwrap_or)]\n    let next_active = filtered\n        .get(index)\n        .map(|(id, data)| Active {\n            client: None,\n            workspace: *id,\n            monitor: data.monitor,\n        })\n        .unwrap_or_else(|| {\n            warn!(\"Unable to find next workspace, returning current workspace\");\n            active\n        });\n    // .expect(\"unable to find next workspace!\");\n    trace!(\"Next active: {next_active:?}\");\n    next_active\n}\n\n#[instrument(\n    level = \"trace\",\n    skip_all,\n    ret,\n    fields(direction = ?direction, wrap = wrap, active = ?active, clients_per_row = clients_per_row)\n)]\npub fn find_next_client(\n    direction: &Direction,\n    wrap: bool,\n    hypr_data: &HyprlandData,\n    active: Active,\n    clients_per_row: u8,\n) -> Active {\n    if hypr_data.clients.is_empty() {\n        debug!(\"No clients available, returning None\");\n        return active;\n    }\n\n    #[allow(clippy::option_if_let_else)]\n    let next_active = match active.client {\n        None => find_first_client(direction, &hypr_data.clients, &hypr_data.workspaces, active),\n        Some(client_id) => {\n            let filtered = hypr_data\n                .clients\n                .iter()\n                .filter(|(_, data)| data.enabled)\n                .collect::<Vec<_>>();\n            if filtered.len() == 1 {\n                trace!(\"Only one client available, returning current client\");\n                let client = &hypr_data.clients[0];\n                return Active {\n                    client: Some(client.0),\n                    workspace: client.1.workspace,\n                    monitor: client.1.monitor,\n                };\n            }\n            let current = filtered\n                .iter()\n                .position(|(id, _)| *id == client_id)\n                .unwrap_or(0);\n            let index = find_next_grid(direction, wrap, filtered.len(), current, clients_per_row);\n            #[allow(clippy::map_unwrap_or)]\n            filtered\n                .get(index)\n                .map(|(id, data)| Active {\n                    client: Some(*id),\n                    workspace: data.workspace,\n                    monitor: data.monitor,\n                })\n                .unwrap_or_else(|| {\n                    warn!(\"Unable to find next client, returning current client\");\n                    active\n                })\n        }\n    };\n\n    trace!(\"Next active: {next_active:?}\");\n    next_active\n}\n\n#[allow(clippy::cast_possible_wrap)] // wrapping wont happen here, number of workspaces or clients are way lower than isize::MAX\n// #[instrument(level = \"trace\", skip_all, ret, fields(len = filtered_len, current = current))]\nfn find_next_grid(\n    direction: &Direction,\n    wrap: bool,\n    filtered_len: usize,\n    current: usize,\n    items_per_row: u8,\n) -> usize {\n    let _span = trace_span!(\"find_next_grid\", len = filtered_len, current = current).entered();\n\n    let i_per_row = isize::from(items_per_row);\n    let items_per_row = usize::from(items_per_row);\n    let offset = match direction {\n        Direction::Right => 1,\n        Direction::Left => -1,\n        Direction::Up => -i_per_row,\n        Direction::Down => i_per_row,\n    };\n    trace!(\"Finding next workspace with offset: {}\", offset);\n    let index = if wrap {\n        let mut index = current as isize + offset;\n        if index >= filtered_len as isize {\n            trace!(\"Index out of bounds, wrapping around {index}\");\n            // subtract all rows / move to the beginning\n            index -= (items_per_row * usize::div_ceil(filtered_len - 1, items_per_row)) as isize;\n            if index < 0 {\n                match direction {\n                    Direction::Right => index = 0,\n                    Direction::Down => {\n                        index += i_per_row;\n                    }\n                    _ => unreachable!(),\n                }\n            }\n        } else if index < 0 {\n            trace!(\"Index out of bounds, wrapping around {index}\");\n            // add all rows / move to end\n            index += (items_per_row * usize::div_ceil(filtered_len - 1, items_per_row)) as isize;\n            if index >= filtered_len as isize {\n                match direction {\n                    Direction::Left => {\n                        index = filtered_len as isize - 1;\n                    }\n                    Direction::Up => {\n                        index -= i_per_row;\n                    }\n                    _ => unreachable!(),\n                }\n            }\n        }\n        #[allow(clippy::cast_sign_loss)] // index always positive, see if else above\n        {\n            index as usize\n        }\n    } else {\n        #[allow(clippy::cast_sign_loss)]\n        // max(current as isize + offset, 0) always positive (max function)\n        {\n            min(max(current as isize + offset, 0) as usize, filtered_len - 1)\n        }\n    };\n    trace!(\"Next index: {index}\");\n    index\n}\n\nfn find_first_client(\n    direction: &Direction,\n    clients: &[(ClientId, ClientData)],\n    workspaces: &[(WorkspaceId, WorkspaceData)],\n    active: Active,\n) -> Active {\n    let get_last = matches!(direction, Direction::Left | Direction::Up);\n    let (id, next) = clients\n        .iter()\n        .filter(|(_, c)| c.workspace == active.workspace && c.enabled)\n        .get_first_or_last(get_last)\n        .unwrap_or_else(|| {\n            trace!(\"No client found in current workspace, looking for next client in workspaces\");\n            workspaces\n                .iter()\n                .reverse_if(get_last)\n                .skip_while(|(id, _)| id != &active.workspace)\n                .find_map(|(id, _)| {\n                    trace!(\"Finding first client in workspace {id:?}\");\n                    clients\n                        .iter()\n                        .filter(|(_, c)| c.workspace == *id && c.enabled)\n                        .get_first_or_last(get_last)\n                })\n                .unwrap_or_else(|| {\n                    clients\n                        .iter()\n                        .filter(|(_, c)| c.monitor == active.monitor)\n                        .get_first_or_last(get_last)\n                        .unwrap_or_else(|| {\n                            clients\n                                .iter()\n                                .get_first_or_last(get_last)\n                                .expect(\"clients contain at least two clients\")\n                        })\n                })\n        });\n    Active {\n        client: Some(*id),\n        workspace: next.workspace,\n        monitor: next.monitor,\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[allow(clippy::type_complexity)]\n    fn create_test_data(\n        client_count: usize,\n        workspace_count: usize,\n        enabled: Option<usize>,\n    ) -> (\n        Vec<(ClientId, ClientData)>,\n        Vec<(WorkspaceId, WorkspaceData)>,\n    ) {\n        let clients: Vec<(ClientId, ClientData)> = (0..client_count)\n            .map(|i| {\n                (\n                    i as ClientId,\n                    ClientData {\n                        x: 0,\n                        y: 0,\n                        width: 0,\n                        height: 0,\n                        class: String::new(),\n                        title: String::new(),\n                        #[allow(clippy::cast_possible_wrap)]\n                        workspace: (i % workspace_count) as WorkspaceId,\n                        monitor: 0,\n                        focus_history_id: 0,\n                        floating: false,\n                        #[allow(clippy::map_unwrap_or)]\n                        enabled: enabled.map(|s| s >= i).unwrap_or(true),\n                        pid: 0,\n                    },\n                )\n            })\n            .collect();\n\n        #[allow(clippy::cast_possible_wrap)]\n        let workspaces: Vec<(WorkspaceId, WorkspaceData)> = (0..workspace_count)\n            .map(|i| {\n                (\n                    i as WorkspaceId,\n                    WorkspaceData {\n                        name: String::new(),\n                        width: 0,\n                        height: 0,\n                        monitor: 0,\n                        any_client_enabled: clients\n                            .iter()\n                            .any(|(_, c)| c.workspace == i as WorkspaceId && c.enabled),\n                    },\n                )\n            })\n            .collect();\n\n        (clients, workspaces)\n    }\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_find_next_workspace_0() {\n        let (clients, workspaces) = create_test_data(0, 0, None);\n        let hypr_data = HyprlandData {\n            clients,\n            workspaces,\n            monitors: vec![],\n        };\n        let active = Active {\n            client: None,\n            workspace: 0,\n            monitor: 0,\n        };\n        trace!(\"data: {hypr_data:?}\");\n\n        assert_eq!(\n            find_next_workspace(&Direction::Right, true, &hypr_data, active, 3),\n            active\n        );\n        assert_eq!(\n            find_next_workspace(&Direction::Left, true, &hypr_data, active, 6),\n            active\n        );\n        assert_eq!(\n            find_next_workspace(&Direction::Up, false, &hypr_data, active, 200),\n            active\n        );\n        assert_eq!(\n            find_next_workspace(&Direction::Down, false, &hypr_data, active, 0),\n            active\n        );\n    }\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_find_next_workspace_1_filter() {\n        let (clients, workspaces) = create_test_data(2, 1, Some(0));\n        let hypr_data = HyprlandData {\n            clients,\n            workspaces,\n            monitors: vec![],\n        };\n        let active = Active {\n            client: None,\n            workspace: 0,\n            monitor: 0,\n        };\n        trace!(\"data: {hypr_data:?}\");\n\n        assert_eq!(\n            find_next_workspace(&Direction::Right, true, &hypr_data, active, 3),\n            active\n        );\n        assert_eq!(\n            find_next_workspace(&Direction::Left, true, &hypr_data, active, 3),\n            active\n        );\n        assert_eq!(\n            find_next_workspace(&Direction::Up, true, &hypr_data, active, 3),\n            active\n        );\n        assert_eq!(\n            find_next_workspace(&Direction::Down, true, &hypr_data, active, 3),\n            active\n        );\n        assert_eq!(\n            find_next_workspace(&Direction::Right, false, &hypr_data, active, 3),\n            active\n        );\n        assert_eq!(\n            find_next_workspace(&Direction::Left, false, &hypr_data, active, 3),\n            active\n        );\n        assert_eq!(\n            find_next_workspace(&Direction::Up, false, &hypr_data, active, 3),\n            active\n        );\n        assert_eq!(\n            find_next_workspace(&Direction::Down, false, &hypr_data, active, 3),\n            active\n        );\n    }\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_find_next_workspace_1() {\n        let (clients, workspaces) = create_test_data(1, 1, None);\n        let hypr_data = HyprlandData {\n            clients,\n            workspaces,\n            monitors: vec![],\n        };\n        let active = Active {\n            client: None,\n            workspace: 0,\n            monitor: 0,\n        };\n        trace!(\"data: {hypr_data:?}\");\n\n        assert_eq!(\n            find_next_workspace(&Direction::Right, true, &hypr_data, active, 3),\n            active\n        );\n        assert_eq!(\n            find_next_workspace(&Direction::Left, true, &hypr_data, active, 3),\n            active\n        );\n        assert_eq!(\n            find_next_workspace(&Direction::Up, true, &hypr_data, active, 3),\n            active\n        );\n        assert_eq!(\n            find_next_workspace(&Direction::Down, true, &hypr_data, active, 3),\n            active\n        );\n        assert_eq!(\n            find_next_workspace(&Direction::Right, false, &hypr_data, active, 3),\n            active\n        );\n        assert_eq!(\n            find_next_workspace(&Direction::Left, false, &hypr_data, active, 3),\n            active\n        );\n        assert_eq!(\n            find_next_workspace(&Direction::Up, false, &hypr_data, active, 3),\n            active\n        );\n        assert_eq!(\n            find_next_workspace(&Direction::Down, false, &hypr_data, active, 3),\n            active\n        );\n    }\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_find_next_workspace_2() {\n        let (clients, workspaces) = create_test_data(5, 5, None);\n        let hypr_data = HyprlandData {\n            clients,\n            workspaces,\n            monitors: vec![],\n        };\n        let active = Active {\n            client: None,\n            workspace: 0,\n            monitor: 0,\n        };\n        trace!(\"data: {hypr_data:?}\");\n\n        let next = find_next_workspace(&Direction::Right, true, &hypr_data, active, 3);\n        assert_eq!(next.workspace, 1);\n        let next = find_next_workspace(&Direction::Left, true, &hypr_data, active, 3);\n        assert_eq!(next.workspace, 4);\n\n        let next = find_next_workspace(&Direction::Up, true, &hypr_data, active, 3);\n        assert_eq!(next.workspace, 3);\n        let next = find_next_workspace(&Direction::Down, true, &hypr_data, active, 3);\n        assert_eq!(next.workspace, 3);\n\n        let next = find_next_workspace(&Direction::Right, false, &hypr_data, active, 3);\n        assert_eq!(next.workspace, 1);\n        let next = find_next_workspace(&Direction::Left, false, &hypr_data, active, 3);\n        assert_eq!(next.workspace, 0);\n\n        let next = find_next_workspace(&Direction::Up, false, &hypr_data, active, 3);\n        assert_eq!(next.workspace, 0);\n        let next = find_next_workspace(&Direction::Down, false, &hypr_data, active, 3);\n        assert_eq!(next.workspace, 3);\n    }\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_find_next_workspace_2_filter() {\n        let (clients, workspaces) = create_test_data(5, 5, Some(2));\n        let hypr_data = HyprlandData {\n            clients,\n            workspaces,\n            monitors: vec![],\n        };\n        let active = Active {\n            client: None,\n            workspace: 0,\n            monitor: 0,\n        };\n        trace!(\"data: {hypr_data:?}\");\n\n        let next = find_next_workspace(&Direction::Right, true, &hypr_data, active, 3);\n        assert_eq!(next.workspace, 1);\n        let next = find_next_workspace(&Direction::Left, true, &hypr_data, active, 3);\n        assert_eq!(next.workspace, 2);\n\n        let next = find_next_workspace(&Direction::Up, true, &hypr_data, active, 3);\n        assert_eq!(next.workspace, 0);\n        let next = find_next_workspace(&Direction::Down, true, &hypr_data, active, 3);\n        assert_eq!(next.workspace, 0);\n\n        let next = find_next_workspace(&Direction::Right, false, &hypr_data, active, 3);\n        assert_eq!(next.workspace, 1);\n        let next = find_next_workspace(&Direction::Left, false, &hypr_data, active, 3);\n        assert_eq!(next.workspace, 0);\n\n        let next = find_next_workspace(&Direction::Up, false, &hypr_data, active, 3);\n        assert_eq!(next.workspace, 0);\n        let next = find_next_workspace(&Direction::Down, false, &hypr_data, active, 3);\n        assert_eq!(next.workspace, 2);\n    }\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_find_next_client() {\n        // Test with no clients\n        let (clients, workspaces) = create_test_data(0, 1, None);\n        let hypr_data = HyprlandData {\n            clients,\n            workspaces,\n            monitors: vec![],\n        };\n        let active = Active {\n            client: None,\n            workspace: 0,\n            monitor: 0,\n        };\n        trace!(\"data: {hypr_data:?}\");\n\n        assert_eq!(\n            find_next_client(&Direction::Right, true, &hypr_data, active, 3),\n            active\n        );\n\n        // Test with one client\n        let (clients, workspaces) = create_test_data(1, 1, None);\n        let hypr_data = HyprlandData {\n            clients,\n            workspaces,\n            monitors: vec![],\n        };\n        trace!(\"data: {hypr_data:?}\");\n\n        let next = find_next_client(&Direction::Right, true, &hypr_data, active, 3);\n        assert_eq!(\n            next,\n            Active {\n                client: Some(0),\n                workspace: 0,\n                monitor: 0,\n            }\n        );\n\n        // Test with multiple clients\n        let (clients, workspaces) = create_test_data(4, 2, None);\n        let hypr_data = HyprlandData {\n            clients,\n            workspaces,\n            monitors: vec![],\n        };\n        trace!(\"data: {hypr_data:?}\");\n\n        // Test with no active client\n        let next = find_next_client(&Direction::Right, true, &hypr_data, active, 3);\n        assert_eq!(next.client, Some(0));\n\n        // Test with active client\n        let active = Active {\n            client: Some(1),\n            workspace: 1,\n            monitor: 0,\n        };\n\n        // Test right direction with wrap\n        let next = find_next_client(&Direction::Right, true, &hypr_data, active, 3);\n        assert_eq!(next.client, Some(2));\n\n        // Test left direction with wrap\n        let next = find_next_client(&Direction::Left, true, &hypr_data, active, 3);\n        assert_eq!(next.client, Some(0));\n\n        // Test without wrap\n        let next = find_next_client(&Direction::Right, false, &hypr_data, active, 3);\n        assert_eq!(next.client, Some(2));\n    }\n}\n"
  },
  {
    "path": "crates/windows-lib/src/overview/close.rs",
    "content": "use crate::global::WindowsOverviewData;\nuse core_lib::transfer::WindowsOverride;\nuse core_lib::{FindByFirst, WarnWithDetails};\nuse exec_lib::switch::{switch_client, switch_workspace};\nuse exec_lib::{reset_no_follow_mouse, to_client_address};\nuse relm4::adw::gtk::glib;\nuse relm4::adw::gtk::prelude::*;\nuse tracing::{debug, debug_span, trace};\n\n#[must_use]\npub fn overview_already_hidden(data: &WindowsOverviewData) -> bool {\n    !data.window_list.iter().any(|w| w.0.get_visible())\n}\n\npub fn close_overview(data: &mut WindowsOverviewData, ids: Option<Option<WindowsOverride>>) {\n    let _span = debug_span!(\"close_overview\").entered();\n    reset_no_follow_mouse().warn_details(\"Failed to reset follow mouse\");\n\n    for (window, monitor_data) in &mut data.window_list.iter_mut() {\n        while let Some(child) = monitor_data.workspaces_flow.first_child() {\n            monitor_data.workspaces_flow.remove(&child);\n        }\n        trace!(\"Hiding window (windows) {:?}\", window.id());\n        window.set_visible(false);\n    }\n\n    if let Some(ids) = ids {\n        let ids = match ids {\n            None => data.active.client.map_or_else(\n                || WindowsOverride::WorkspaceID(data.active.workspace),\n                WindowsOverride::ClientId,\n            ),\n            Some(WindowsOverride::ClientId(client_id)) => WindowsOverride::ClientId(client_id),\n            Some(WindowsOverride::WorkspaceID(workspace_id)) => {\n                WindowsOverride::WorkspaceID(workspace_id)\n            }\n        };\n        match ids {\n            WindowsOverride::ClientId(client_id) => {\n                debug!(\n                    \"Switching to client {}\",\n                    data.hypr_data\n                        .clients\n                        .find_by_first(&client_id)\n                        .map_or_else(|| \"<Unknown>\".to_string(), |c| c.title.clone())\n                );\n                glib::idle_add_local(move || {\n                    switch_client(to_client_address(client_id))\n                        .warn_details(&format!(\"Failed to execute with id {client_id:?}\"));\n                    glib::ControlFlow::Break\n                });\n            }\n            WindowsOverride::WorkspaceID(workspace_id) => {\n                debug!(\n                    \"Switching to workspace {}\",\n                    data.hypr_data\n                        .workspaces\n                        .find_by_first(&workspace_id)\n                        .map_or_else(|| \"<Unknown>\".to_string(), |c| c.name.clone())\n                );\n                glib::idle_add_local(move || {\n                    switch_workspace(workspace_id).warn_details(&format!(\n                        \"Failed to execute switch workspace with id {workspace_id:?}\"\n                    ));\n                    glib::ControlFlow::Break\n                });\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "crates/windows-lib/src/overview/create.rs",
    "content": "use crate::global::{WindowsOverviewConfig, WindowsOverviewData, WindowsOverviewMonitorData};\nuse anyhow::Context;\nuse config_lib::{FilterBy, Overview, Windows};\nuse core_lib::{HyprlandData, OVERVIEW_NAMESPACE};\nuse exec_lib::{get_initial_active, get_monitors};\nuse gtk4_layer_shell::{Edge, KeyboardMode, Layer, LayerShell};\nuse relm4::adw::gtk::gdk::{Display, Monitor};\nuse relm4::adw::gtk::prelude::*;\nuse relm4::adw::gtk::{\n    Application, ApplicationWindow, FlowBox, Orientation, Overlay, SelectionMode,\n};\nuse std::collections::HashMap;\nuse tracing::{debug, debug_span};\n\npub fn create_windows_overview_window(\n    app: &Application,\n    overview: &Overview,\n    windows: &Windows,\n) -> anyhow::Result<WindowsOverviewData> {\n    let _span = debug_span!(\"create_windows_overview_window\").entered();\n    let mut window_list = HashMap::new();\n\n    let monitors = get_monitors();\n    if let Ok(display) = Display::default().context(\"Could not connect to a display\") {\n        let gtk_monitors = display\n            .monitors()\n            .iter()\n            .filter_map(Result::ok)\n            .collect::<Vec<Monitor>>();\n\n        for gtk_monitor in gtk_monitors {\n            let monitor_name = gtk_monitor.connector().unwrap_or_default();\n            if let Some(monitor) = monitors.iter().find(|m| m.name == monitor_name) {\n                let workspaces_flow = FlowBox::builder()\n                    .selection_mode(SelectionMode::None)\n                    .orientation(Orientation::Horizontal)\n                    .max_children_per_line(u32::from(windows.items_per_row))\n                    .min_children_per_line(u32::from(windows.items_per_row))\n                    .build();\n\n                let workspaces_flow_overlay = Overlay::builder()\n                    .child(&workspaces_flow)\n                    .css_classes([\"monitor\"])\n                    .build();\n\n                let window = ApplicationWindow::builder()\n                    .css_classes([\"window\"])\n                    .application(app)\n                    .child(&workspaces_flow_overlay)\n                    .default_height(10)\n                    .default_width(10)\n                    .build();\n\n                window.init_layer_shell();\n                window.set_namespace(Some(OVERVIEW_NAMESPACE));\n                window.set_layer(Layer::Top);\n                window.set_anchor(Edge::Top, true);\n                window.set_margin(Edge::Top, 430i32);\n                window.set_keyboard_mode(KeyboardMode::None);\n                window.set_monitor(Some(&gtk_monitor));\n\n                debug!(\n                    \"Created overview window ({}) for monitor {monitor_name:?}\",\n                    window.id()\n                );\n                window_list.insert(\n                    window,\n                    WindowsOverviewMonitorData::new(monitor.id, workspaces_flow),\n                );\n            }\n        }\n    }\n\n    let active = get_initial_active().context(\"unable to get initial active data\")?;\n    Ok(WindowsOverviewData {\n        config: WindowsOverviewConfig {\n            items_per_row: windows.items_per_row,\n            scale: windows.scale,\n            filter_current_workspace: overview.filter_by.contains(&FilterBy::CurrentWorkspace),\n            filter_current_monitor: overview.filter_by.contains(&FilterBy::CurrentMonitor),\n            filter_same_class: overview.filter_by.contains(&FilterBy::SameClass),\n        },\n        window_list,\n        active,\n        initial_active: active,\n        hypr_data: HyprlandData::default(),\n    })\n}\n"
  },
  {
    "path": "crates/windows-lib/src/overview/mod.rs",
    "content": "mod close;\nmod create;\nmod open;\nmod stop;\nmod update;\n\npub use close::*;\npub use create::*;\npub use open::*;\npub use stop::*;\npub use update::*;\n"
  },
  {
    "path": "crates/windows-lib/src/overview/open.rs",
    "content": "use crate::data::{SortConfig, collect_data};\nuse crate::global::WindowsOverviewData;\nuse crate::icon::set_icon;\nuse anyhow::Context;\nuse async_channel::Sender;\nuse core_lib::transfer::{CloseOverviewConfig, TransferType, WindowsOverride};\nuse core_lib::{ClientId, WarnWithDetails};\nuse exec_lib::set_no_follow_mouse;\nuse relm4::adw::gtk::gdk::Cursor;\nuse relm4::adw::gtk::prelude::*;\nuse relm4::adw::gtk::{Button, Fixed, Frame, Image, Label, Overflow, Overlay, pango};\nuse relm4::gtk;\nuse std::borrow::Cow;\nuse tracing::{debug, debug_span, trace};\n\nfn scale<T: Into<f64>>(value: T, scale: f64) -> i32 {\n    (value.into() / (15f64 - scale)) as i32\n}\n\n#[must_use]\npub fn overview_already_open(data: &WindowsOverviewData) -> bool {\n    data.window_list.iter().any(|w| w.0.get_visible())\n}\n\n#[allow(clippy::too_many_lines)]\npub fn open_overview(\n    data: &mut WindowsOverviewData,\n    event_sender: &Sender<TransferType>,\n) -> anyhow::Result<()> {\n    let _span = debug_span!(\"open_overview\").entered();\n    set_no_follow_mouse().warn_details(\"Failed to set set_remain_focused\");\n\n    let (hypr_data, active) = collect_data(&SortConfig {\n        filter_current_monitor: data.config.filter_current_monitor,\n        filter_current_workspace: data.config.filter_current_workspace,\n        filter_same_class: data.config.filter_same_class,\n        sort_recent: false,\n    })\n    .context(\"Failed to collect data\")?;\n    let remove_html = regex::Regex::new(r\"<[^>]*>\").context(\"Invalid regex\")?;\n\n    for (window, monitor_data) in &mut data.window_list {\n        trace!(\"Showing window {:?}\", window.id());\n        window.set_visible(true);\n\n        'workspaces: for (wid, workspace) in &hypr_data.workspaces {\n            if workspace.monitor != monitor_data.id {\n                continue 'workspaces;\n            }\n            trace!(\n                \"Creating workspace {wid} with ({}x{})\",\n                scale(workspace.width, data.config.scale),\n                scale(workspace.height, data.config.scale)\n            );\n            let workspace_fixed = Fixed::builder()\n                .width_request(scale(workspace.width, data.config.scale))\n                .height_request(scale(workspace.height, data.config.scale))\n                .build();\n            let id_string = wid.to_string();\n            let title = if workspace.name.trim().is_empty() {\n                Cow::from(&id_string)\n            } else {\n                remove_html.replace_all(&workspace.name, \"\")\n            };\n\n            let workspace_frame = Frame::builder()\n                .label(title)\n                .label_xalign(0.5)\n                .child(&workspace_fixed)\n                .build();\n\n            let workspace_button = {\n                let workspace_overlay = Overlay::builder().child(&workspace_frame).build();\n                let button = gtk::Box::builder().css_classes([\"workspace\"]).build();\n                button.append(&workspace_overlay);\n                if active.client.is_none() && active.workspace == *wid {\n                    button.add_css_class(\"active\");\n                }\n                button\n            };\n            if workspace.name.starts_with(\"special:\") {\n                workspace_button.add_css_class(\"special\");\n            }\n            monitor_data.workspaces_flow.insert(&workspace_button, -1);\n            monitor_data.workspaces.insert(*wid, workspace_button);\n\n            'clients: for (address, client) in &hypr_data.clients {\n                if client.workspace != *wid {\n                    continue 'clients;\n                }\n                let client_button = {\n                    let title = if client.title.trim().is_empty() {\n                        &client.class\n                    } else {\n                        &client.title\n                    };\n                    let client_label = Label::builder()\n                        .label(title)\n                        .overflow(Overflow::Visible)\n                        .margin_start(6)\n                        .ellipsize(pango::EllipsizeMode::End)\n                        .build();\n                    let client_frame = Frame::builder()\n                        .label_xalign(0.5)\n                        .label_widget(&client_label)\n                        .build();\n\n                    // hide picture if client so small\n                    let client_h_w = scale(client.height, data.config.scale)\n                        .min(scale(client.width, data.config.scale));\n                    if client_h_w > 60 {\n                        let image = Image::builder()\n                            .css_classes([\"client-image\"])\n                            .pixel_size((f64::from(client_h_w.clamp(50, 600)) / 1.7) as i32 - 20)\n                            .build();\n                        if !client.enabled {\n                            image.add_css_class(\"monochrome\");\n                        }\n                        set_icon(&client.class, client.pid, &image);\n                        client_frame.set_child(Some(&image));\n                    }\n\n                    let client_overlay = Overlay::builder()\n                        .overflow(Overflow::Hidden)\n                        .child(&client_frame)\n                        .build();\n                    let button = Button::builder()\n                        .child(&client_overlay)\n                        .css_classes([\"client\"])\n                        .width_request(scale(client.width, data.config.scale))\n                        .height_request(scale(client.height, data.config.scale))\n                        .build();\n                    button.set_cursor(Cursor::from_name(\"pointer\", None).as_ref());\n\n                    // add initial border around initial active client\n                    if active.client == Some(*address) {\n                        button.add_css_class(\"active\");\n                    }\n\n                    click_client(&button, *address, event_sender.clone());\n                    button\n                };\n                trace!(\n                    \"Creating Client {address} with ({}x{}) at ({}x{})\",\n                    scale(client.width, data.config.scale),\n                    scale(client.height, data.config.scale),\n                    f64::from(scale(client.x, data.config.scale)),\n                    f64::from(scale(client.y, data.config.scale))\n                );\n                workspace_fixed.put(\n                    &client_button,\n                    f64::from(scale(client.x, data.config.scale)),\n                    f64::from(scale(client.y, data.config.scale)),\n                );\n                monitor_data.clients.insert(*address, client_button);\n            }\n        }\n    }\n\n    data.active = active;\n    data.initial_active = active;\n    data.hypr_data = hypr_data;\n    Ok(())\n}\n\nfn click_client(button: &Button, client_id: ClientId, event_sender: Sender<TransferType>) {\n    button.connect_clicked(move |_| {\n        debug!(\"Exiting on click of client button\");\n        event_sender\n            .send_blocking(TransferType::CloseOverview(CloseOverviewConfig::Windows(\n                WindowsOverride::ClientId(client_id),\n            )))\n            .warn_details(\"unable to send\");\n    });\n}\n"
  },
  {
    "path": "crates/windows-lib/src/overview/stop.rs",
    "content": "use crate::global::WindowsOverviewData;\nuse core_lib::WarnWithDetails;\nuse exec_lib::reset_no_follow_mouse;\nuse relm4::adw::gtk::prelude::*;\nuse tracing::{debug_span, trace};\n\npub fn stop_overview(data: &WindowsOverviewData) {\n    let _span = debug_span!(\"stop_overview\").entered();\n    reset_no_follow_mouse().warn_details(\"Failed to reset follow mouse\");\n    for window in data.window_list.keys() {\n        trace!(\"Closing window {:?}\", window.id());\n        window.close();\n    }\n}\n"
  },
  {
    "path": "crates/windows-lib/src/overview/update.rs",
    "content": "use crate::global::WindowsOverviewData;\nuse crate::next::{find_next_client, find_next_workspace};\nuse core_lib::transfer::{Direction, SwitchOverviewConfig};\nuse relm4::adw::gtk::prelude::*;\nuse tracing::{debug_span, error};\n\npub fn update_overview(data: &mut WindowsOverviewData, config: &SwitchOverviewConfig) {\n    let _span = debug_span!(\"update_overview\").entered();\n\n    let active = if config.workspace {\n        find_next_workspace(\n            &config.direction,\n            false,\n            &data.hypr_data,\n            data.active,\n            data.config.items_per_row,\n        )\n    } else {\n        if config.direction == Direction::Up || config.direction == Direction::Down {\n            error!(\n                \"Clients in overview can only be switched left and right (forwards and backwards)\"\n            );\n            return;\n        }\n        find_next_client(\n            &config.direction,\n            false,\n            &data.hypr_data,\n            data.active,\n            data.config.items_per_row,\n        )\n    };\n    data.active = active;\n\n    for monitor_data in data.window_list.values_mut() {\n        if config.workspace {\n            for button in monitor_data.clients.values_mut() {\n                button.remove_css_class(\"active\");\n            }\n            for (id, button) in &mut monitor_data.workspaces {\n                button.remove_css_class(\"active\");\n                if active.workspace == *id {\n                    button.add_css_class(\"active\");\n                }\n            }\n        } else {\n            for button in monitor_data.workspaces.values_mut() {\n                button.remove_css_class(\"active\");\n            }\n            for (id, button) in &mut monitor_data.clients {\n                button.remove_css_class(\"active\");\n                if active.client == Some(*id) {\n                    button.add_css_class(\"active\");\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "crates/windows-lib/src/sort.rs",
    "content": "use core_lib::{ClientData, ClientId, MonitorData, MonitorId, WorkspaceData, WorkspaceId};\nuse std::cmp::Ordering;\nuse std::collections::{BTreeMap, HashMap, VecDeque};\n\n/// Sorts clients with complex sorting\npub fn sort_clients_by_position(\n    clients: Vec<(ClientId, ClientData)>,\n    workspaces: &Vec<(WorkspaceId, WorkspaceData)>,\n    monitors: &Vec<(MonitorId, MonitorData)>,\n) -> Vec<(ClientId, ClientData)> {\n    // monitor -> workspace -> clients\n    let mut presorted_clients = {\n        let mut presorted_clients: BTreeMap<\n            MonitorId,\n            BTreeMap<WorkspaceId, Vec<(ClientId, ClientData)>>,\n        > = BTreeMap::new();\n        for (addr, client) in clients {\n            presorted_clients\n                .entry(client.monitor)\n                .or_default()\n                .entry(client.workspace)\n                .or_default()\n                .push((addr, client));\n        }\n        presorted_clients\n    };\n\n    let mut sorted_clients = Vec::new();\n    for (monitor_id, _) in monitors {\n        for (workspace_id, _) in workspaces {\n            let mut clients = presorted_clients\n                .get_mut(monitor_id)\n                .and_then(|m| m.remove(workspace_id))\n                .unwrap_or(vec![]);\n            clients.sort_by(|(_, a), (_, b)| {\n                if a.x == b.x {\n                    a.y.cmp(&b.y)\n                } else {\n                    a.x.cmp(&b.x)\n                }\n            });\n            let mut queue: VecDeque<(ClientId, ClientData)> = VecDeque::from(clients);\n\n            let mut line_start = queue.pop_front();\n            while let Some((current_addr, current)) = line_start {\n                let mut current_bottom = current.y + current.height;\n                sorted_clients.push((current_addr, current));\n\n                loop {\n                    let mut next_index = None;\n\n                    /*\n                    1. Check If Top left of window is higher or lower than bottom left of current\n                    2. Check if any window(not taken) on left top is higher or lower than current Lower (if true take this)\n                    3. Check if any window(not taken) on left bottom is higher than current bottom (if true take this)\n                    => Take if Top higher than current Bottom and no window on left has higher Top than window Bottom\n                     */\n                    for (i, (_, client)) in queue.iter().enumerate() {\n                        let client_top = client.y;\n                        let client_bottom = client.y + client.height;\n                        let client_left = client.x;\n\n                        if client_top < current_bottom {\n                            // 1.\n                            // client top is inside current row\n\n                            // 2.\n                            let on_left = queue\n                                .iter()\n                                .enumerate()\n                                .find(|(_, (_, c))| c.x < client_left && c.y < client_bottom);\n\n                            // 3.\n                            let on_left_2 = queue.iter().enumerate().find(|(_, (_, c))| {\n                                c.x < client_left && c.y + c.height < client_bottom\n                            });\n\n                            match (on_left, on_left_2) {\n                                (Some((idx, (_, c))), _) | (_, Some((idx, (_, c)))) => {\n                                    current_bottom = c.y + c.height;\n                                    next_index = Some(idx);\n                                }\n                                (None, None) => {\n                                    next_index = Some(i);\n                                }\n                            }\n                            break;\n                        }\n                    }\n                    match next_index.and_then(|i| queue.remove(i)) {\n                        Some(next) => {\n                            sorted_clients.push(next);\n                        }\n                        None => {\n                            break;\n                        }\n                    }\n                }\n                line_start = queue.pop_front();\n            }\n        }\n    }\n\n    sorted_clients\n}\n\npub fn sort_clients_by_recent(clients: &mut [(ClientId, ClientData)]) {\n    let focus_map = clients\n        .iter()\n        .map(|(id, client_data)| (*id, client_data.focus_history_id))\n        .collect::<HashMap<ClientId, i8>>();\n    clients.sort_by(|(a_addr, a), (b_addr, b)| {\n        match (focus_map.get(a_addr), focus_map.get(b_addr)) {\n            (None, None) => a.focus_history_id.cmp(&b.focus_history_id), // both none -> sort by focus_history_id\n            (None, Some(_)) => Ordering::Greater,\n            (Some(_), None) => Ordering::Less,\n            (Some(a_id), Some(b_id)) => a_id.cmp(b_id),\n        }\n    });\n}\n\npub fn sort_workspaces_by_recent(\n    workspaces: &mut [(WorkspaceId, WorkspaceData)],\n    clients: &[(ClientId, ClientData)],\n) {\n    let mut ordering = vec![];\n    for (_, client) in clients {\n        if !ordering.contains(&client.workspace) {\n            ordering.push(client.workspace);\n        }\n    }\n\n    workspaces.sort_by(|(a_id, _), (b_id, _)| {\n        let a_pos = ordering\n            .iter()\n            .position(|id| id == a_id)\n            .unwrap_or(usize::MAX);\n        let b_pos = ordering\n            .iter()\n            .position(|id| id == b_id)\n            .unwrap_or(usize::MAX);\n        a_pos.cmp(&b_pos)\n    });\n}\n\npub fn sort_monitor_by_x(monitors: &mut [(MonitorId, MonitorData)]) {\n    monitors.sort_by(|(_, a), (_, b)| a.x.cmp(&b.x));\n}\n\npub fn sort_workspaces_by_position(\n    workspaces: &mut [(WorkspaceId, WorkspaceData)],\n    monitors: &[(MonitorId, MonitorData)],\n) {\n    workspaces.sort_by(|(a_id, a), (b_id, b)| {\n        let monitor_a = monitors\n            .iter()\n            .position(|(id, _)| id == &a.monitor)\n            .unwrap_or(0);\n        let monitor_b = monitors\n            .iter()\n            .position(|(id, _)| id == &b.monitor)\n            .unwrap_or(0);\n        match monitor_a.cmp(&monitor_b) {\n            // move special workspaces with -ids after normal workspaces\n            Ordering::Equal => {\n                // -10 > 10\n                if *a_id < 0 && *b_id > 0 {\n                    return Ordering::Greater;\n                }\n                // 10 > -10\n                if *a_id > 0 && *b_id < 0 {\n                    return Ordering::Less;\n                }\n                a_id.cmp(b_id)\n            }\n            other => other,\n        }\n    });\n}\n"
  },
  {
    "path": "crates/windows-lib/src/styles.css",
    "content": ".monitor {\n    padding: var(--window-padding, 2px);\n\n    box-shadow: 0 0 16px 5px var(--bg-window-color, rgba(20, 30, 40, 0.9));\n\n    border-radius: var(--border-radius, 12px);\n    background: var(--bg-window-color, rgba(20, 20, 20, 0.9));\n    border: var(--border-size, 3px) var(--border-style, solid) var(--border-color, rgba(90, 90, 120, 0.4));\n}\n\n.workspace {\n    font-size: 1.8rem;\n    font-weight: bolder;\n\n    border-radius: var(--border-radius, 12px);\n    border: var(--border-size, 3px) var(--border-style, solid) var(--border-color, rgba(90, 90, 120, 0.4));\n}\n\n.workspace.active {\n    border: var(--border-size, 3px) var(--border-style, solid) var(--border-color-active, rgba(239, 9, 9, 0.9));\n}\n\n.workspace.special {\n    box-shadow: inset 0 0 10px rgba(20, 200, 20, 0.8);\n}\n\n\n.client {\n    font-size: 1.2rem;\n    font-weight: normal;\n\n    border-radius: var(--border-radius, 12px);\n    background: var(--bg-color, rgba(20, 20, 20, 0.9));\n    border: var(--border-size, 3px) var(--border-style, solid) var(--border-color, rgba(90, 90, 120, 0.4));\n}\n\n.client.active {\n    border: var(--border-size, 3px) var(--border-style, solid) var(--border-color-active, rgba(239, 9, 9, 0.9));\n}\n\n.client.no-hover:hover {\n    background: var(--bg-color, rgba(20, 20, 20, 0.9));\n}\n\n.client:hover {\n    background: var(--bg-color-hover, rgba(40, 40, 50, 1));\n}\n\n\n.client-image {\n    margin-top: 4px;\n    margin-bottom: 4px;\n}"
  },
  {
    "path": "crates/windows-lib/src/switch/close.rs",
    "content": "use crate::global::WindowsSwitchData;\nuse core_lib::{FindByFirst, WarnWithDetails};\nuse exec_lib::switch::{switch_client, switch_workspace};\nuse exec_lib::{reset_no_follow_mouse, to_client_address};\nuse relm4::adw::gtk::glib;\nuse relm4::adw::gtk::prelude::*;\nuse tracing::{debug, debug_span, trace};\n\n#[must_use]\npub fn switch_already_hidden(data: &WindowsSwitchData) -> bool {\n    !data.window.is_visible()\n}\n\npub fn close_switch(data: &mut WindowsSwitchData, switch: bool) {\n    let _span = debug_span!(\"close_switch\").entered();\n\n    reset_no_follow_mouse().warn_details(\"Failed to reset follow mouse\");\n    while let Some(child) = data.main_flow.first_child() {\n        data.main_flow.remove(&child);\n    }\n    trace!(\"Hiding window (windows) {:?}\", data.window.id());\n    data.window.set_visible(false);\n\n    if switch {\n        if let Some(id) = data.active.client {\n            debug!(\n                \"Switching to client {}\",\n                data.hypr_data\n                    .clients\n                    .find_by_first(&id)\n                    .map_or_else(|| \"<Unknown>\".to_string(), |c| c.title.clone())\n            );\n            // we need to do this because the window might still be visible and have KeyboardMode::Exclusive\n            glib::idle_add_local(move || {\n                switch_client(to_client_address(id))\n                    .warn_details(&format!(\"Failed to execute with id {id:?}\"));\n                glib::ControlFlow::Break\n            });\n        } else {\n            let id = data.active.workspace;\n            debug!(\n                \"Switching to workspace {}\",\n                data.hypr_data\n                    .workspaces\n                    .find_by_first(&id)\n                    .map_or_else(|| \"<Unknown>\".to_string(), |c| c.name.clone())\n            );\n            glib::idle_add_local(move || {\n                switch_workspace(id).warn_details(&format!(\n                    \"Failed to execute switch workspace with id {id:?}\"\n                ));\n                glib::ControlFlow::Break\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "crates/windows-lib/src/switch/create.rs",
    "content": "use crate::global::{WindowsSwitchConfig, WindowsSwitchData};\nuse anyhow::Context;\nuse async_channel::Sender;\nuse config_lib::{FilterBy, Modifier, Switch, Windows};\nuse core_lib::transfer::{Direction, SwitchSwitchConfig, TransferType};\nuse core_lib::{HyprlandData, SWITCH_NAMESPACE, WarnWithDetails};\nuse exec_lib::get_initial_active;\nuse gtk4_layer_shell::{KeyboardMode, Layer, LayerShell};\nuse relm4::adw::gtk::gdk::Key;\nuse relm4::adw::gtk::glib::Propagation;\nuse relm4::adw::gtk::prelude::*;\nuse relm4::adw::gtk::{\n    Application, ApplicationWindow, EventControllerKey, FlowBox, Orientation, Overlay,\n    SelectionMode,\n};\nuse std::collections::HashMap;\nuse tracing::{debug, debug_span};\n\npub fn create_windows_switch_window(\n    app: &Application,\n    switch: &Switch,\n    windows: &Windows,\n    event_sender: Sender<TransferType>,\n) -> anyhow::Result<WindowsSwitchData> {\n    let _span = debug_span!(\"create_windows_switch_window\").entered();\n\n    let clients_flow = FlowBox::builder()\n        .selection_mode(SelectionMode::None)\n        .orientation(Orientation::Horizontal)\n        .max_children_per_line(u32::from(windows.items_per_row))\n        .min_children_per_line(u32::from(windows.items_per_row))\n        .build();\n\n    let clients_flow_overlay = Overlay::builder()\n        .child(&clients_flow)\n        .css_classes([\"monitor\", \"no-hover\"])\n        .build();\n\n    let window = ApplicationWindow::builder()\n        .css_classes([\"window\"])\n        .application(app)\n        .child(&clients_flow_overlay)\n        .default_height(10)\n        .default_width(10)\n        .build();\n\n    let s_key = Key::from_name(switch.key.to_string()).context(\"invalid switch key\")?;\n    let key_controller = EventControllerKey::new();\n    let event_sender_2 = event_sender.clone();\n    key_controller.connect_key_pressed(move |_, key, _, _| handle_key(key, s_key, &event_sender_2));\n    let event_sender_3 = event_sender;\n    let r#mod = switch.modifier;\n    key_controller.connect_key_released(move |_, key, _, _| {\n        handle_release(key, r#mod, &event_sender_3);\n    });\n    window.add_controller(key_controller);\n\n    window.init_layer_shell();\n    window.set_namespace(Some(SWITCH_NAMESPACE));\n    window.set_layer(Layer::Top);\n    // we only have one window, so we can do this\n    // we also don't use relm4::adw::gtk::Popover which doesnt work with exclusive mode\n    window.set_keyboard_mode(KeyboardMode::Exclusive);\n\n    debug!(\"Created switch window ({})\", window.id());\n\n    Ok(WindowsSwitchData {\n        config: WindowsSwitchConfig {\n            items_per_row: windows.items_per_row,\n            scale: windows.scale,\n            filter_current_workspace: switch.filter_by.contains(&FilterBy::CurrentWorkspace),\n            filter_current_monitor: switch.filter_by.contains(&FilterBy::CurrentMonitor),\n            filter_same_class: switch.filter_by.contains(&FilterBy::SameClass),\n            switch_workspaces: switch.switch_workspaces,\n        },\n        window,\n        main_flow: clients_flow,\n        workspaces: HashMap::default(),\n        clients: HashMap::default(),\n        active: get_initial_active().context(\"unable to get initial active data\")?,\n        hypr_data: HyprlandData::default(),\n    })\n}\n\nfn handle_release(key: Key, modifier: Modifier, event_sender: &Sender<TransferType>) {\n    if ((key == Key::Alt_L || key == Key::Alt_R) && modifier == Modifier::Alt)\n        || ((key == Key::Control_L || key == Key::Control_R) && modifier == Modifier::Ctrl)\n        || ((key == Key::Super_L || key == Key::Super_R) && modifier == Modifier::Super)\n    {\n        event_sender\n            .send_blocking(TransferType::CloseSwitch)\n            .warn_details(\"unable to send\");\n    }\n}\n\nfn handle_key(key: Key, s_key: Key, event_sender: &Sender<TransferType>) -> Propagation {\n    match key {\n        k if k == s_key || k == Key::l || k == Key::Right => {\n            event_sender\n                .send_blocking(TransferType::SwitchSwitch(SwitchSwitchConfig {\n                    direction: Direction::Right,\n                }))\n                .warn_details(\"unable to send\");\n            Propagation::Stop\n        }\n        Key::ISO_Left_Tab | Key::grave | Key::dead_grave | Key::h | Key::Left => {\n            event_sender\n                .send_blocking(TransferType::SwitchSwitch(SwitchSwitchConfig {\n                    direction: Direction::Left,\n                }))\n                .warn_details(\"unable to send\");\n            Propagation::Stop\n        }\n        Key::j | Key::Down => {\n            event_sender\n                .send_blocking(TransferType::SwitchSwitch(SwitchSwitchConfig {\n                    direction: Direction::Down,\n                }))\n                .warn_details(\"unable to send\");\n            Propagation::Stop\n        }\n        Key::k | Key::Up => {\n            event_sender\n                .send_blocking(TransferType::SwitchSwitch(SwitchSwitchConfig {\n                    direction: Direction::Up,\n                }))\n                .warn_details(\"unable to send\");\n            Propagation::Stop\n        }\n        _ => Propagation::Proceed,\n    }\n}\n"
  },
  {
    "path": "crates/windows-lib/src/switch/mod.rs",
    "content": "mod close;\nmod create;\nmod open;\nmod stop;\nmod update;\n\npub use close::*;\npub use create::*;\npub use open::*;\npub use stop::*;\npub use update::*;\n"
  },
  {
    "path": "crates/windows-lib/src/switch/open.rs",
    "content": "use crate::data::{SortConfig, collect_data};\nuse crate::global::WindowsSwitchData;\nuse crate::icon::set_icon;\nuse crate::next::{find_next_client, find_next_workspace};\nuse anyhow::Context;\nuse core_lib::transfer::{Direction, OpenSwitch};\nuse core_lib::{ClientData, ClientId, WarnWithDetails};\nuse exec_lib::{get_current_monitor, set_no_follow_mouse};\nuse relm4::adw::gtk::gdk::Cursor;\nuse relm4::adw::gtk::prelude::*;\nuse relm4::adw::gtk::{Button, Fixed, Frame, Image, Label, Overflow, Overlay, pango};\nuse std::borrow::Cow;\nuse tracing::{debug_span, trace};\n\nfn scale<T: Into<f64>>(value: T, scale: f64) -> i32 {\n    (value.into() / (15f64 - scale)) as i32\n}\n\n#[must_use]\npub fn switch_already_open(data: &WindowsSwitchData) -> bool {\n    data.window.get_visible()\n}\n\n#[allow(clippy::too_many_lines)]\npub fn open_switch(data: &mut WindowsSwitchData, config: &OpenSwitch) -> anyhow::Result<()> {\n    let _span = debug_span!(\"open_switch\").entered();\n    set_no_follow_mouse().warn_details(\"Failed to set set_remain_focused\");\n\n    let (clients_data, active_prev) = collect_data(&SortConfig {\n        filter_current_monitor: data.config.filter_current_monitor,\n        filter_current_workspace: data.config.filter_current_workspace,\n        filter_same_class: data.config.filter_same_class,\n        sort_recent: true,\n    })\n    .context(\"Failed to collect data\")?;\n    let dir = if config.reverse {\n        Direction::Left\n    } else {\n        Direction::Right\n    };\n    let active = if data.config.switch_workspaces {\n        find_next_workspace(\n            &dir,\n            true,\n            &clients_data,\n            active_prev,\n            data.config.items_per_row,\n        )\n    } else {\n        find_next_client(\n            &dir,\n            true,\n            &clients_data,\n            active_prev,\n            data.config.items_per_row,\n        )\n    };\n\n    let remove_html = regex::Regex::new(r\"<[^>]*>\").context(\"Invalid regex\")?;\n\n    trace!(\"Showing window {:?}\", data.window.id());\n    data.window.set_visible(true);\n\n    let current_monitor = get_current_monitor().context(\"Failed to get current monitor\")?;\n\n    if data.config.switch_workspaces {\n        for (wid, workspace) in &clients_data.workspaces {\n            let clients: Vec<&(ClientId, ClientData)> = {\n                let mut clients = clients_data\n                    .clients\n                    .iter()\n                    .filter(|(_, client)| client.workspace == *wid && client.enabled)\n                    .collect::<Vec<_>>();\n                clients.sort_by(|(_, a), (_, b)| {\n                    // prefer smaller windows\n                    if a.floating && b.floating {\n                        (b.width * b.height).cmp(&(a.width * a.height))\n                    } else {\n                        a.floating.cmp(&b.floating)\n                    }\n                });\n                clients\n            };\n            if clients.is_empty() {\n                continue;\n            }\n            let workspace_fixed = Fixed::builder()\n                .width_request(scale(workspace.width, data.config.scale))\n                .height_request(scale(workspace.height, data.config.scale))\n                .build();\n            let id_string = wid.to_string();\n            let title = if workspace.name.trim().is_empty() {\n                Cow::from(&id_string)\n            } else {\n                remove_html.replace_all(&workspace.name, \"\")\n            };\n            let workspace_frame = Frame::builder()\n                .label(title)\n                .label_xalign(0.5)\n                .child(&workspace_fixed)\n                .build();\n\n            let workspace_button = {\n                let workspace_overlay = Overlay::builder().child(&workspace_frame).build();\n                let button = Button::builder()\n                    .child(&workspace_overlay)\n                    .css_classes([\"workspace\", \"no-hover\"])\n                    .build();\n                button.set_cursor(Cursor::from_name(\"pointer\", None).as_ref());\n                if active.workspace == *wid {\n                    button.add_css_class(\"active\");\n                }\n                button\n            };\n            if workspace.name.starts_with(\"special:\") {\n                workspace_button.add_css_class(\"special\");\n            }\n            data.main_flow.insert(&workspace_button, -1);\n            data.workspaces.insert(*wid, workspace_button);\n\n            for (address, client) in clients {\n                if !client.enabled {\n                    continue;\n                }\n\n                let client_button = {\n                    let title = if client.title.trim().is_empty() {\n                        &client.class\n                    } else {\n                        &client.title\n                    };\n                    let client_label = Label::builder()\n                        .label(title)\n                        .overflow(Overflow::Visible)\n                        .margin_start(6)\n                        .ellipsize(pango::EllipsizeMode::End)\n                        .build();\n                    let client_frame = Frame::builder()\n                        .label_xalign(0.5)\n                        .label_widget(&client_label)\n                        .build();\n\n                    // hide picture if client so small\n                    let client_h_w = scale(client.height, data.config.scale)\n                        .min(scale(client.width, data.config.scale));\n                    if client_h_w > 70 {\n                        let image = Image::builder()\n                            .css_classes([\"client-image\"])\n                            .pixel_size((f64::from(client_h_w.clamp(50, 600)) / 1.6) as i32 - 20)\n                            .build();\n                        if !client.enabled {\n                            image.add_css_class(\"monochrome\");\n                        }\n                        set_icon(&client.class, client.pid, &image);\n                        client_frame.set_child(Some(&image));\n                    }\n\n                    let client_overlay = Overlay::builder()\n                        .overflow(Overflow::Hidden)\n                        .child(&client_frame)\n                        .build();\n                    let button = Button::builder()\n                        .child(&client_overlay)\n                        .css_classes([\"client\", \"no-hover\"])\n                        .width_request(scale(client.width, data.config.scale))\n                        .height_request(scale(client.height, data.config.scale))\n                        .build();\n                    button.set_cursor(Cursor::from_name(\"pointer\", None).as_ref());\n\n                    // add initial border around initial active client\n                    if active.client == Some(*address) {\n                        button.add_css_class(\"active\");\n                    }\n                    button\n                };\n                workspace_fixed.put(\n                    &client_button,\n                    f64::from(scale(client.x, data.config.scale)),\n                    f64::from(scale(client.y, data.config.scale)),\n                );\n                data.clients.insert(*address, client_button);\n            }\n        }\n    } else {\n        for (address, client) in &clients_data.clients {\n            if !client.enabled {\n                continue;\n            }\n            let client_button = {\n                let title = if client.title.trim().is_empty() {\n                    &client.class\n                } else {\n                    &client.title\n                };\n                let client_label = Label::builder()\n                    .label(title)\n                    .overflow(Overflow::Visible)\n                    .margin_start(6)\n                    .ellipsize(pango::EllipsizeMode::End)\n                    .build();\n                let client_frame = Frame::builder()\n                    .label_xalign(0.5)\n                    .label_widget(&client_label)\n                    .build();\n\n                // hide picture if client so small\n                let client_h_w = scale(i16::try_from(current_monitor.height)?, data.config.scale)\n                    .min(scale(\n                        (f32::from(current_monitor.height) / current_monitor.scale) as i16,\n                        data.config.scale,\n                    ));\n                if client_h_w > 70 {\n                    let image = Image::builder()\n                        .css_classes([\"client-image\"])\n                        .pixel_size((f64::from(client_h_w.clamp(50, 600)) / 1.5) as i32 - 20)\n                        .build();\n                    if !client.enabled {\n                        image.add_css_class(\"monochrome\");\n                    }\n                    set_icon(&client.class, client.pid, &image);\n                    client_frame.set_child(Some(&image));\n                }\n\n                let client_overlay = Overlay::builder()\n                    .overflow(Overflow::Hidden)\n                    .child(&client_frame)\n                    .build();\n                let button = Button::builder()\n                    .child(&client_overlay)\n                    .css_classes([\"client\", \"no-hover\"])\n                    .width_request(scale(\n                        (f32::from(current_monitor.width) / current_monitor.scale) as i16,\n                        data.config.scale,\n                    ))\n                    .height_request(scale(\n                        (f32::from(current_monitor.height) / current_monitor.scale) as i16,\n                        data.config.scale,\n                    ))\n                    .build();\n                button.set_cursor(Cursor::from_name(\"pointer\", None).as_ref());\n\n                // add border around initial active client\n                if active.client == Some(*address) {\n                    button.add_css_class(\"active\");\n                }\n                button\n            };\n            data.main_flow.insert(&client_button, -1);\n            data.clients.insert(*address, client_button);\n        }\n    }\n\n    data.active = active;\n    data.hypr_data = clients_data;\n    Ok(())\n}\n"
  },
  {
    "path": "crates/windows-lib/src/switch/stop.rs",
    "content": "use crate::global::WindowsSwitchData;\nuse core_lib::WarnWithDetails;\nuse exec_lib::reset_no_follow_mouse;\nuse relm4::adw::gtk::prelude::*;\nuse tracing::{debug_span, trace};\n\npub fn stop_switch(data: &WindowsSwitchData) {\n    let _span = debug_span!(\"stop_switch\").entered();\n    reset_no_follow_mouse().warn_details(\"Failed to reset follow mouse\");\n    trace!(\"Closing window {:?}\", data.window.id());\n    data.window.close();\n}\n"
  },
  {
    "path": "crates/windows-lib/src/switch/update.rs",
    "content": "use crate::global::WindowsSwitchData;\nuse crate::next::{find_next_client, find_next_workspace};\nuse core_lib::transfer::SwitchSwitchConfig;\nuse relm4::adw::prelude::WidgetExt;\nuse tracing::debug_span;\n\npub fn update_switch(data: &mut WindowsSwitchData, config: &SwitchSwitchConfig) {\n    let _span = debug_span!(\"update_switch\").entered();\n\n    let active = if data.config.switch_workspaces {\n        find_next_workspace(\n            &config.direction,\n            true,\n            &data.hypr_data,\n            data.active,\n            data.config.items_per_row,\n        )\n    } else {\n        find_next_client(\n            &config.direction,\n            true,\n            &data.hypr_data,\n            data.active,\n            data.config.items_per_row,\n        )\n    };\n    data.active = active;\n\n    if data.config.switch_workspaces {\n        for button in data.clients.values() {\n            button.remove_css_class(\"active\");\n        }\n        for (id, button) in &data.workspaces {\n            button.remove_css_class(\"active\");\n            if active.workspace == *id {\n                button.add_css_class(\"active\");\n            }\n        }\n    } else {\n        for button in data.workspaces.values() {\n            button.remove_css_class(\"active\");\n        }\n        for (id, button) in &data.clients {\n            button.remove_css_class(\"active\");\n            if active.client == Some(*id) {\n                button.add_css_class(\"active\");\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dep-crates/hyprland-rs/Cargo.toml",
    "content": "[package]\nname = \"hyprshell-hyprland\"\nedition = \"2021\"\nreadme = \"README.md\"\ndescription = \"A unoffical rust wrapper for hyprland's IPC\"\nhomepage = \"https://github.com/hyprland-community/hyprland-rs\"\nversion = \"4.9.5\"\nlicense = \"GPL-3.0-or-later\"\nrepository = \"https://github.com/hyprland-community/hyprland-rs\"\nkeywords = [\"hyprland\", \"ipc\", \"hypr\", \"wayland\", \"hyprpaper\"]\ncategories = [\"api-bindings\"]\nauthors = [\"yavko <yavornkolev@gmail.com>\"]\nrust-version = \"1.75.0\"\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[package.metadata.nix]\nbuild = true\n\n[dependencies]\nhyprland-macros = { package = \"hyprshell-hyprland-macros\", path = \"hyprland-macros\", version = \"=4.9.5\" }\nserde = { version = \"1\", features = [\"derive\"] }\nserde_json = \"1\"\nserde_repr = \"0.1\"\ntokio = { version = \"1\", features = [\n    \"io-std\",\n    \"io-util\",\n    \"macros\",\n    \"net\",\n    \"sync\",\n    \"rt\"\n], optional = true }\nasync-net = { version = \"2.0\", optional = true }\nfutures-lite = { version = \"2.3\", default-features = false }\npaste = \"1.0.14\"\nderive_more = { version = \"1.0.0\", features = [\n    \"display\",\n    \"constructor\",\n] }\nahash = { version = \"0.8.12\", features = [\n    \"std\",\n    \"no-rng\",\n    \"serde\",\n], optional = true, default-features = false }\neither = \"1.13.0\"\nasync-stream = \"0.3.6\"\n\n[features]\ndefault = [\n    \"listener\",\n    \"dispatch\",\n    \"data\",\n    \"keyword\",\n    \"config\",\n    \"ctl\",\n    \"tokio\",\n    \"hyprpaper\",\n]\nlistener = [\"data\", \"dispatch\"]\ndispatch = []\ndata = []\nkeyword = []\nconfig = [\"dispatch\", \"keyword\"]\nctl = []\nhyprpaper = []\n\nahash = [\"dep:ahash\"]\nparking_lot = [\"tokio?/parking_lot\"]\nunsafe-impl = []\n\ntokio = [\"dep:tokio\"]\nasync-lite = [\"dep:async-net\"]\n\n"
  },
  {
    "path": "dep-crates/hyprland-rs/LICENSE",
    "content": "Hyprland-rs the unofficial Hyprland IPC rust wrapper\nCopyright (C) 2023 Yavor Kolev (Yavko) and contributors\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program. If not, see <https://www.gnu.org/licenses/>.\n"
  },
  {
    "path": "dep-crates/hyprland-rs/MIGRATION.md",
    "content": "# Migration Guide\n\n## `0.2.5` ⟶ `0.3.0`\n> This release is action packed so be prepared!\n\nSteps (This order is recommended so lsp doesn't scream at you lol)\n1. Add the prelude! This new release includes traits! `use hyprland::prelude::*;`\n2. Switch to the `HResult` Result type (`hyprland::shared::HResult`)\n3. [Update your data fetcher functions to structs](#update-data-fetchers)\n4. [Update your dispatch functions to structs](#update-dispatchers)\n5. Switch all `String`s to `&str`s in your dispatchers\n6. Switch from `Positive`/`Negative` `Relative` dispatchers to just `Relative..`, and signed ints\n7. When using `WorkspaceType::Special`, now include a `Some(name: String)` or `None`\n8. [Update your keyword calls to new module](#update-keyword-calls)\n9. [Update start listener calls](#update-start-listener-calls)\n10. Follow Rustc and Clippy for everything else! \n\n## `0.3.*` ⟶ `0.3.3`\n* Changes some integer types\n* Switch to new event structs (no longer tuple!)\n\n## `0.3.0` ⟶ `0.3.1`\n* Change some types in the data\nstructs, because they changed (Only int types)\n* Fix some dispatchers (ex. `ToggleSpecialWorkspace`)\n* Update active window event, cuz I merged it with activewindowv2\n* Update location of `Result` type, cuz I changed it\n\n## `0.3.3` ⟶ `0.3.4`\n* EventListener now requires mutable reference to start\n\n## More in-depth steps\n\n### 0.3 Update\n\n#### Update Data fetchers\n| Notation Type | Async Notation                     | Blocking Notation           |\n|---------------|------------------------------------|-----------------------------|\n| Old notation  | `asynchronous::get_something()`    | `blocking::get_something()` |\n| New Notation  | `Something::get_async().collect()` | `Something::get().collect()`|\n> The `.collect()` is required because data fetchers are now iterators!\n>\n> If you are just converting to a `Vec`, **USE THE `.to_vec()`** method instead,\n> this being an iterator is useful if you wanna for example loop over all the workspaces\n\n\n#### Update Dispatchers\n\n| Notation Type | Async Notation           | Blocking Notation     |\n|---------------|--------------------------|-----------------------|\n| Old notation  | `dispatch()`             | `dispatch_blocking()` |\n| New Notation  | `Dispatch::call_async()` | `Dispatch::call()`    |\n\n#### Update start listener calls\n| Notation Type | Async Notation           | Blocking Notation           |\n|---------------|--------------------------|-----------------------------|\n| Old notation  | `start_listener()`       | `start_listener_blocking()` |\n| New Notation  | `start_listener_async()` | `start_listener()`          |\n\n#### Update Keyword calls\nThis has gone through a complete rework, and now is in the `keyword` module\n\n##### Setting a keyword\nBlocking: `Keyword::set(key, val)`<br />\nAsynchronous: `Keyword::set_async(key, val)`\n\n##### Getting a keyword \n> AKA getoption\n\nBlocking: `Keyword::get(key)`<br />\nAsynchronous: `Keyword::get_async(key)`\n"
  },
  {
    "path": "dep-crates/hyprland-rs/README.md",
    "content": "# Hyprland-rs\n\n[![Crates.io](https://img.shields.io/crates/v/hyprland)](https://crates.io/crates/hyprland)\n![Crates.io](https://img.shields.io/crates/d/hyprland)\n[![Crates.io](https://img.shields.io/crates/l/hyprland)](https://www.gnu.org/licenses/gpl-3.0.html)\n[![docs.rs](https://img.shields.io/docsrs/hyprland)](https://docs.rs/hyprland)\n[![Hyprland](https://img.shields.io/badge/Made%20for-Hyprland-blue)](https://github.com/hyprwm/Hyprland)\n[![Discord](https://img.shields.io/discord/1055990214411169892?label=discord)](https://discord.gg/zzWqvcKRMy)\n\nAn unofficial rust wrapper for Hyprland's IPC\n\n## Help Wanted!\nWe need help with developing the next version of hyprland-rs `0.4`,\nif you know how to do the things in <https://github.com/hyprland-community/hyprland-rs/milestone/4>\ncontributions in those areas would be greatly appreciated!\n\n## Disclaimer\n\nIf something doesn't work, doesn't matter what,\nmake sure you are on the latest version (or commit) of Hyprland before making an issue!\n\n## Getting started!\n\nLet's get started with Hyprland-rs!\n\n### Adding to your project\n\nAdd the code below to the dependencies section of your Cargo.toml file!\n\n```toml\nhyprland = \"0.4.0-beta.1\"\n```\n\n### Reading the docs\n\nHyprland-rs has a ton of types (and some really long ones)! Its important you know how the ones you will be using work!\nThe docs can be found at [docs.rs/hyprland](https://docs.rs/hyprland)\n\n#### Master version\n\nIf Hyprland-rs is broken (or other reason) and is taking too long for a release to come out,\nyou can use the master branch in Cargo (will not allow the crate to be published to `crates.io`):\n\n```toml\nhyprland = { git = \"https://github.com/hyprland-community/hyprland-rs\", branch = \"master\" }\n```\n\n### What this crate provides\n\nThis crate provides 6 modules (+1 for shared things)\n\n- `data` for getting information on the compositor\n- `event_listener` which provides the `EventListener` struct for listening for events\n- `dispatch` for calling dispatchers\n- `keyword` for dealing with config option (aka keywords)\n- `config::binds` for changing binds (in future `config` might have config generation)\n- `ctl` for calling hyprctl commands\n\n## Example Usage\n\nCheck the examples in the [`examples` directory](https://github.com/hyprland-community/hyprland-rs/tree/master/examples)\n"
  },
  {
    "path": "dep-crates/hyprland-rs/examples/bind.rs",
    "content": "use hyprshell_hyprland as hyprland;\n\nuse hyprland::dispatch::DispatchType;\nuse hyprland::instance::Instance;\nuse hyprland::keyword::Keyword;\nuse hyprland::{default_instance_panic, dispatch};\n/// Demonstates the use of Hyprland-rs for creating key bindings\n/// and using submaps\n///\n/// Usage: cargo run --example bind\nuse std::io::Read;\n\nfn main() -> hyprland::Result<()> {\n    let instance = default_instance_panic();\n    Keyword::instance_set(instance, \"submap\", \"example\")?;\n    hyprland::bind!(instance, SUPER, Key, \"I\" => ToggleFloating, None)?;\n    hyprland::bind!(instance, l | CTRL ALT, Key, \"Delete\" => Exec, \"sudo reboot\")?; // Reboot including from lock screen\n    hyprland::bind!(instance, e | SUPER, Key, \"C\" => KillActiveWindow)?; // Kill all your windows\n    Keyword::instance_set(instance, \"submap\", \"reset\")?;\n\n    let instance = Instance::from_instance(\"long instance name\".to_string())?;\n    Keyword::instance_set(&instance, \"submap\", \"example\")?;\n    hyprland::bind!(&instance, SUPER, Key, \"I\" => ToggleFloating, None)?;\n    hyprland::bind!(&instance, l | CTRL ALT, Key, \"Delete\" => Exec, \"sudo reboot\")?; // Reboot including from lock screen\n    hyprland::bind!(&instance, e | SUPER, Key, \"C\" => KillActiveWindow)?; // Kill all your windows\n    Keyword::instance_set(&instance, \"submap\", \"reset\")?;\n\n    Keyword::set(\"submap\", \"example\")?;\n    hyprland::bind!(SUPER, Key, \"I\" => ToggleFloating, None)?;\n    hyprland::bind!(l | CTRL ALT, Key, \"Delete\" => Exec, \"sudo reboot\")?; // Reboot including from lock screen\n    hyprland::bind!(e | SUPER, Key, \"C\" => KillActiveWindow)?; // Kill all your windows\n    Keyword::set(\"submap\", \"reset\")?;\n\n    let instance = Instance::from_current_env()?;\n    dispatch!(&instance, Custom, \"submap\", \"example\")?;\n    println!(\"Press enter to revert to default keymap\");\n    let _ = std::io::stdin()\n        .read(&mut [0u8])\n        .expect(\"Crashed: Run `hyprctl dispatch submap reset` to return to default submap\");\n    dispatch!(&instance, Custom, \"submap\", \"reset\")?;\n\n    Ok(())\n}\n"
  },
  {
    "path": "dep-crates/hyprland-rs/examples/bind_async.rs",
    "content": "use hyprshell_hyprland as hyprland;\n\nuse hyprland::dispatch::DispatchType;\nuse hyprland::instance::Instance;\nuse hyprland::keyword::Keyword;\nuse hyprland::{default_instance_panic, dispatch};\n/// Demonstates the use of Hyprland-rs for asyncronous creation key bindings\n/// and using submaps\n///\n/// Usage: cargo run --example bind\nuse std::io::Read;\n\n#[tokio::main(flavor = \"current_thread\")]\nasync fn main() -> hyprland::Result<()> {\n    let instance1 = default_instance_panic();\n    Keyword::instance_set_async(instance1, \"submap\", \"example\").await?;\n    hyprland::bind!(async; instance1, SUPER, Key, \"I\" => ToggleFloating, None).await?;\n    hyprland::bind!(async; instance1, l | CTRL ALT, Key, \"Delete\" => Exec, \"sudo reboot\").await?; // Reboot including from lock screen\n    hyprland::bind!(async; instance1, e | SUPER, Key, \"C\" => KillActiveWindow).await?; // Kill all your windows\n    Keyword::instance_set_async(instance1, \"submap\", \"reset\").await?;\n\n    let instance2 = Instance::from_instance(\"long instance name\".to_string())?;\n    Keyword::instance_set_async(&instance2, \"submap\", \"example2\").await?;\n    hyprland::bind!(async; &instance2, SUPER, Key, \"I\" => ToggleFloating, None).await?;\n    hyprland::bind!(async; &instance2, l | CTRL ALT, Key, \"Delete\" => Exec, \"sudo reboot\").await?; // Reboot including from lock screen\n    hyprland::bind!(async; &instance2, e | SUPER, Key, \"C\" => KillActiveWindow).await?; // Kill all your windows\n    Keyword::instance_set_async(&instance2, \"submap\", \"reset2\").await?;\n\n    Keyword::set_async(\"submap\", \"example\").await?;\n    hyprland::bind!(async; SUPER, Key, \"I\" => ToggleFloating, None).await?;\n    hyprland::bind!(async; l | CTRL ALT, Key, \"Delete\" => Exec, \"sudo reboot\").await?; // Reboot including from lock screen\n    hyprland::bind!(async; e | SUPER, Key, \"C\" => KillActiveWindow).await?; // Kill all your windows\n    Keyword::set_async(\"submap\", \"reset\").await?;\n\n    let instance3 = Instance::from_current_env()?;\n    dispatch!(async; &instance3, Custom, \"submap\", \"example\").await?;\n    println!(\"Press enter to revert to default keymap\");\n    let _ = std::io::stdin().read(&mut [0u8])?;\n    dispatch!(async; &instance3, Custom, \"submap\", \"reset\").await?;\n    Ok(())\n}\n"
  },
  {
    "path": "dep-crates/hyprland-rs/examples/data.rs",
    "content": "use hyprshell_hyprland as hyprland;\n\n/// Demostrates using hyprland-rs to fetch information about clients, workspaces and monitors\n///\n/// Usage: cargo run --example data <animations|binds|client(s)|workspace(s)|monitor(s)>\n/// Example: cargo run --example data client        (Gets data on active client)\n/// Example: cargo run --example data workspaces    (Gets data on all workspaces)\nuse hyprland::data::{\n    Animations, Binds, Client, Clients, Monitor, Monitors, Workspace, Workspaces,\n};\nuse hyprland::shared::{HyprData, HyprDataActive, HyprDataActiveOptional};\n\nfn main() -> hyprland::Result<()> {\n    let args: Vec<_> = std::env::args().skip(1).collect();\n    if args.is_empty() {\n        panic!(\"You have to specify client, workspace or monitor\")\n    }\n\n    match args[0].as_str() {\n        \"client\" => println!(\"{:#?}\", Client::get_active()?),\n        \"monitor\" => println!(\"{:#?}\", Monitor::get_active()?),\n        \"workspace\" => println!(\"{:#?}\", Workspace::get_active()?),\n        \"animations\" => println!(\"{:#?}\", Animations::get()?),\n        \"binds\" => println!(\"{:#?}\", Binds::get()?),\n        \"clients\" => println!(\"{:#?}\", Clients::get()?),\n        \"monitors\" => println!(\"{:#?}\", Monitors::get()?),\n        \"workspaces\" => println!(\"{:#?}\", Workspaces::get()?),\n        _ => println!(\"Specify one of client(s), monitor(s) or workspace(s)\"),\n    };\n\n    Ok(())\n}\n"
  },
  {
    "path": "dep-crates/hyprland-rs/examples/data_async.rs",
    "content": "use hyprshell_hyprland as hyprland;\n\n/// Demostrates asynchronously using Hyprland-rs to fetch information about your Hyprland environment\n///\n/// Usage: cargo run --example data_async <animations|binds|client(s)|workspace(s)|monitor(s)>\n/// Example: cargo run --example data_async client        (Gets data on active client)\n/// Example: cargo run --example data_async workspaces    (Gets data on all workspaces)\nuse hyprland::data::{\n    Animations, Binds, Client, Clients, Monitor, Monitors, Workspace, Workspaces,\n};\nuse hyprland::shared::{HyprData, HyprDataActive, HyprDataActiveOptional};\n\n#[tokio::main(flavor = \"current_thread\")]\nasync fn main() -> hyprland::Result<()> {\n    let args: Vec<_> = std::env::args().skip(1).collect();\n\n    if args.is_empty() {\n        panic!(\"You have to specify client, workspace or monitor\")\n    }\n\n    match args[0].as_str() {\n        \"client\" => println!(\"{:#?}\", Client::get_active_async().await?),\n        \"monitor\" => println!(\"{:#?}\", Monitor::get_active_async().await?),\n        \"workspace\" => println!(\"{:#?}\", Workspace::get_active_async().await?),\n        \"animations\" => println!(\"{:#?}\", Animations::get_async().await?),\n        \"binds\" => println!(\"{:#?}\", Binds::get_async().await?),\n        \"clients\" => println!(\"{:#?}\", Clients::get_async().await?),\n        \"monitors\" => println!(\"{:#?}\", Monitors::get_async().await?),\n        \"workspaces\" => println!(\"{:#?}\", Workspaces::get_async().await?),\n        _ => println!(\"Specify one of client(s), monitor(s) or workspace(s)\"),\n    };\n\n    Ok(())\n}\n"
  },
  {
    "path": "dep-crates/hyprland-rs/examples/dispatch.rs",
    "content": "use hyprshell_hyprland as hyprland;\n\n/// Demonstrates usage of various dispatch calls\n///\n/// Usage: cargo run --example dispatch <hyprland args>? <program_name>? <program_args>?\n/// Example: cargo run --example dispatch [workspace 2] kitty\nuse hyprland::dispatch;\nuse hyprland::dispatch::DispatchType::*;\nuse hyprland::dispatch::{Corner, Dispatch, FullscreenType, WorkspaceIdentifierWithSpecial};\n\nfn describe(desc: &str) {\n    std::thread::sleep(std::time::Duration::from_secs(2));\n    println!(\"{desc}\");\n}\n\nfn main() -> hyprland::Result<()> {\n    let program = std::env::args().skip(1).collect::<Vec<_>>().join(\" \");\n\n    dispatch!(Exec, &program)?;\n\n    describe(\"Moving cursor to top left\");\n    dispatch!(MoveCursorToCorner, Corner::TopLeft)?;\n\n    describe(\"Moving cursor to top right\");\n    dispatch!(MoveCursorToCorner, Corner::TopRight)?;\n\n    describe(\"Moving cursor to bottom right\");\n    dispatch!(MoveCursorToCorner, Corner::BottomRight)?;\n\n    describe(\"Moving cursor to bottom left\");\n    dispatch!(MoveCursorToCorner, Corner::BottomLeft)?;\n\n    describe(\"Moving window to next workspace\");\n    dispatch!(\n        MoveToWorkspace,\n        WorkspaceIdentifierWithSpecial::Relative(1),\n        None\n    )?;\n\n    describe(\"Moving window to previous workspace\");\n    dispatch!(\n        MoveToWorkspace,\n        WorkspaceIdentifierWithSpecial::Relative(-1),\n        None\n    )?;\n\n    describe(\"Toggling fullscreen\");\n    dispatch!(ToggleFullscreen, FullscreenType::Maximize)?;\n    describe(\"Reverting fullscreen\");\n    Dispatch::call(ToggleFullscreen(FullscreenType::Maximize))?;\n\n    describe(\"Toggling floating window\");\n    dispatch!(ToggleFloating, None)?;\n    describe(\"Reverting floating window\");\n    Dispatch::call(ToggleFloating(None))?;\n\n    describe(\"Toggling split layout\");\n    Dispatch::call(ToggleSplit)?;\n    describe(\"Reverting split layout\");\n    Dispatch::call(ToggleSplit)?;\n\n    describe(\"Toggling opaque\");\n    Dispatch::call(ToggleOpaque)?;\n    describe(\"Reverting opaque\");\n    Dispatch::call(ToggleOpaque)?;\n\n    describe(\"Closing window\");\n    Dispatch::call(KillActiveWindow)?;\n\n    Ok(())\n}\n"
  },
  {
    "path": "dep-crates/hyprland-rs/examples/dispatch_async.rs",
    "content": "use hyprshell_hyprland as hyprland;\n\n/// Demonstrates usage of various asyncronous dispatch calls\n///\n/// Usage: cargo run --example dispatch_async\nuse hyprland::dispatch;\nuse hyprland::dispatch::DispatchType::*;\nuse hyprland::dispatch::{Corner, Dispatch, FullscreenType, WorkspaceIdentifierWithSpecial};\n\nfn describe(desc: &str) {\n    std::thread::sleep(std::time::Duration::from_secs(2));\n    println!(\"{desc}\");\n}\n\n#[tokio::main(flavor = \"current_thread\")]\nasync fn main() -> hyprland::Result<()> {\n    let program = std::env::args().skip(1).collect::<Vec<_>>().join(\" \");\n\n    println!(\"Executing {program}\");\n    dispatch!(async; Exec, &program).await?;\n\n    describe(\"Moving cursor to top left\");\n    dispatch!(async; MoveCursorToCorner, Corner::TopLeft).await?;\n\n    describe(\"Moving cursor to top right\");\n    dispatch!(async; MoveCursorToCorner, Corner::TopRight).await?;\n\n    describe(\"Moving cursor to bottom right\");\n    dispatch!(async; MoveCursorToCorner, Corner::BottomRight).await?;\n\n    describe(\"Moving cursor to bottom left\");\n    dispatch!(async; MoveCursorToCorner, Corner::BottomLeft).await?;\n\n    describe(\"Moving window to next workspace\");\n    dispatch!(async; MoveToWorkspace, WorkspaceIdentifierWithSpecial::Relative(1), None).await?;\n\n    describe(\"Moving window to previous workspace\");\n    dispatch!(async; MoveToWorkspace, WorkspaceIdentifierWithSpecial::Relative(-1), None).await?;\n\n    describe(\"Toggling fullscreen\");\n    dispatch!(async; ToggleFullscreen, FullscreenType::Maximize).await?;\n    describe(\"Reverting fullscreen\");\n    dispatch!(async; ToggleFullscreen, FullscreenType::Maximize).await?;\n\n    describe(\"Toggling floating window\");\n    dispatch!(async; ToggleFloating, None).await?;\n    describe(\"Reverting floating window\");\n    Dispatch::call_async(ToggleFloating(None)).await?;\n\n    describe(\"Toggling split layout\");\n    Dispatch::call_async(ToggleSplit).await?;\n    describe(\"Reverting split layout\");\n    Dispatch::call_async(ToggleSplit).await?;\n\n    describe(\"Toggling opaque\");\n    Dispatch::call_async(ToggleOpaque).await?;\n    describe(\"Reverting opaque\");\n    Dispatch::call_async(ToggleOpaque).await?;\n\n    describe(\"Closing window\");\n    Dispatch::call_async(KillActiveWindow).await?;\n\n    Ok(())\n}\n"
  },
  {
    "path": "dep-crates/hyprland-rs/examples/events.rs",
    "content": "use hyprshell_hyprland as hyprland;\n\n/// Demostrats using hyprland-rs to listen for events\n/// Usage: cargo run --example events\nuse hyprland::event_listener::EventListener;\n\nfn main() -> hyprland::Result<()> {\n    // Create a event listener\n    let mut event_listener = EventListener::new();\n\n    event_listener.add_active_window_changed_handler(|data| println!(\"{data:#?}\"));\n    event_listener.add_fullscreen_state_changed_handler(|fstate| {\n        println!(\"Window {} fullscreen\", if fstate { \"is\" } else { \"is not\" })\n    });\n    event_listener\n        .add_active_monitor_changed_handler(|state| println!(\"Monitor state: {state:#?}\"));\n    event_listener.add_workspace_changed_handler(|id| println!(\"workspace changed to {id:?}\"));\n\n    // and execute the function\n    // here we are using the blocking variant\n    // but there is a async version too\n    event_listener.start_listener()?;\n    Ok(())\n}\n"
  },
  {
    "path": "dep-crates/hyprland-rs/examples/events_async.rs",
    "content": "use hyprshell_hyprland as hyprland;\n\nuse hyprland::event_listener::AsyncEventListener;\n/// Demostrats using hyprland-rs to asynchronously listen for events\n///\n/// Usage: cargo run --example events\nuse hyprland::prelude::*;\n\n#[tokio::main(flavor = \"current_thread\")]\nasync fn main() -> hyprland::Result<()> {\n    // Create a event listener\n    let mut event_listener = AsyncEventListener::new();\n\n    event_listener.add_workspace_changed_handler(async_closure! {\n        |data| println!(\"{data:#?}\")\n    });\n\n    event_listener.add_fullscreen_state_changed_handler(async_closure! {\n        |fstate| println!(\"Window {} fullscreen\", if fstate { \"is\" } else { \"is not\" })\n    });\n\n    event_listener.add_active_monitor_changed_handler(async_closure! {\n        |state| println!(\"Monitor state: {state:#?}\")\n    });\n\n    // add event, yes functions and closures both work!\n    event_listener.add_workspace_changed_handler(async_closure! {\n        |id| println!(\"workspace changed to {id:?}\")\n    });\n\n    // and execute the function\n    // here we are using the blocking variant\n    // but there is a async version too\n    event_listener.start_listener_async().await?;\n    Ok(())\n}\n"
  },
  {
    "path": "dep-crates/hyprland-rs/examples/keyword.rs",
    "content": "use hyprshell_hyprland as hyprland;\n\n/// Demostrates how to fetch and set keywords\n///\n/// Usage: cargo run --example keyword <keyword> <value>?\n/// Example: cargo run --example keyword decoration:rounding (prints value)\n/// Example: cargo run --example keyword decoration:rounding  15 (sets value)\nuse hyprland::keyword::Keyword;\n\nfn main() -> hyprland::Result<()> {\n    let args: Vec<_> = std::env::args().skip(1).collect();\n    let keyword = args[0].clone();\n\n    match args.len() {\n        0 => panic!(\"You need to pass a keyword\"),\n        1 => println!(\"{} value is {}\", keyword, Keyword::get(&keyword)?.value),\n        2 => Keyword::set(keyword, args[1].clone())?,\n        _ => panic!(\"Takes up to 2 arguments only!\"),\n    }\n    Ok(())\n}\n"
  },
  {
    "path": "dep-crates/hyprland-rs/examples/keyword_async.rs",
    "content": "use hyprshell_hyprland as hyprland;\n\n/// Demostrates how to fetch and set keywords asyncronously\n///\n/// Usage: cargo run --example keyword_async <keyword> <value>\n/// Example: cargo run --example keyword_async decoration:rounding (prints value)\n/// Example: cargo run --example keyword_async decoration:rounding  15 (sets value)\nuse hyprland::keyword::Keyword;\n\n#[tokio::main(flavor = \"current_thread\")]\nasync fn main() -> hyprland::Result<()> {\n    let args: Vec<_> = std::env::args().skip(1).collect();\n    let keyword = args[0].clone();\n\n    match args.len() {\n        0 => panic!(\"You need to pass a keyword\"),\n        1 => println!(\n            \"{} value is {}\",\n            keyword,\n            Keyword::get_async(&keyword).await?.value\n        ),\n        2 => Keyword::set_async(keyword, args[1].clone()).await?,\n        _ => panic!(\"Takes up to 2 arguments only!\"),\n    }\n    Ok(())\n}\n"
  },
  {
    "path": "dep-crates/hyprland-rs/flake.nix",
    "content": "{\n  description = \"Hyprland-rs devshell\";\n  inputs = {\n    nixpkgs.url = \"github:nixos/nixpkgs/nixos-unstable\";\n    dream2nix = {\n      url = \"github:71/dream2nix/rust-ws-inherit-version\";\n    };\n    nci = {\n      url = \"github:yusdacra/nix-cargo-integration\";\n      inputs = {\n        dream2nix.follows = \"dream2nix\";\n      };\n    };\n    flake-parts = {\n      inputs = {\n        nixpkgs-lib.follows = \"nixpkgs\";\n      };\n    };\n  };\n  outputs =\n    inputs@{\n      flake-parts,\n      nci,\n      ...\n    }:\n    flake-parts.lib.mkFlake { inherit inputs; } {\n      imports = [\n        nci.flakeModule\n      ];\n      systems = [\n        \"x86_64-linux\"\n        \"aarch64-linux\"\n      ];\n      perSystem =\n        {\n          pkgs,\n          config,\n          ...\n        }:\n        let\n          crateName = \"hyprland\";\n          crateOutputs = config.nci.outputs.${crateName};\n        in\n        {\n          nci.projects.${crateName}.relPath = \"\";\n          nci.crates.${crateName} = {\n            export = true;\n          };\n          devShells.default = crateOutputs.devShell;\n          packages.default = crateOutputs.packages.release;\n        };\n    };\n}\n"
  },
  {
    "path": "dep-crates/hyprland-rs/hyprland-macros/Cargo.toml",
    "content": "[package]\nname = \"hyprshell-hyprland-macros\"\ndescription = \"Macros used in hyprland-rs\"\nversion = \"4.9.5\"\nlicense = \"GPL-3.0-or-later\"\nreadme = \"README.md\"\nedition = \"2021\"\nhomepage = \"https://github.com/hyprland-community/hyprland-rs/tree/master/hyprland-macros\"\nrepository = \"https://github.com/hyprland-community/hyprland-rs\"\n\n[package.metadata.nix]\nbuild = true\n\n[lib]\nproc-macro = true\n\n[dependencies]\nsyn = { version = \"2\", features = [\"full\", \"parsing\"] }\nquote = \"1\"\nproc-macro2 = \"1\"\n"
  },
  {
    "path": "dep-crates/hyprland-rs/hyprland-macros/README.md",
    "content": "# Hyprland Macros\nLiterally only async closure macro lol\n"
  },
  {
    "path": "dep-crates/hyprland-rs/hyprland-macros/src/lib.rs",
    "content": "#![doc = include_str!(\"../README.md\")]\n#![deny(missing_docs)]\n\nuse proc_macro::TokenStream;\nuse proc_macro2::TokenStream as TokenStream2;\nuse quote::{quote, ToTokens};\nuse syn::{\n    parse::{Parse, ParseStream},\n    parse_macro_input, Block, ExprClosure, Result, Token, Type,\n};\n\n/// Creates a async closure\n#[proc_macro]\npub fn async_closure(input: TokenStream) -> TokenStream {\n    let input = parse_macro_input!(input as ExprClosure);\n    let body = input.body;\n    let inputs = input.inputs;\n    let mova = input.capture;\n    let expanded = quote! {{\n        use std::future::IntoFuture;\n        #mova |#inputs| Box::pin(async move { #body })\n    }};\n    expanded.into()\n}\n\nstruct If<T: Parse> {\n    type_to_match: Type,\n    input_type: Type,\n    true_branch: T,\n    false_branch: T,\n}\n\nimpl<T: Parse> Parse for If<T> {\n    fn parse(input: ParseStream) -> Result<Self> {\n        let type_to_match: Type = input.parse()?;\n        input.parse::<Token![,]>()?;\n        let input_type: Type = input.parse()?;\n        input.parse::<Token![,]>()?;\n        let true_branch: T = input.parse()?;\n        input.parse::<Token![,]>()?;\n        let false_branch: T = input.parse()?;\n        Ok(If {\n            type_to_match,\n            input_type,\n            true_branch,\n            false_branch,\n        })\n    }\n}\n\n/// Creates a compile time if statement\n/// that takes checks if 2 types are the same\n/// and if returns one of the branches\n#[proc_macro]\npub fn block_if(input: TokenStream) -> TokenStream {\n    let If {\n        type_to_match,\n        input_type,\n        true_branch,\n        false_branch,\n    } = parse_macro_input!(input as If<Block>);\n    let used_branch = if type_to_match.to_token_stream().to_string()\n        == input_type.to_token_stream().to_string()\n    {\n        true_branch\n    } else {\n        false_branch\n    }\n    .stmts;\n    let mut strm = TokenStream2::new();\n    for stmt in used_branch {\n        stmt.to_tokens(&mut strm)\n    }\n    strm.into()\n}\n\n/// Creates a compile time if statement\n/// that takes checks if 2 types are the same\n/// and if returns one of the branches\n#[proc_macro]\npub fn type_if(input: TokenStream) -> TokenStream {\n    let If {\n        type_to_match,\n        input_type,\n        true_branch,\n        false_branch,\n    } = parse_macro_input!(input as If<Type>);\n    let used_branch = if type_to_match.to_token_stream().to_string()\n        == input_type.to_token_stream().to_string()\n    {\n        true_branch\n    } else {\n        false_branch\n    };\n    used_branch.into_token_stream().into()\n}\n\n/// Creates a compile time if statement\n/// that takes checks if 2 types are the same\n/// and if returns one of the branches\n#[proc_macro]\npub fn expr_if(input: TokenStream) -> TokenStream {\n    let If {\n        type_to_match,\n        input_type,\n        true_branch,\n        false_branch,\n    } = parse_macro_input!(input as If<syn::Expr>);\n    let used_branch = if type_to_match.to_token_stream().to_string()\n        == input_type.to_token_stream().to_string()\n    {\n        true_branch\n    } else {\n        false_branch\n    };\n    used_branch.into_token_stream().into()\n}\n"
  },
  {
    "path": "dep-crates/hyprland-rs/src/config.rs",
    "content": "//! # Hyprland Configuration in Rust\n//!\n\nuse crate::dispatch::{gen_dispatch_str, DispatchType};\nuse crate::keyword::Keyword;\n\n/// Module providing stuff for adding an removing keybinds\npub mod binds {\n    use super::*;\n    use crate::default_instance;\n    use crate::instance::Instance;\n\n    trait Join: IntoIterator {\n        fn join(&self) -> String;\n    }\n\n    /// Type for a key held by a bind\n    #[derive(Debug, Clone, PartialEq, Eq)]\n    pub enum Key<'a> {\n        /// Variant for if the bind holds a modded key\n        Mod(\n            /// Mods\n            Vec<Mod>,\n            /// Key\n            &'a str,\n        ),\n        /// Variant for a regular key\n        Key(&'a str),\n    }\n\n    impl std::fmt::Display for Key<'_> {\n        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n            write!(\n                f,\n                \"{}\",\n                match self {\n                    Key::Mod(m, s) => format!(\"{}_{s}\", m.join()),\n                    Key::Key(s) => s.to_string(),\n                }\n            )\n        }\n    }\n\n    #[derive(Debug, Clone, Copy, PartialEq, Eq, derive_more::Display)]\n    #[allow(missing_docs)]\n    /// Enum for mod keys used in bind combinations\n    pub enum Mod {\n        #[display(\"SUPER\")]\n        SUPER,\n        #[display(\"SHIFT\")]\n        SHIFT,\n        #[display(\"ALT\")]\n        ALT,\n        #[display(\"CTRL\")]\n        CTRL,\n        #[display(\"\")]\n        NONE,\n    }\n\n    impl Join for Vec<Mod> {\n        fn join(&self) -> String {\n            let mut buf = String::new();\n            for i in self {\n                buf.push_str(&i.to_string());\n            }\n            buf\n        }\n    }\n\n    #[derive(Debug, Clone, Copy, PartialEq, Eq, derive_more::Display)]\n    #[allow(non_camel_case_types)]\n    /// Enum for bind flags\n    pub enum Flag {\n        /// Works when screen is locked\n        #[display(\"l\")]\n        l,\n        /// Activates on release\n        #[display(\"r\")]\n        r,\n        /// Repeats when held\n        #[display(\"e\")]\n        e,\n        /// Non-consuming, key/mouse events will be passed to the active window in addition to triggering the dispatcher.\n        #[display(\"n\")]\n        n,\n        /// Used for mouse binds\n        #[display(\"m\")]\n        m,\n        /// Transparent, cannot be shadowed by other binds.\n        #[display(\"t\")]\n        t,\n        /// Ignore mods, will ignore modifiers.\n        #[display(\"i\")]\n        i,\n        /// Separate, will arbitrarily combine keys between each mod/key\n        #[display(\"s\")]\n        s,\n        /// Has description, will allow you to write a description for your bind.\n        #[display(\"d\")]\n        d,\n        /// Bypasses the app's requests to inhibit keybinds.\n        #[display(\"p\")]\n        p,\n    }\n\n    impl Join for Vec<Flag> {\n        fn join(&self) -> String {\n            let mut buf = String::new();\n            for f in self {\n                buf.push_str(&f.to_string());\n            }\n            buf\n        }\n    }\n\n    /// A struct providing a key bind\n    #[derive(Debug, Clone)]\n    pub struct Binding<'a> {\n        /// All the mods\n        pub mods: Vec<Mod>,\n        /// The key\n        pub key: Key<'a>,\n        /// Bind flags\n        pub flags: Vec<Flag>,\n        /// The dispatcher to be called once complete\n        pub dispatcher: DispatchType<'a>,\n    }\n\n    /// Struct to hold methods for adding and removing binds\n    pub struct Binder;\n\n    impl Binder {\n        pub(crate) fn gen_str(binding: Binding) -> crate::Result<String> {\n            Ok(format!(\n                \"{mods},{key},{dispatcher}\",\n                mods = binding.mods.join(),\n                key = binding.key,\n                dispatcher = gen_dispatch_str(binding.dispatcher, false)?.data\n            ))\n        }\n\n        /// Binds a keybinding\n        pub fn bind(binding: Binding) -> crate::Result<()> {\n            Self::instance_bind(default_instance()?, binding)\n        }\n\n        /// Binds a keybinding\n        pub fn instance_bind(instance: &Instance, binding: Binding) -> crate::Result<()> {\n            Keyword::instance_set(\n                instance,\n                format!(\"bind{}\", binding.flags.join()),\n                Self::gen_str(binding)?,\n            )?;\n            Ok(())\n        }\n\n        /// Binds a keybinding (async)\n        #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n        pub async fn bind_async(binding: Binding<'_>) -> crate::Result<()> {\n            Self::instance_bind_async(default_instance()?, binding).await\n        }\n\n        /// Binds a keybinding (async)\n        #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n        pub async fn instance_bind_async(\n            instance: &Instance,\n            binding: Binding<'_>,\n        ) -> crate::Result<()> {\n            Keyword::instance_set_async(\n                instance,\n                format!(\"bind{}\", binding.flags.join()),\n                Self::gen_str(binding)?,\n            )\n            .await?;\n            Ok(())\n        }\n    }\n\n    /// Very macro basic abstraction over [Binder] for internal use, **Dont use this instead use [crate::bind]**\n    ///\n    /// ```rust\n    /// # use hyprland::{bind_raw, default_instance, default_instance_panic, dispatch::DispatchType};\n    /// # async fn test() {\n    ///   let instance = default_instance()?;\n    ///   bind_raw!(instance , vec! [ Mod :: SHIFT ] , Key :: Key ( \"m\"  ) ,  vec ! [ Flag :: l , Flag :: r , Flag :: m ] ,  DispatchType :: Exit )?;\n    ///   bind_raw!(vec! [ Mod :: SHIFT ] , Key :: Key ( \"m\"  ) ,  vec ! [ Flag :: l , Flag :: r , Flag :: m ] ,  DispatchType :: Exit )?;\n    ///   bind_raw!(async, instance, vec ! [ Mod :: SHIFT ] , Key :: Key ( \"m\"  ) ,  vec ! [ Flag :: l , Flag :: r , Flag :: m ] ,  DispatchType :: Exit ).await?;\n    ///   bind_raw!(async, vec ! [ Mod :: SHIFT ] , Key :: Key ( \"m\"  ) ,  vec ! [ Flag :: l , Flag :: r , Flag :: m ] ,  DispatchType :: Exit ).await?;\n    /// # }\n    /// ```\n    #[macro_export]\n    macro_rules! bind_raw {\n        (async, $instance:expr,$mods:expr,$key:expr,$flags:expr,$dis:expr ) => {{\n            use $crate::config::binds::*;\n            let binding = Binding {\n                mods: $mods,\n                key: $key,\n                flags: $flags,\n                dispatcher: $dis,\n            };\n            Binder::instance_bind_async($instance, binding)\n        }};\n        (async, $mods:expr,$key:expr,$flags:expr,$dis:expr ) => {{\n            use $crate::config::binds::*;\n            let binding = Binding {\n                mods: $mods,\n                key: $key,\n                flags: $flags,\n                dispatcher: $dis,\n            };\n            Binder::bind_async(binding)\n        }};\n        ($instance:expr,$mods:expr,$key:expr,$flags:expr,$dis:expr ) => {{\n            use $crate::config::binds::*;\n            let binding = Binding {\n                mods: $mods,\n                key: $key,\n                flags: $flags,\n                dispatcher: $dis,\n            };\n            Binder::instance_bind($instance, binding)\n        }};\n        ($mods:expr,$key:expr,$flags:expr,$dis:expr ) => {{\n            use $crate::config::binds::*;\n            let binding = Binding {\n                mods: $mods,\n                key: $key,\n                flags: $flags,\n                dispatcher: $dis,\n            };\n            Binder::bind(binding)\n        }};\n    }\n\n    /// Macro abstraction over [Binder]\n    ///\n    /// ```rust\n    /// # use hyprland::{bind, default_instance_panic};\n    /// # use hyprland::instance::Instance;\n    ///\n    /// # async fn test() {\n    ///     let instance = default_instance()?;\n    ///     bind!(instance, l r m | SHIFT, Key, \"m\" => Exit);\n    ///     bind!(SHIFT ALT, Key, \"b\" => CenterWindow);\n    ///     bind!(async ; l r m | SHIFT, Key, \"m\" => Exit);\n    ///     bind!(async ; instance,  SUPER, Mod, vec![Mod::SUPER], \"l\" => CenterWindow);\n    ///     bind!(async ; SHIFT ALT, Key, \"b\" => CenterWindow);\n    /// # }\n    /// ```\n    #[macro_export]\n    macro_rules! bind {\n        (async ; $instance:expr, $( $flag:ident ) *|$( $mod:ident ) *,$keyt:ident, $( $key:expr ), * => $dis:ident, $( $arg:expr ), *) => {\n            $crate::bind_raw!(\n                async,\n                $instance,\n                vec![$(Mod::$mod), *],\n                Key::$keyt( $( $key ), * ),\n                vec![$(Flag::$flag), *],\n                DispatchType::$dis( $($arg),* )\n            )\n        };\n        (async ; $instance:expr, $( $flag:ident ) *|$( $mod:ident ) *,$keyt:ident,$( $key:expr ), * => $dis:ident ) => {\n            $crate::bind_raw!(\n                async,\n                $instance,\n                vec![$(Mod::$mod), *],\n                Key::$keyt( $( $key ), * ),\n                vec![$(Flag::$flag), *],\n                DispatchType::$dis\n            )\n        };\n        (async ; $instance:expr, $( $mod:ident ) *,$keyt:ident,$( $key:expr ), * => $dis:ident, $( $arg:expr ), *) => {\n            $crate::bind_raw!(\n                async,\n                $instance,\n                vec![$(Mod::$mod), *],\n                Key::$keyt( $( $key ), * ),\n                vec![],\n                DispatchType::$dis( $($arg),* )\n            )\n        };\n        (async ; $instance:expr, $( $mod:ident ) *,$keyt:ident,$( $key:expr ), * => $dis:ident ) => {\n            $crate::bind_raw!(\n                async,\n                $instance,\n                vec![$(Mod::$mod), *],\n                Key::$keyt( $( $key ), * ),\n                vec![],\n                DispatchType::$dis\n            )\n        };\n        (async ; $( $flag:ident ) *|$( $mod:ident ) *,$keyt:ident, $( $key:expr ), * => $dis:ident, $( $arg:expr ), *) => {\n            $crate::bind_raw!(\n                async,\n                vec![$(Mod::$mod), *],\n                Key::$keyt( $( $key ), * ),\n                vec![$(Flag::$flag), *],\n                DispatchType::$dis( $($arg),* )\n            )\n        };\n        (async ; $( $flag:ident ) *|$( $mod:ident ) *,$keyt:ident,$( $key:expr ), * => $dis:ident ) => {\n            $crate::bind_raw!(\n                async,\n                vec![$(Mod::$mod), *],\n                Key::$keyt( $( $key ), * ),\n                vec![$(Flag::$flag), *],\n                DispatchType::$dis\n            )\n        };\n        (async ; $( $mod:ident ) *,$keyt:ident,$( $key:expr ), * => $dis:ident, $( $arg:expr ), *) => {\n            $crate::bind_raw!(\n                async,\n                vec![$(Mod::$mod), *],\n                Key::$keyt( $( $key ), * ),\n                vec![],\n                DispatchType::$dis( $($arg),* )\n            )\n        };\n        (async ; $( $mod:ident ) *,$keyt:ident,$( $key:expr ), * => $dis:ident ) => {\n            $crate::bind_raw!(\n                async,\n                vec![$(Mod::$mod), *],\n                Key::$keyt( $( $key ), * ),\n                vec![],\n                DispatchType::$dis\n            )\n        };\n        ($instance:expr, $( $flag:ident ) *|$( $mod:ident ) *,$keyt:ident, $( $key:expr ), * => $dis:ident, $( $arg:expr ), *) => {\n            $crate::bind_raw!(\n                $instance,\n                vec![$(Mod::$mod), *],\n                Key::$keyt( $( $key ), * ),\n                vec![$(Flag::$flag), *],\n                DispatchType::$dis( $($arg),* )\n            )\n        };\n        ($instance:expr, $( $flag:ident ) *|$( $mod:ident ) *,$keyt:ident,$( $key:expr ), * => $dis:ident ) => {\n            $crate::bind_raw!(\n                $instance,\n                vec![$(Mod::$mod), *],\n                Key::$keyt( $( $key ), * ),\n                vec![$(Flag::$flag), *],\n                DispatchType::$dis\n            )\n        };\n        ($instance:expr, $( $mod:ident ) *,$keyt:ident,$( $key:expr ), * => $dis:ident, $( $arg:expr ), *) => {\n            $crate::bind_raw!(\n                $instance,\n                vec![$(Mod::$mod), *],\n                Key::$keyt( $( $key ), * ),\n                vec![],\n                DispatchType::$dis( $($arg),* )\n            )\n        };\n        ($instance:expr, $( $mod:ident ) *,$keyt:ident,$( $key:expr ), * => $dis:ident ) => {\n            $crate::bind_raw!(\n                $instance,\n                vec![$(Mod::$mod), *],\n                Key::$keyt( $( $key ), * ),\n                vec![],\n                DispatchType::$dis\n            )\n        };\n        ($( $flag:ident ) *|$( $mod:ident ) *,$keyt:ident, $( $key:expr ), * => $dis:ident, $( $arg:expr ), *) => {\n            $crate::bind_raw!(\n                vec![$(Mod::$mod), *],\n                Key::$keyt( $( $key ), * ),\n                vec![$(Flag::$flag), *],\n                DispatchType::$dis( $($arg),* )\n            )\n        };\n        ($( $flag:ident ) *|$( $mod:ident ) *,$keyt:ident,$( $key:expr ), * => $dis:ident ) => {\n            $crate::bind_raw!(\n                vec![$(Mod::$mod), *],\n                Key::$keyt( $( $key ), * ),\n                vec![$(Flag::$flag), *],\n                DispatchType::$dis\n            )\n        };\n        ($( $mod:ident ) *,$keyt:ident,$( $key:expr ), * => $dis:ident, $( $arg:expr ), *) => {\n            $crate::bind_raw!(\n                vec![$(Mod::$mod), *],\n                Key::$keyt( $( $key ), * ),\n                vec![],\n                DispatchType::$dis( $($arg),* )\n            )\n        };\n        ($( $mod:ident ) *,$keyt:ident,$( $key:expr ), * => $dis:ident ) => {\n            $crate::bind_raw!(\n                vec![$(Mod::$mod), *],\n                Key::$keyt( $( $key ), * ),\n                vec![],\n                DispatchType::$dis\n            )\n        };\n    }\n}\n\n#[test]\nfn test_binds() {\n    use binds::*;\n    let binding = Binding {\n        mods: vec![Mod::SUPER],\n        key: Key::Key(\"v\"),\n        flags: vec![],\n        dispatcher: DispatchType::ToggleFloating(None),\n    };\n    let built_bind = match Binder::gen_str(binding) {\n        Ok(v) => v,\n        Err(e) => panic!(\"Error occured: {e}\"), // Note to greppers: this is in a test!\n    };\n    assert_eq!(built_bind, \"SUPER,v,togglefloating\");\n}\n"
  },
  {
    "path": "dep-crates/hyprland-rs/src/ctl.rs",
    "content": "use derive_more::{Constructor, Display as MDisplay};\nuse std::fmt::Display as FDisplay;\n\nuse crate::default_instance;\nuse crate::instance::Instance;\nuse crate::shared::*;\n\n/// Reload hyprland config\npub mod reload {\n    use super::*;\n\n    /// Reload hyprland config\n    pub fn call() -> crate::Result<()> {\n        instance_call(default_instance()?)\n    }\n\n    /// Reload hyprland config\n    pub fn instance_call(instance: &Instance) -> crate::Result<()> {\n        instance.write_to_socket(command!(Empty, \"reload\"))?;\n        Ok(())\n    }\n\n    /// Reload hyprland config (async)\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    pub async fn call_async() -> crate::Result<()> {\n        instance_call_async(default_instance()?).await\n    }\n\n    /// Reload hyprland config (async)\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    pub async fn instance_call_async(instance: &Instance) -> crate::Result<()> {\n        instance\n            .write_to_socket_async(command!(Empty, \"reload\"))\n            .await?;\n        Ok(())\n    }\n}\n/// Enter kill mode (similar to xkill)\npub mod kill {\n    use super::*;\n\n    /// Enter kill mode (similar to xkill)\n    pub fn call() -> crate::Result<()> {\n        instance_call(default_instance()?)\n    }\n\n    /// Enter kill mode (similar to xkill)\n    pub fn instance_call(instance: &Instance) -> crate::Result<()> {\n        instance.write_to_socket(command!(Empty, \"kill\"))?;\n        Ok(())\n    }\n\n    /// Enter kill mode (similar to xkill) (async)\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    pub async fn call_async() -> crate::Result<()> {\n        instance_call_async(default_instance()?).await\n    }\n\n    /// Enter kill mode (similar to xkill) (async)\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    pub async fn instance_call_async(instance: &Instance) -> crate::Result<()> {\n        instance\n            .write_to_socket_async(command!(Empty, \"kill\"))\n            .await?;\n        Ok(())\n    }\n}\n\n/// Set the cursor theme\npub mod set_cursor {\n    use super::*;\n\n    /// Set the cursor theme\n    pub fn call<Str: FDisplay>(theme: Str, size: u16) -> crate::Result<()> {\n        instance_call(default_instance()?, theme, size)\n    }\n\n    /// Set the cursor theme\n    pub fn instance_call<Str: FDisplay>(\n        instance: &Instance,\n        theme: Str,\n        size: u16,\n    ) -> crate::Result<()> {\n        instance.write_to_socket(command!(Empty, \"setcursor {theme} {size}\"))?;\n        Ok(())\n    }\n\n    /// Set the cursor theme (async)\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    pub async fn call_async<Str: FDisplay>(theme: Str, size: u16) -> crate::Result<()> {\n        instance_call_async(default_instance()?, theme, size).await\n    }\n\n    /// Set the cursor theme (async)\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    pub async fn instance_call_async<Str: FDisplay>(\n        instance: &Instance,\n        theme: Str,\n        size: u16,\n    ) -> crate::Result<()> {\n        instance\n            .write_to_socket_async(command!(Empty, \"setcursor {theme} {size}\"))\n            .await?;\n        Ok(())\n    }\n}\n\n/// Stuff related to managing virtual outputs/displays\npub mod output {\n    use super::*;\n\n    /// Output backend types\n    #[derive(Debug, MDisplay, Clone, Copy, PartialEq, Eq)]\n    pub enum OutputBackends {\n        /// The wayland output backend\n        #[display(\"wayland\")]\n        Wayland,\n        /// The x11 output backend\n        #[display(\"x11\")]\n        X11,\n        /// The headless output backend\n        #[display(\"headless\")]\n        Headless,\n        /// Let Hyprland decide the backend type\n        #[display(\"auto\")]\n        Auto,\n    }\n\n    /// Create virtual displays\n    pub fn create(backend: OutputBackends, name: Option<&str>) -> crate::Result<()> {\n        instance_create(default_instance()?, backend, name)\n    }\n\n    /// Remove virtual displays\n    pub fn remove<Str: FDisplay>(name: Str) -> crate::Result<()> {\n        instance_remove(default_instance()?, name)\n    }\n\n    /// Create virtual displays\n    pub fn instance_create(\n        instance: &Instance,\n        backend: OutputBackends,\n        name: Option<&str>,\n    ) -> crate::Result<()> {\n        let name = name.unwrap_or_default();\n        instance.write_to_socket(command!(Empty, \"output create {backend} {name}\"))?;\n        Ok(())\n    }\n\n    /// Remove virtual displays\n    pub fn instance_remove<Str: FDisplay>(instance: &Instance, name: Str) -> crate::Result<()> {\n        instance.write_to_socket(command!(Empty, \"output remove {name}\"))?;\n        Ok(())\n    }\n\n    /// Create virtual displays\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    pub async fn create_async(backend: OutputBackends, name: Option<&str>) -> crate::Result<()> {\n        instance_create_async(default_instance()?, backend, name).await\n    }\n\n    /// Create virtual displays\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    pub async fn instance_create_async(\n        instance: &Instance,\n        backend: OutputBackends,\n        name: Option<&str>,\n    ) -> crate::Result<()> {\n        let name = name.unwrap_or_default();\n        instance\n            .write_to_socket_async(command!(Empty, \"output create {backend} {name}\"))\n            .await?;\n        Ok(())\n    }\n\n    /// Remove virtual displays\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    pub async fn remove_async<Str: FDisplay>(name: Str) -> crate::Result<()> {\n        instance_remove_async(default_instance()?, name).await\n    }\n\n    /// Remove virtual displays\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    pub async fn instance_remove_async<Str: FDisplay>(\n        instance: &Instance,\n        name: Str,\n    ) -> crate::Result<()> {\n        instance\n            .write_to_socket_async(command!(Empty, \"output remove {name}\"))\n            .await?;\n        Ok(())\n    }\n}\n\n/// Switch the xkb layout index for a keyboard\npub mod switch_xkb_layout {\n    use super::*;\n\n    /// The types of Cmds used by [switch_xkb_layout]\n    #[derive(Debug, MDisplay, Clone, Copy, PartialEq, Eq)]\n    pub enum SwitchXKBLayoutCmdTypes {\n        /// Next input\n        #[display(\"next\")]\n        Next,\n        /// Previous inout\n        #[display(\"prev\")]\n        Previous,\n        /// Set to a specific input id\n        #[display(\"{_0}\")]\n        Id(u8),\n    }\n\n    /// Switch the xkb layout index for a keyboard\n    pub fn call<Str: FDisplay>(device: Str, cmd: SwitchXKBLayoutCmdTypes) -> crate::Result<()> {\n        instance_call(default_instance()?, device, cmd)\n    }\n\n    /// Switch the xkb layout index for a keyboard\n    pub fn instance_call<Str: FDisplay>(\n        instance: &Instance,\n        device: Str,\n        cmd: SwitchXKBLayoutCmdTypes,\n    ) -> crate::Result<()> {\n        instance.write_to_socket(command!(Empty, \"switchxkblayout {device} {cmd}\"))?;\n        Ok(())\n    }\n\n    /// Switch the xkb layout index for a keyboard\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    pub async fn call_async<Str: FDisplay>(\n        instance: &Instance,\n        device: Str,\n        cmd: SwitchXKBLayoutCmdTypes,\n    ) -> crate::Result<()> {\n        instance_call_async(instance, device, cmd).await\n    }\n\n    /// Switch the xkb layout index for a keyboard\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    pub async fn instance_call_async<Str: FDisplay>(\n        instance: &Instance,\n        device: Str,\n        cmd: SwitchXKBLayoutCmdTypes,\n    ) -> crate::Result<()> {\n        instance\n            .write_to_socket_async(command!(Empty, \"switchxkblayout {device} {cmd}\"))\n            .await?;\n        Ok(())\n    }\n}\n\n/// Creates a error that Hyprland will display\npub mod set_error {\n    use super::*;\n\n    /// Creates a error that Hyprland will display\n    pub fn call(color: Color, msg: String) -> crate::Result<()> {\n        instance_call(default_instance()?, color, msg)\n    }\n\n    /// Creates a error that Hyprland will display\n    pub fn instance_call(instance: &Instance, color: Color, msg: String) -> crate::Result<()> {\n        instance.write_to_socket(command!(Empty, \"seterror {color} {msg}\"))?;\n        Ok(())\n    }\n\n    /// Creates a error that Hyprland will display (async)\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    pub async fn call_async(color: Color, msg: String) -> crate::Result<()> {\n        instance_call_async(default_instance()?, color, msg).await\n    }\n\n    /// Creates a error that Hyprland will display (async)\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    pub async fn instance_call_async(\n        instance: &Instance,\n        color: Color,\n        msg: String,\n    ) -> crate::Result<()> {\n        instance\n            .write_to_socket_async(command!(Empty, \"seterror {color} {msg}\"))\n            .await?;\n        Ok(())\n    }\n}\n\n/// Creates a notification with Hyprland\npub mod notify {\n    use super::*;\n\n    #[allow(missing_docs)]\n    #[derive(Debug, Copy, Clone, PartialEq, Eq)]\n    #[repr(i8)]\n    pub enum Icon {\n        NoIcon = -1,\n        Warning = 0,\n        Info = 1,\n        Hint = 2,\n        Error = 3,\n        Confused = 4,\n        Ok = 5,\n    }\n\n    /// Creates a notification with Hyprland\n    pub fn call(\n        icon: Icon,\n        time: std::time::Duration,\n        color: Color,\n        msg: String,\n    ) -> crate::Result<()> {\n        instance_call(default_instance()?, icon, time, color, msg)\n    }\n\n    /// Creates a notification with Hyprland\n    pub fn instance_call(\n        instance: &Instance,\n        icon: Icon,\n        time: std::time::Duration,\n        color: Color,\n        msg: String,\n    ) -> crate::Result<()> {\n        instance.write_to_socket(command!(\n            Empty,\n            \"notify {} {} {color} {msg}\",\n            icon as i8,\n            time.as_millis()\n        ))?;\n        Ok(())\n    }\n\n    /// Creates a error that Hyprland will display (async)\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    pub async fn call_async(\n        icon: Icon,\n        time: std::time::Duration,\n        color: Color,\n        msg: String,\n    ) -> crate::Result<()> {\n        instance_call_async(default_instance()?, icon, time, color, msg).await\n    }\n\n    /// Creates a error that Hyprland will display (async)\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    pub async fn instance_call_async(\n        instance: &Instance,\n        icon: Icon,\n        time: std::time::Duration,\n        color: Color,\n        msg: String,\n    ) -> crate::Result<()> {\n        instance\n            .write_to_socket_async(command!(\n                Empty,\n                \"notify {} {} {color} {msg}\",\n                icon as i8,\n                time.as_millis()\n            ))\n            .await?;\n        Ok(())\n    }\n}\n/// Dismisses all or up to a specified amount of notifications with Hyprland\npub mod dismissnotify {\n    use super::*;\n\n    /// Dismisses notifications with Hyprland\n    ///\n    /// If `amount` is [None] then will dismiss ALL notifications\n    pub fn call(amount: Option<std::num::NonZeroU8>) -> crate::Result<()> {\n        instance_call(default_instance()?, amount)\n    }\n\n    /// Dismisses notifications with Hyprland\n    ///\n    /// If `amount` is [None] then will dismiss ALL notifications\n    pub fn instance_call(\n        instance: &Instance,\n        amount: Option<std::num::NonZeroU8>,\n    ) -> crate::Result<()> {\n        instance.write_to_socket(command!(\n            Empty,\n            \"dismissnotify {}\",\n            if let Some(amount) = amount {\n                amount.to_string()\n            } else {\n                (-1).to_string()\n            }\n        ))?;\n        Ok(())\n    }\n\n    /// Dismisses notifications with Hyprland (async)\n    ///\n    /// If `amount` is [None] then will dismiss ALL notifications\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    pub async fn call_async(amount: Option<std::num::NonZeroU8>) -> crate::Result<()> {\n        instance_call_async(default_instance()?, amount).await\n    }\n\n    /// Dismisses notifications with Hyprland (async)\n    ///\n    /// If `amount` is [None] then will dismiss ALL notifications\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    pub async fn instance_call_async(\n        instance: &Instance,\n        amount: Option<std::num::NonZeroU8>,\n    ) -> crate::Result<()> {\n        instance\n            .write_to_socket_async(command!(\n                Empty,\n                \"dismissnotify {}\",\n                if let Some(amount) = amount {\n                    amount.to_string()\n                } else {\n                    (-1).to_string()\n                }\n            ))\n            .await?;\n        Ok(())\n    }\n}\n\n/// A 8-bit color with a alpha channel\n#[derive(Debug, Copy, Clone, MDisplay, Constructor, PartialEq, Eq)]\n#[display(\"rgba({_0:02x}{_1:02x}{_2:02x}{_3:02x})\")]\npub struct Color(u8, u8, u8, u8);\n\n/// Provides things to setting props\npub mod set_prop {\n    use super::*;\n\n    fn l(b: bool) -> &'static str {\n        if b {\n            \"lock\"\n        } else {\n            \"\"\n        }\n    }\n\n    /// Type that represents a prop\n    #[derive(MDisplay, Clone, PartialEq)]\n    pub enum PropType {\n        /// The animation style\n        #[display(\"animationstyle {_0}\")]\n        AnimationStyle(String),\n        /// The roundness\n        #[display(\"rounding {_0} {}\", l(*_1))]\n        Rounding(\n            i64,\n            /// locked\n            bool,\n        ),\n        /// Force no blur\n        #[display(\"forcenoblur {} {}\", *_0 as u8, l(*_1))]\n        ForceNoBlur(\n            bool,\n            /// locked\n            bool,\n        ),\n        /// Force opaque\n        #[display(\"forceopaque {} {}\", *_0 as u8, l(*_1))]\n        ForceOpaque(\n            bool,\n            /// locked\n            bool,\n        ),\n        /// Force opaque overriden\n        #[display(\"forceopaqueoverriden {} {}\", *_0 as u8, l(*_1))]\n        ForceOpaqueOverriden(\n            bool,\n            /// locked\n            bool,\n        ),\n        /// Force allow input\n        #[display(\"forceallowsinput {} {}\", *_0 as u8, l(*_1))]\n        ForceAllowsInput(\n            bool,\n            /// locked\n            bool,\n        ),\n        /// Force no animations\n        #[display(\"forcenoanims {} {}\", *_0 as u8, l(*_1))]\n        ForceNoAnims(\n            bool,\n            /// locked\n            bool,\n        ),\n        /// Force no border\n        #[display(\"forcenoborder {} {}\", *_0 as u8, l(*_1))]\n        ForceNoBorder(\n            bool,\n            /// locked\n            bool,\n        ),\n        /// Force no shadow\n        #[display(\"forcenoshadow {} {}\", *_0 as u8, l(*_1))]\n        ForceNoShadow(\n            bool,\n            /// locked\n            bool,\n        ),\n        /// Allow for windoe dancing?\n        #[display(\"windowdancecompat {} {}\", *_0 as u8, l(*_1))]\n        WindowDanceCompat(\n            bool,\n            /// locked\n            bool,\n        ),\n        /// Allow for overstepping max size\n        #[display(\"nomaxsize {} {}\", *_0 as u8, l(*_1))]\n        NoMaxSize(\n            bool,\n            /// locked\n            bool,\n        ),\n        /// Dim around?\n        #[display(\"dimaround {} {}\", *_0 as u8, l(*_1))]\n        DimAround(\n            bool,\n            /// locked\n            bool,\n        ),\n        /// Makes the next setting be override instead of multiply\n        #[display(\"alphaoverride {} {}\", *_0 as u8, l(*_1))]\n        AlphaOverride(\n            bool,\n            /// locked\n            bool,\n        ),\n        /// The alpha\n        #[display(\"alpha {_0} {}\", l(*_1))]\n        Alpha(\n            f32,\n            /// locked\n            bool,\n        ),\n        /// Makes the next setting be override instead of multiply\n        #[display(\"alphainactiveoverride {} {}\", *_0 as u8, l(*_1))]\n        AlphaInactiveOverride(\n            bool,\n            /// locked\n            bool,\n        ),\n        /// The alpha for inactive\n        #[display(\"alphainactive {_0} {}\", l(*_1))]\n        AlphaInactive(\n            f32,\n            /// locked\n            bool,\n        ),\n        /// The active border color\n        #[display(\"alphabordercolor {_0} {}\", l(*_1))]\n        ActiveBorderColor(\n            Color,\n            /// locked\n            bool,\n        ),\n        /// The inactive border color\n        #[display(\"inalphabordercolor {_0} {}\", l(*_1))]\n        InactiveBorderColor(\n            Color,\n            /// locked\n            bool,\n        ),\n    }\n\n    /// Sets a window prob\n    pub fn call(ident: String, prop: PropType, lock: bool) -> crate::Result<()> {\n        instance_call(default_instance()?, ident, prop, lock)\n    }\n\n    /// Sets a window prob\n    pub fn instance_call(\n        instance: &Instance,\n        ident: String,\n        prop: PropType,\n        lock: bool,\n    ) -> crate::Result<()> {\n        instance.write_to_socket(command!(\n            Empty,\n            \"setprop {ident} {prop} {}\",\n            if lock { \"lock\" } else { \"\" }\n        ))?;\n        Ok(())\n    }\n\n    /// Sets a window prob (async)\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    pub async fn call_async(ident: String, prop: PropType, lock: bool) -> crate::Result<()> {\n        instance_call_async(default_instance()?, ident, prop, lock).await\n    }\n\n    /// Sets a window prob (async)\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    pub async fn instance_call_async(\n        instance: &Instance,\n        ident: String,\n        prop: PropType,\n        lock: bool,\n    ) -> crate::Result<()> {\n        instance\n            .write_to_socket_async(command!(\n                Empty,\n                \"setprop {ident} {prop} {}\",\n                if lock { \"lock\" } else { \"\" }\n            ))\n            .await?;\n        Ok(())\n    }\n}\n\n/// Provides functions for communication with plugin system\npub mod plugin {\n    use super::*;\n    use crate::error::HyprError;\n    use std::path::Path;\n\n    /// This struct represents a loaded plugin\n    #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]\n    pub struct Plugin {\n        /// plugin name\n        pub name: String,\n        /// plugin author\n        pub author: String,\n        /// handle to plugin\n        pub handle: String,\n        /// plugin version\n        pub version: String,\n        /// plugin description\n        pub description: String,\n    }\n\n    /// Returns a list of all plugins\n    pub fn list() -> crate::Result<Vec<Plugin>> {\n        instance_list(default_instance()?)\n    }\n\n    /// Returns a list of all plugins\n    pub fn instance_list(instance: &Instance) -> crate::Result<Vec<Plugin>> {\n        let data = instance.write_to_socket(command!(JSON, \"plugin list\"))?;\n        let deserialized: Vec<Plugin> = serde_json::from_str(&data)?;\n        Ok(deserialized)\n    }\n\n    /// Returns a list of all plugins (async)\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    pub async fn list_async() -> crate::Result<Vec<Plugin>> {\n        instance_list_async(default_instance()?).await\n    }\n\n    /// Returns a list of all plugins (async)\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    pub async fn instance_list_async(instance: &Instance) -> crate::Result<Vec<Plugin>> {\n        let data = instance\n            .write_to_socket_async(command!(JSON, \"plugin list\"))\n            .await?;\n        let deserialized: Vec<Plugin> = serde_json::from_str(&data)?;\n        Ok(deserialized)\n    }\n\n    /// Loads a plugin, by absolute path\n    pub fn load(path: &Path) -> crate::Result<()> {\n        instance_load(default_instance()?, path)\n    }\n\n    /// Loads a plugin, by absolute path\n    pub fn instance_load(instance: &Instance, path: &Path) -> crate::Result<()> {\n        let str = instance.write_to_socket(command!(Empty, \"plugin load {}\", path.display()))?;\n        if str.contains(\"could not be loaded\") {\n            Err(HyprError::Internal(str))\n        } else {\n            Ok(())\n        }\n    }\n\n    /// Loads a plugin, by absolute path (async)\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    pub async fn load_async(path: &Path) -> crate::Result<()> {\n        instance_load_async(default_instance()?, path).await\n    }\n\n    /// Loads a plugin, by absolute path (async)\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    pub async fn instance_load_async(instance: &Instance, path: &Path) -> crate::Result<()> {\n        let str = instance\n            .write_to_socket_async(command!(Empty, \"plugin load {}\", path.display()))\n            .await?;\n        if str.contains(\"could not be loaded\") {\n            Err(HyprError::Internal(str))\n        } else {\n            Ok(())\n        }\n    }\n\n    /// Unloads a plugin, by absolute path.\n    pub fn unload(path: &Path) -> crate::Result<()> {\n        instance_unload(default_instance()?, path)\n    }\n\n    /// Unloads a plugin, by absolute path.\n    pub fn instance_unload(instance: &Instance, path: &Path) -> crate::Result<()> {\n        let str = instance.write_to_socket(command!(Empty, \"plugin unload {}\", path.display()))?;\n        if str.contains(\"plugin not loaded\") {\n            Err(HyprError::Internal(str))\n        } else {\n            Ok(())\n        }\n    }\n\n    /// Unloads a plugin, by absolute path (async)\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    pub async fn unload_async(path: &Path) -> crate::Result<()> {\n        instance_unload_async(default_instance()?, path).await\n    }\n\n    /// Unloads a plugin, by absolute path (async)\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    pub async fn instance_unload_async(instance: &Instance, path: &Path) -> crate::Result<()> {\n        let str = instance\n            .write_to_socket_async(command!(Empty, \"plugin unload {}\", path.display()))\n            .await?;\n        if str.contains(\"plugin not loaded\") {\n            Err(HyprError::Internal(str))\n        } else {\n            Ok(())\n        }\n    }\n}\n\n/// This module allows listing running hyprland instances\npub mod instance {\n    use crate::shared::get_hypr_path;\n    use std::fs::{DirEntry, File};\n    use std::io::Read;\n    use std::path::Path;\n\n    /// This struct represents a running Hyprland instance\n    #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]\n    pub struct Instance {\n        /// instance name (9958d29...) in /run/user/$UID/hypr/$instance\n        pub instance: String,\n        /// ???\n        pub time: u64,\n        /// pid of hyprland process\n        pub pid: u32,\n        /// name of wayland socket in /run/user/$UID/$wl_socket\n        pub wl_socket: String,\n    }\n\n    /// Returns a list of running instances\n    pub fn instance_list() -> crate::Result<Vec<Instance>> {\n        let buf = get_hypr_path()?;\n        let entries = std::fs::read_dir(buf)?;\n        let mut instances = Vec::new();\n        for entry in entries.flatten() {\n            if let Some(instance) = parse_instance_entry(entry) {\n                instances.push(instance);\n            }\n        }\n        instances.retain(|el| Path::new(&format!(\"/proc/{}\", el.pid)).exists());\n        Ok(instances)\n    }\n\n    fn parse_instance_entry(entry: DirEntry) -> Option<Instance> {\n        let file_name = entry.file_name().to_string_lossy().to_string();\n        let first = file_name.find('_')?;\n        let last = file_name.rfind('_')?;\n        if last <= first {\n            return None;\n        }\n        let time = file_name[first + 1..last].parse::<u64>().ok()?;\n\n        let lock_path = entry.path().join(\"hyprland.lock\");\n        let mut file = File::open(&lock_path).ok()?;\n        if file.metadata().ok()?.len() == 0 {\n            return None; // Empty lock file, skip this instance\n        }\n        let mut content = String::new();\n        file.read_to_string(&mut content).ok()?;\n        let data = content\n            .lines()\n            .map(|line| line.trim().to_string())\n            .collect::<Vec<_>>();\n        if data.len() != 2 {\n            return None;\n        }\n\n        let pid = data.first().and_then(|s| s.parse::<u32>().ok())?;\n        let wl_socket = data.get(1).cloned().unwrap_or_default();\n\n        Some(Instance {\n            instance: file_name,\n            time,\n            pid,\n            wl_socket,\n        })\n    }\n}\n"
  },
  {
    "path": "dep-crates/hyprland-rs/src/data/helpers.rs",
    "content": "use super::*;\nuse crate::default_instance;\n\n/// A helper struct that provides the current fullscreen state\n#[derive(Debug, Clone, Copy, PartialEq, Eq, derive_more::Display)]\npub struct FullscreenState(\n    /// State\n    pub bool,\n);\n\nimpl HyprData for FullscreenState {\n    fn get() -> crate::Result<Self> {\n        Self::instance_get(default_instance()?)\n    }\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    async fn get_async() -> crate::Result<Self> {\n        Self::instance_get_async(default_instance()?).await\n    }\n    fn instance_get(instance: &crate::instance::Instance) -> crate::Result<Self> {\n        Ok(Self(Workspace::instance_get_active(instance)?.fullscreen))\n    }\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    async fn instance_get_async(instance: &crate::instance::Instance) -> crate::Result<Self> {\n        Ok(Self(\n            Workspace::instance_get_active_async(instance)\n                .await?\n                .fullscreen,\n        ))\n    }\n}\n\nimpl FullscreenState {\n    /// This method returns a bool of the current fullscreen state\n    pub fn bool(self) -> bool {\n        self.0\n    }\n}\n"
  },
  {
    "path": "dep-crates/hyprland-rs/src/data/macros.rs",
    "content": "macro_rules! impl_on {\n    ($name:ident) => {\n        impl HyprData for $name {\n            fn get() -> $crate::Result<Self> {\n                Self::instance_get(crate::default_instance()?)\n            }\n            fn instance_get(instance: &crate::instance::Instance) -> $crate::Result<Self> {\n                let data = instance.write_to_socket(command!(JSON, \"{}\", DataCommands::$name))?;\n                let deserialized: $name = serde_json::from_str(&data)?;\n                Ok(deserialized)\n            }\n            #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n            async fn get_async() -> $crate::Result<Self> {\n                Self::instance_get_async(crate::default_instance()?).await\n            }\n            #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n            async fn instance_get_async(\n                instance: &crate::instance::Instance,\n            ) -> $crate::Result<Self> {\n                let data = instance\n                    .write_to_socket_async(command!(JSON, \"{}\", DataCommands::$name))\n                    .await?;\n                let deserialized: $name = serde_json::from_str(&data)?;\n                Ok(deserialized)\n            }\n        }\n    };\n}\n\nmacro_rules! implement_iterators {\n    (\n        vector,\n        name: $name:ident,\n        iterated_field: $iterated_field:tt,\n        holding_type: $holding_type:ty,\n    ) => {\n        impl $name {\n            paste!(\n                #[doc = \"Creates the iterator by references of `\" $name \"`.\"]\n                pub fn iter(&self) -> std::slice::Iter<'_, $holding_type> {\n                    self.0.iter()\n                }\n\n                #[doc = \"Creates the iterator by mutable references of \" $name \"`.\"]\n                pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, $holding_type> {\n                    self.0.iter_mut()\n                }\n            );\n        }\n\n        impl IntoIterator for $name {\n            type Item = $holding_type;\n\n            type IntoIter = std::vec::IntoIter<$holding_type>;\n\n            fn into_iter(self) -> Self::IntoIter {\n                self.0.into_iter()\n            }\n        }\n\n        impl<'a> IntoIterator for &'a $name {\n            type Item = &'a $holding_type;\n            type IntoIter = std::slice::Iter<'a, $holding_type>;\n\n            fn into_iter(self) -> Self::IntoIter {\n                self.0.iter()\n            }\n        }\n\n        impl<'a> IntoIterator for &'a mut $name {\n            type Item = &'a mut $holding_type;\n            type IntoIter = std::slice::IterMut<'a, $holding_type>;\n\n            fn into_iter(self) -> Self::IntoIter {\n                self.0.iter_mut()\n            }\n        }\n    };\n\n    (\n        table,\n        name: $name:ident,\n        iterated_field: $iterated_field:tt,\n        key: $key:ty,\n        value: $value:ty,\n    ) => {\n        impl $name {\n            paste!(\n                #[doc = \"Creates the iterator of map by references of \" $name]\n                pub fn iter(&self) -> std::collections::hash_map::Iter<'_, $key, $value> {\n                    self.$iterated_field.iter()\n                }\n\n                #[doc = \"Creates the iterator of map by mutable references of `\" $name \"`.\"]\n                pub fn iter_mut(&mut self) -> std::collections::hash_map::IterMut<'_, $key, $value> {\n                    self.$iterated_field.iter_mut()\n                }\n\n                #[doc = \"Creates the consuming iterator by keys with type `\" $key \"` of `\" $name \"`.\"]\n                pub fn into_keys(self) -> std::collections::hash_map::IntoKeys<$key, $value> {\n                    self.$iterated_field.into_keys()\n                }\n\n                #[doc = \"Creates the consuming iterator by values of `\" $name \"`.\"]\n                pub fn into_values(self) -> std::collections::hash_map::IntoValues<$key, $value> {\n                    self.$iterated_field.into_values()\n                }\n            );\n        }\n\n        impl IntoIterator for $name {\n            type Item = ($key, $value);\n            type IntoIter = std::collections::hash_map::IntoIter<$key, $value>;\n\n            fn into_iter(self) -> Self::IntoIter {\n                self.$iterated_field.into_iter()\n            }\n        }\n\n        impl<'a> IntoIterator for &'a $name {\n            type Item = (&'a $key, &'a $value);\n            type IntoIter = std::collections::hash_map::Iter<'a, $key, $value>;\n\n            fn into_iter(self) -> Self::IntoIter {\n                self.$iterated_field.iter()\n            }\n        }\n\n        impl<'a> IntoIterator for &'a mut $name {\n            type Item = (&'a $key, &'a mut $value);\n            type IntoIter = std::collections::hash_map::IterMut<'a, $key, $value>;\n\n            fn into_iter(self) -> Self::IntoIter {\n                self.$iterated_field.iter_mut()\n            }\n        }\n    }\n}\n\nmacro_rules! create_data_struct {\n    (\n        vector,\n        name: $name:ident,\n        command: $cmd_kind:path,\n        holding_type: $holding_type:ty,\n        doc: $doc:literal\n    ) => {\n        #[doc = $doc]\n        #[derive(Debug, Clone)]\n        pub struct $name(Vec<$holding_type>);\n\n        implement_iterators!(\n            vector,\n            name: $name,\n            iterated_field: 0,\n            holding_type: $holding_type,\n        );\n\n        impl HyprData for $name {\n            fn get() -> $crate::Result<Self> {\n                Self::instance_get(crate::default_instance()?)\n            }\n            fn instance_get(instance: &crate::instance::Instance) -> $crate::Result<Self> {\n                let data = instance.write_to_socket(command!(JSON, \"{}\", $cmd_kind))?;\n                let deserialized: Vec<$holding_type> = serde_json::from_str(&data)?;\n                Ok(Self(deserialized))\n            }\n            #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n            async fn get_async() -> $crate::Result<Self> {\n                Self::instance_get_async(crate::default_instance()?).await\n            }\n            #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n            async fn instance_get_async(instance: &crate::instance::Instance) -> $crate::Result<Self> {\n                let data = instance.write_to_socket_async(command!(JSON, \"{}\", $cmd_kind)).await?;\n                let deserialized: Vec<$holding_type> = serde_json::from_str(&data)?;\n                Ok(Self(deserialized))\n            }\n        }\n\n        impl HyprDataVec<$holding_type> for $name {\n            fn to_vec(self) -> Vec<$holding_type> {\n                self.0\n            }\n        }\n    };\n\n    (\n        table,\n        name: $name:ident,\n        command: $cmd_kind:path,\n        key: $key:ty,\n        value: $value:ty,\n        doc: $doc:literal\n    ) => {\n        #[doc = $doc]\n        #[derive(Debug)]\n        pub struct $name(HashMap<$key, $value>);\n\n        implement_iterators!(\n            table,\n            name: $name,\n            iterated_field: 0,\n            key: $key,\n            value: $value,\n        );\n\n        impl HyprData for $name {\n            fn get() -> $crate::Result<Self> {\n                Self::instance_get(crate::default_instance()?)\n            }\n            fn instance_get(instance: &crate::instance::Instance) -> $crate::Result<Self> {\n                let data = instance.write_to_socket(command!(JSON, \"{}\", $cmd_kind))?;\n                let deserialized: HashMap<$key, $value> = serde_json::from_str(&data)?;\n                Ok(Self(deserialized))\n            }\n            #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n            async fn get_async() -> $crate::Result<Self> {\n                Self::instance_get_async(crate::default_instance()?).await\n            }\n            #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n            async fn instance_get_async(instance: &crate::instance::Instance) -> $crate::Result<Self> {\n                let data = instance.write_to_socket_async(command!(JSON, \"{}\", $cmd_kind)).await?;\n                let deserialized: HashMap<$key, $value> = serde_json::from_str(&data)?;\n                Ok(Self(deserialized))\n            }\n        }\n    };\n}\n"
  },
  {
    "path": "dep-crates/hyprland-rs/src/data/mod.rs",
    "content": "//! # Data module\n//!\n//! This module provides functions for getting information on the compositor\n//!\n//! ## Usage\n//!\n//! here is a example of every function in use! (blocking)\n//! ```rust\n//! use hyprland::data::*;\n//! use hyprland::prelude::*;\n//! use hyprland::Result;\n//!\n//! fn main() -> Result<()> {\n//!     let instance = &hyprland::instance::Instance::from_current_env()?;\n//!\n//!     let monitors = Monitors::get(instance)?.to_vec();\n//!     println!(\"{monitors:#?}\");\n//!\n//!     let workspaces = Workspaces::get(instance)?.to_vec();\n//!     println!(\"{workspaces:#?}\");\n//!\n//!     let clients = Clients::get(instance)?.to_vec();\n//!     println!(\"{clients:#?}\");\n//!\n//!     let active_window = Client::get_active(instance)?;\n//!     println!(\"{active_window:#?}\");\n//!\n//!     let layers = Layers::get(instance)?;\n//!     println!(\"{layers:#?}\");\n//!\n//!     let devices = Devices::get(instance)?;\n//!     println!(\"{devices:#?}\");\n//!\n//!     let version = Version::get(instance)?;\n//!     println!(\"{version:#?}\");\n//!\n//!     let cursor_pos = CursorPosition::get(instance)?;\n//!     println!(\"{cursor_pos:#?}\");\n//!     Ok(())\n//! }\n//! ```\n\n#[macro_use]\nmod macros;\n\nuse crate::shared::*;\n\n#[cfg(feature = \"ahash\")]\nuse ahash::HashMap;\n#[cfg(not(feature = \"ahash\"))]\nuse std::collections::HashMap;\n\nmod regular;\n\n/// Helpers data commands, these use other hyprctl commands to create new ones!\nmod helpers;\n\npub use crate::data::helpers::*;\n\npub use crate::data::regular::*;\n\n//// This module provides async function calls\n//pub mod asynchronous;\n\n//// This module provides blocking function calls\n//pub mod blocking;\n"
  },
  {
    "path": "dep-crates/hyprland-rs/src/data/regular.rs",
    "content": "use super::*;\nuse crate::default_instance;\nuse crate::error::hypr_err;\nuse crate::instance::Instance;\nuse derive_more::Display;\nuse serde::{Deserialize, Serialize};\nuse serde_repr::{Deserialize_repr, Serialize_repr};\n\n/// This pub(crate) enum holds every socket command that returns data\n#[derive(Debug, Display, Clone, Copy, PartialEq, Eq)]\npub(crate) enum DataCommands {\n    #[display(\"monitors all\")]\n    Monitors,\n    #[display(\"workspaces\")]\n    Workspaces,\n    #[display(\"activeworkspace\")]\n    ActiveWorkspace,\n    #[display(\"clients\")]\n    Clients,\n    #[display(\"activewindow\")]\n    ActiveWindow,\n    #[display(\"layers\")]\n    Layers,\n    #[display(\"devices\")]\n    Devices,\n    #[display(\"version\")]\n    Version,\n    #[display(\"cursorpos\")]\n    CursorPosition,\n    #[display(\"binds\")]\n    Binds,\n    #[display(\"animations\")]\n    Animations,\n    #[display(\"workspacerules\")]\n    WorkspaceRules,\n}\n\n/// This struct holds a basic identifier for a workspace often used in other structs\n#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]\npub struct WorkspaceBasic {\n    /// The workspace Id\n    pub id: WorkspaceId,\n    /// The workspace's name\n    pub name: String,\n}\n\n/// This enum provides the different monitor transforms\n#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, PartialEq, Eq, Copy)]\n#[repr(u8)]\npub enum Transforms {\n    /// No transform\n    Normal = 0,\n    /// Rotated 90 degrees\n    Normal90 = 1,\n    /// Rotated 180 degrees\n    Normal180 = 2,\n    /// Rotated 270 degrees\n    Normal270 = 3,\n    /// Flipped\n    Flipped = 4,\n    /// Flipped and rotated 90 degrees\n    Flipped90 = 5,\n    /// Flipped and rotated 180 degrees\n    Flipped180 = 6,\n    /// Flipped and rotated 270 degrees\n    Flipped270 = 7,\n}\n\n/// This struct holds information for a monitor\n#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]\npub struct Monitor {\n    /// The monitor id\n    pub id: MonitorId,\n    /// The monitor's name\n    pub name: String,\n    /// The monitor's description\n    pub description: String,\n    /// The monitor width (in pixels)\n    pub width: u16,\n    /// The monitor height (in pixels)\n    pub height: u16,\n    /// The monitor's refresh rate (in hertz)\n    #[serde(rename = \"refreshRate\")]\n    pub refresh_rate: f32,\n    /// The monitor's position on the x axis (not irl ofc)\n    pub x: i32,\n    /// The monitor's position on the x axis (not irl ofc)\n    pub y: i32,\n    /// A basic identifier for the active workspace\n    #[serde(rename = \"activeWorkspace\")]\n    pub active_workspace: WorkspaceBasic,\n    /// A basic identifier for the special workspace\n    #[serde(rename = \"specialWorkspace\")]\n    pub special_workspace: WorkspaceBasic,\n    /// Reserved is the amount of space (in pre-scale pixels) that a layer surface has claimed\n    pub reserved: (u16, u16, u16, u16),\n    /// The display's scale\n    pub scale: f32,\n    /// I think like the rotation?\n    pub transform: Transforms,\n    /// a string that identifies if the display is active\n    pub focused: bool,\n    /// The dpms status of a monitor\n    #[serde(rename = \"dpmsStatus\")]\n    pub dpms_status: bool,\n    /// VRR state\n    pub vrr: bool,\n    /// Is the monitor disabled or not\n    pub disabled: bool,\n}\n\nimpl HyprDataActive for Monitor {\n    fn get_active() -> crate::Result<Self> {\n        Self::instance_get_active(default_instance()?)\n    }\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    async fn get_active_async() -> crate::Result<Self> {\n        Self::instance_get_active_async(default_instance()?).await\n    }\n    fn instance_get_active(instance: &Instance) -> crate::Result<Self> {\n        let all = Monitors::instance_get(instance)?;\n        if let Some(it) = all.into_iter().find(|item| item.focused) {\n            Ok(it)\n        } else {\n            hypr_err!(\"No active Hyprland monitor detected!\")\n        }\n    }\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    async fn instance_get_active_async(instance: &Instance) -> crate::Result<Self> {\n        let all = Monitors::instance_get_async(instance).await?;\n        if let Some(it) = all.into_iter().find(|item| item.focused) {\n            Ok(it)\n        } else {\n            hypr_err!(\"No active Hyprland monitor detected!\")\n        }\n    }\n}\n\ncreate_data_struct!(\n    vector,\n    name: Monitors,\n    command: DataCommands::Monitors,\n    holding_type: Monitor,\n    doc: \"This struct holds a vector of monitors\"\n);\n\n/// This struct holds information for a workspace\n#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]\npub struct Workspace {\n    /// The workspace Id\n    pub id: WorkspaceId,\n    /// The workspace's name\n    pub name: String,\n    /// The monitor the workspace is on\n    pub monitor: String,\n    /// The monitor id the workspace is on, can be None in some cases\n    #[serde(rename = \"monitorID\")]\n    pub monitor_id: Option<MonitorId>,\n    /// The amount of windows in the workspace\n    pub windows: u16,\n    /// A bool that shows if there is a fullscreen window in the workspace\n    #[serde(rename = \"hasfullscreen\")]\n    pub fullscreen: bool,\n    /// The last window's [Address]\n    #[serde(rename = \"lastwindow\")]\n    pub last_window: Address,\n    /// The last window's title\n    #[serde(rename = \"lastwindowtitle\")]\n    pub last_window_title: String,\n}\n\nimpl HyprDataActive for Workspace {\n    fn get_active() -> crate::Result<Self> {\n        Self::instance_get_active(default_instance()?)\n    }\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    async fn get_active_async() -> crate::Result<Self> {\n        Self::instance_get_active_async(default_instance()?).await\n    }\n    fn instance_get_active(instance: &Instance) -> crate::Result<Self> {\n        let data = instance.write_to_socket(command!(JSON, \"{}\", DataCommands::ActiveWorkspace))?;\n        let deserialized: Workspace = serde_json::from_str(&data)?;\n        Ok(deserialized)\n    }\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    async fn instance_get_active_async(instance: &Instance) -> crate::Result<Self> {\n        let data = instance\n            .write_to_socket_async(command!(JSON, \"{}\", DataCommands::ActiveWorkspace))\n            .await?;\n        let deserialized: Workspace = serde_json::from_str(&data)?;\n        Ok(deserialized)\n    }\n}\n\ncreate_data_struct!(\n    vector,\n    name: Workspaces,\n    command: DataCommands::Workspaces,\n    holding_type: Workspace,\n    doc: \"This type provides a vector of workspaces\"\n);\n\n/// This struct holds information for a client/window fullscreen mode\n#[derive(Serialize_repr, Deserialize_repr, Debug, Clone, PartialEq, Eq, Copy)]\n#[repr(u8)]\npub enum FullscreenMode {\n    /// Normal window\n    None = 0,\n    /// Maximized window\n    Maximized = 1,\n    /// Fullscreen window\n    Fullscreen = 2,\n    /// Maximized and fullscreen window\n    MaximizedFullscreen = 3,\n}\n\n/// This struct holds information for a client/window\n#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]\npub struct Client {\n    /// The client's [`Address`][crate::shared::Address]\n    pub address: Address,\n    /// The window location\n    pub at: (i16, i16),\n    /// The window size\n    pub size: (i16, i16),\n    /// The workspace its on\n    pub workspace: WorkspaceBasic,\n    /// Is this window floating?\n    pub floating: bool,\n    /// The internal fullscreen mode\n    pub fullscreen: FullscreenMode,\n    /// The client fullscreen mode\n    #[serde(rename = \"fullscreenClient\")]\n    pub fullscreen_client: FullscreenMode,\n    /// The monitor id the window is on, can be None in some cases\n    pub monitor: Option<MonitorId>,\n    /// The initial window class\n    #[serde(rename = \"initialClass\")]\n    pub initial_class: String,\n    /// The window class\n    pub class: String,\n    /// The initial window title\n    #[serde(rename = \"initialTitle\")]\n    pub initial_title: String,\n    /// The window title\n    pub title: String,\n    /// The process Id of the client\n    pub pid: i32,\n    /// Is this window running under XWayland?\n    pub xwayland: bool,\n    /// Is this window pinned?\n    pub pinned: bool,\n    /// Group members\n    pub grouped: Vec<Box<Address>>,\n    /// Is this window print on screen\n    pub mapped: bool,\n    /// The swallowed window\n    pub swallowing: Option<Box<Address>>,\n    /// When was this window last focused relatively to other windows? 0 for current, 1 previous, 2 previous before that, etc\n    #[serde(rename = \"focusHistoryID\")]\n    pub focus_history_id: i8,\n}\n\n#[derive(Deserialize, Debug)]\n#[serde(deny_unknown_fields)]\nstruct Empty {}\n\nimpl HyprDataActiveOptional for Client {\n    fn get_active() -> crate::Result<Option<Self>> {\n        Self::instance_get_active(default_instance()?)\n    }\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    async fn get_active_async() -> crate::Result<Option<Self>> {\n        Self::instance_get_active_async(default_instance()?).await\n    }\n    fn instance_get_active(instance: &Instance) -> crate::Result<Option<Self>> {\n        let data = instance.write_to_socket(command!(JSON, \"{}\", DataCommands::ActiveWindow))?;\n        let res = serde_json::from_str::<Empty>(&data);\n        if res.is_err() {\n            let t = serde_json::from_str::<Client>(&data)?;\n            Ok(Some(t))\n        } else {\n            Ok(None)\n        }\n    }\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    async fn instance_get_active_async(instance: &Instance) -> crate::Result<Option<Self>> {\n        let data = instance\n            .write_to_socket_async(command!(JSON, \"{}\", DataCommands::ActiveWindow))\n            .await?;\n        let res = serde_json::from_str::<Empty>(&data);\n        if res.is_err() {\n            let t = serde_json::from_str::<Client>(&data)?;\n            Ok(Some(t))\n        } else {\n            Ok(None)\n        }\n    }\n}\n\ncreate_data_struct!(\n    vector,\n    name: Clients,\n    command: DataCommands::Clients,\n    holding_type: Client,\n    doc: \"This struct holds a vector of clients\"\n);\n\n/// This struct holds information about a layer surface/client\n#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]\npub struct LayerClient {\n    /// The layer's [`Address`][crate::shared::Address]\n    pub address: Address,\n    /// The layer's x position\n    pub x: i32,\n    /// The layer's y position\n    pub y: i32,\n    /// The layer's width\n    pub w: i16,\n    /// The layer's height\n    pub h: i16,\n    /// The layer's namespace\n    pub namespace: String,\n}\n\n/// This struct holds all the layer surfaces for a display\n#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]\npub struct LayerDisplay {\n    /// The different levels of layers\n    pub levels: HashMap<String, Vec<LayerClient>>,\n}\n\nimplement_iterators!(\n    table,\n    name: LayerDisplay,\n    iterated_field: levels,\n    key: String,\n    value: Vec<LayerClient>,\n);\n\ncreate_data_struct!(\n    table,\n    name: Layers,\n    command: DataCommands::Layers,\n    key: String,\n    value: LayerDisplay,\n    doc: \"This struct holds a hashmap of all current displays, and their layer surfaces\"\n);\n\n/// This struct holds information about a mouse device\n#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]\npub struct Mouse {\n    /// The mouse's address\n    pub address: Address,\n    /// The mouse's name\n    pub name: String,\n}\n\n/// This struct holds information about a keyboard device\n#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]\npub struct Keyboard {\n    /// The keyboard's address\n    pub address: Address,\n    /// The keyboard's name\n    pub name: String,\n    /// The keyboard rules\n    pub rules: String,\n    /// The keyboard model\n    pub model: String,\n    /// The layout of the keyboard\n    pub layout: String,\n    /// The keyboard variant\n    pub variant: String,\n    /// The keyboard options\n    pub options: String,\n    /// The keyboard's active keymap\n    pub active_keymap: String,\n    /// The keyboard's primary status\n    pub main: bool,\n}\n\n/// A enum that holds the types of tablets\n#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]\npub enum TabletType {\n    /// The TabletPad type of tablet\n    #[serde(rename = \"tabletPad\")]\n    TabletPad,\n    /// The TabletTool type of tablet\n    #[serde(rename = \"tabletTool\")]\n    TabletTool,\n}\n\n/// A enum to match what the tablet belongs to\n#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]\n#[serde(untagged)]\npub enum TabletBelongsTo {\n    /// The belongsTo data if the tablet is of type TabletPad\n    TabletPad {\n        /// The name of the parent\n        name: String,\n        /// The address of the parent\n        address: Address,\n    },\n    /// The belongsTo data if the tablet is of type TabletTool\n    Address(Address),\n}\n\n/// This struct holds information about a tablet device\n#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]\npub struct Tablet {\n    /// The tablet's address\n    pub address: Address,\n    /// The tablet type\n    #[serde(rename = \"type\")]\n    pub tablet_type: Option<TabletType>,\n    /// What the tablet belongs to\n    #[serde(rename = \"belongsTo\")]\n    pub belongs_to: Option<TabletBelongsTo>,\n    /// The name of the tablet\n    pub name: Option<String>,\n}\n\n/// This struct holds all current devices\n#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]\npub struct Devices {\n    /// All the mice\n    pub mice: Vec<Mouse>,\n    /// All the keyboards\n    pub keyboards: Vec<Keyboard>,\n    /// All the tablets\n    pub tablets: Vec<Tablet>,\n}\nimpl_on!(Devices);\n\n/// This struct holds version information\n#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]\npub struct Version {\n    /// The git branch Hyprland was built on\n    pub branch: String,\n    /// The git commit Hyprland was built on\n    pub commit: String,\n    #[serde(default)]\n    /// The Hyprland version\n    pub version: Option<String>,\n    /// This is true if there were unstaged changed when Hyprland was built\n    pub dirty: bool,\n    /// The git commit message\n    pub commit_message: String,\n    /// The git commit date\n    pub commit_date: String,\n    /// The git tag hyprland was built on\n    pub tag: String,\n    /// The amount of commits to Hyprland at buildtime\n    pub commits: String,\n    /// Aquamarine version\n    #[serde(rename = \"buildAquamarine\")]\n    pub build_aquamarine: String,\n    /// Hyprland version\n    #[serde(rename = \"buildHyprlang\")]\n    pub build_hyprlang: String,\n    /// Hyprutils version\n    #[serde(rename = \"buildHyprutils\")]\n    pub build_hyprutils: String,\n    /// Hyprcursor version\n    #[serde(rename = \"buildHyprcursor\")]\n    pub build_hyprcursor: String,\n    /// Hyprgraphics version\n    #[serde(rename = \"buildHyprgraphics\")]\n    pub build_hyprgraphics: String,\n    /// System aquamarine version\n    #[serde(rename = \"systemAquamarine\")]\n    pub system_aquamarine: String,\n    /// System hyprlang version\n    #[serde(rename = \"systemHyprlang\")]\n    pub system_hyprlang: String,\n    /// System hyprutils version\n    #[serde(rename = \"systemHyprutils\")]\n    pub system_hyprutils: String,\n    /// System hyprcursor version\n    #[serde(rename = \"systemHyprcursor\")]\n    pub system_hyprcursor: String,\n    /// System hyprgraphics version\n    #[serde(rename = \"systemHyprgraphics\")]\n    pub system_hyprgraphics: String,\n    /// The flags that Hyprland was built with\n    pub flags: Vec<String>,\n}\nimpl_on!(Version);\n\n/// This struct holds information on the cursor position\n#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]\npub struct CursorPosition {\n    /// The x position of the cursor\n    pub x: i64,\n    /// The y position of the cursor\n    pub y: i64,\n}\nimpl_on!(CursorPosition);\n\n/// A keybinding returned from the binds command\n#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]\npub struct Bind {\n    /// Is it locked?\n    pub locked: bool,\n    /// Is it a mouse bind?\n    pub mouse: bool,\n    /// Does it execute on release?\n    pub release: bool,\n    /// Can it be held?\n    pub repeat: bool,\n    /// It's modmask\n    pub modmask: u16,\n    /// The submap its apart of\n    pub submap: String,\n    /// The key\n    pub key: String,\n    /// The keycode\n    pub keycode: i16,\n    /// The dispatcher to be executed\n    pub dispatcher: String,\n    /// The dispatcher arg\n    pub arg: String,\n}\n\ncreate_data_struct!(\n    vector,\n    name: Binds,\n    command: DataCommands::Binds,\n    holding_type: Bind,\n    doc: \"This struct holds a vector of binds\"\n);\n\n/// Animation styles\n#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]\npub enum AnimationStyle {\n    /// Slide animation\n    Slide,\n    /// Vertical slide animation\n    SlideVert,\n    /// Fading slide animation\n    SlideFade,\n    /// Fading slide animation in a vertical direction\n    SlideFadeVert,\n    /// Popin animation (with percentage)\n    PopIn(u8),\n    /// Fade animation\n    Fade,\n    /// Once animation used for gradient animation\n    Once,\n    /// Loop animation used for gradient animation\n    Loop,\n    /// No animation style\n    None,\n    /// Unknown style\n    Unknown(String),\n}\n\nimpl From<String> for AnimationStyle {\n    fn from(value: String) -> Self {\n        if value.starts_with(\"popin\") {\n            let mut iter = value.split(' ');\n            iter.next();\n            AnimationStyle::PopIn({\n                let mut str = iter.next().unwrap_or(\"100%\").to_string();\n                str.remove(str.len() - 1);\n\n                str.parse().unwrap_or(100_u8)\n            })\n        } else {\n            match value.as_str() {\n                \"slide\" => AnimationStyle::Slide,\n                \"slidevert\" => AnimationStyle::SlideVert,\n                \"fade\" => AnimationStyle::Fade,\n                \"slidefade\" => AnimationStyle::SlideFade,\n                \"slidefadevert\" => AnimationStyle::SlideFadeVert,\n                \"once\" => AnimationStyle::Once,\n                \"loop\" => AnimationStyle::Loop,\n                \"\" => AnimationStyle::None,\n                _ => AnimationStyle::Unknown(value),\n            }\n        }\n    }\n}\n/// Bezier identifier\n#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]\npub enum BezierIdent {\n    /// No bezier specified\n    #[serde(rename = \"\")]\n    None,\n    /// The default bezier\n    #[serde(rename = \"default\")]\n    Default,\n    /// A specified bezier\n    #[serde(rename = \"name\")]\n    Specified(String),\n}\n\nimpl From<String> for BezierIdent {\n    fn from(value: String) -> Self {\n        match value.as_str() {\n            \"\" => BezierIdent::None,\n            \"default\" => BezierIdent::Default,\n            _ => BezierIdent::Specified(value),\n        }\n    }\n}\n\n#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]\nstruct RawBezierIdent {\n    pub name: String,\n}\n\n/// A bezier curve\n#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]\npub struct Bezier {\n    ///. Name of the bezier\n    pub name: String,\n    /// X position of first point\n    pub x0: f32,\n    /// Y position of first point\n    pub y0: f32,\n    /// X position of second point\n    pub x1: f32,\n    /// Y position of second point\n    pub y1: f32,\n}\n\n/// A struct representing a animation\n#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]\nstruct AnimationRaw {\n    /// The name of the animation\n    pub name: String,\n    /// Is it overridden?\n    pub overridden: bool,\n    /// What bezier does it use?\n    pub bezier: String,\n    /// Is it enabled?\n    pub enabled: bool,\n    /// How fast is it?\n    pub speed: f32,\n    /// The style of animation\n    pub style: String,\n}\n\n/// A struct representing a animation\n#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]\npub struct Animation {\n    /// The name of the animation\n    pub name: String,\n    /// Is it overridden?\n    pub overridden: bool,\n    /// What bezier does it use?\n    pub bezier: BezierIdent,\n    /// Is it enabled?\n    pub enabled: bool,\n    /// How fast is it?\n    pub speed: f32,\n    /// The style of animation\n    pub style: AnimationStyle,\n}\n\n#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]\nstruct AnimationsRaw(Vec<AnimationRaw>, Vec<RawBezierIdent>);\n\n/// Struct that holds animations and beziers\n#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]\npub struct Animations(pub Vec<Animation>, pub Vec<BezierIdent>);\n\nimpl HyprData for Animations {\n    fn get() -> crate::Result<Self> {\n        Self::instance_get(default_instance()?)\n    }\n\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    async fn get_async() -> crate::Result<Self> {\n        Self::instance_get_async(default_instance()?).await\n    }\n\n    fn instance_get(instance: &Instance) -> crate::Result<Self> {\n        let out = instance.write_to_socket(command!(JSON, \"{}\", DataCommands::Animations))?;\n        let des: AnimationsRaw = serde_json::from_str(&out)?;\n        let AnimationsRaw(anims, beziers) = des;\n        let new_anims: Vec<Animation> = anims\n            .into_iter()\n            .map(|item| Animation {\n                name: item.name,\n                overridden: item.overridden,\n                bezier: item.bezier.into(),\n                enabled: item.enabled,\n                speed: item.speed,\n                style: item.style.into(),\n            })\n            .collect();\n        let new_bezs: Vec<BezierIdent> = beziers.into_iter().map(|item| item.name.into()).collect();\n        Ok(Animations(new_anims, new_bezs))\n    }\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    async fn instance_get_async(instance: &Instance) -> crate::Result<Self> {\n        let out = instance\n            .write_to_socket_async(command!(JSON, \"{}\", DataCommands::Animations))\n            .await?;\n        let des: AnimationsRaw = serde_json::from_str(&out)?;\n        let AnimationsRaw(anims, beziers) = des;\n        let new_anims: Vec<Animation> = anims\n            .into_iter()\n            .map(|item| Animation {\n                name: item.name,\n                overridden: item.overridden,\n                bezier: item.bezier.into(),\n                enabled: item.enabled,\n                speed: item.speed,\n                style: item.style.into(),\n            })\n            .collect();\n        let new_bezs: Vec<BezierIdent> = beziers.into_iter().map(|item| item.name.into()).collect();\n        Ok(Animations(new_anims, new_bezs))\n    }\n}\n\n// HACK: shadow and decorate are actually missing from the hyprctl json output for some reason\n// HACK: gaps_in and gaps_out are returned as arrays with 4 integers, even though Hyprland doesn't support per-side gaps\n/// The rules of an individual workspace, as returned by hyprctl json.\n#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]\npub struct WorkspaceRuleset {\n    /// The name of the workspace\n    #[serde(rename = \"workspaceString\")]\n    pub workspace_string: String,\n    /// The monitor the workspace is on\n    pub monitor: Option<String>,\n    /// Is it default?\n    pub default: Option<bool>,\n    /// The gaps between windows\n    #[serde(rename = \"gapsIn\")]\n    pub gaps_in: Option<Vec<i64>>,\n    /// The gaps between windows and monitor edges\n    #[serde(rename = \"gapsOut\")]\n    pub gaps_out: Option<Vec<i64>>,\n    /// The size of window borders\n    #[serde(rename = \"borderSize\")]\n    pub border_size: Option<i64>,\n    /// Are borders enabled?\n    pub border: Option<bool>,\n    /// Are shadows enabled?\n    pub shadow: Option<bool>,\n    /// Is rounding enabled?\n    pub rounding: Option<bool>,\n    /// Are window decorations enabled?\n    pub decorate: Option<bool>,\n    /// Is it persistent?\n    pub persistent: Option<bool>,\n}\n\ncreate_data_struct!(\n    vector,\n    name: WorkspaceRules,\n    command: DataCommands::WorkspaceRules,\n    holding_type: WorkspaceRuleset,\n    doc: \"This struct holds a vector of workspace rules per workspace\"\n);\n"
  },
  {
    "path": "dep-crates/hyprland-rs/src/dispatch.rs",
    "content": "//! # Dispatch module\n//!\n//! This module is used for calling dispatchers and changing keywords\n//!\n//! ## Usage\n//!\n//! ```rust\n//! use hyprland::Result;\n//! use hyprland::dispatch::{Dispatch, DispatchType};\n//! fn main() -> Result<()> {\n//!     Dispatch::call(DispatchType::Exec(\"kitty\"))?;\n//!\n//!    Ok(())\n//! }\n//! ````\n\nuse crate::default_instance;\nuse crate::dispatch::fmt::*;\nuse crate::error::HyprError;\nuse crate::shared::*;\nuse derive_more::Display;\nuse std::string::ToString;\n\n/// This enum is for identifying a window\n#[derive(Debug, Clone, Display)]\npub enum WindowIdentifier<'a> {\n    /// The address of a window\n    #[display(\"address:{_0}\")]\n    Address(Address),\n    /// A Regular Expression to match the window class (handled by Hyprland)\n    #[display(\"class:{_0}\")]\n    ClassRegularExpression(&'a str),\n    /// The window title\n    #[display(\"title:{_0}\")]\n    Title(&'a str),\n    /// The window's process Id\n    #[display(\"pid:{_0}\")]\n    ProcessId(u32),\n}\n\n/// This enum holds the fullscreen types\n#[derive(Debug, Clone, Display)]\npub enum FullscreenType {\n    /// Fills the whole screen\n    #[display(\"0\")]\n    Real,\n    /// Maximizes the window\n    #[display(\"1\")]\n    Maximize,\n    /// Passes no param\n    #[display(\"\")]\n    NoParam,\n}\n\n/// This enum holds directions, typically used for moving\n#[derive(Debug, Clone, Display)]\n#[allow(missing_docs)]\npub enum Direction {\n    #[display(\"u\")]\n    Up,\n    #[display(\"d\")]\n    Down,\n    #[display(\"r\")]\n    Right,\n    #[display(\"l\")]\n    Left,\n}\n\n/// This enum is used for resizing and moving windows precisely\n#[derive(Debug, Clone)]\npub enum Position {\n    /// A delta\n    Delta(i16, i16),\n    /// The exact size\n    Exact(i16, i16),\n}\n\nimpl std::fmt::Display for Position {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        let out = match self {\n            Position::Delta(x, y) => format!(\"{x} {y}\"),\n            Position::Exact(w, h) => format!(\"exact {w} {h}\"),\n        };\n        write!(f, \"{out}\")\n    }\n}\n\n/// This enum holds a direction for cycling\n#[allow(missing_docs)]\n#[derive(Debug, Clone, Display)]\npub enum CycleDirection {\n    #[display(\"\")]\n    Next,\n    #[display(\"prev\")]\n    Previous,\n}\n\n/// This enum holds a direction for switch windows in a group\n#[allow(missing_docs)]\n#[derive(Debug, Clone, Display)]\npub enum WindowSwitchDirection {\n    #[display(\"b\")]\n    Back,\n    #[display(\"f\")]\n    Forward,\n}\n\n/// This enum is used for identifying monitors\n#[derive(Debug, Clone)]\npub enum MonitorIdentifier<'a> {\n    /// The monitor that is to the specified direction of the active one\n    Direction(Direction),\n    /// The monitor id\n    Id(MonitorId),\n    /// The monitor name\n    Name(&'a str),\n    /// The current monitor\n    Current,\n    /// The workspace relative to the current workspace\n    Relative(i32),\n}\n\nimpl std::fmt::Display for MonitorIdentifier<'_> {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        let out = match self {\n            MonitorIdentifier::Direction(dir) => dir.to_string(),\n            MonitorIdentifier::Id(id) => id.to_string(),\n            MonitorIdentifier::Name(name) => name.to_string(),\n            MonitorIdentifier::Current => \"current\".to_string(),\n            MonitorIdentifier::Relative(int) => format_relative(*int, \"\"),\n        };\n        write!(f, \"{out}\")\n    }\n}\n\n/// This enum holds corners\n#[allow(missing_docs)]\n#[derive(Debug, Clone)]\npub enum Corner {\n    BottomLeft = 0,\n    BottomRight = 1,\n    TopRight = 2,\n    TopLeft = 3,\n}\n\n/// This enum holds options that are applied to the current workspace\n#[derive(Debug, Clone, Display)]\npub enum WorkspaceOptions {\n    /// Makes all windows pseudo tiled\n    #[display(\"allfloat\")]\n    AllPseudo,\n    /// Makes all windows float\n    #[display(\"allpseudo\")]\n    AllFloat,\n}\n\n/// This enum holds a direction for cycling\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub struct FirstEmpty {\n    /// If the first empty workspace should be on the monitor\n    pub on_monitor: bool,\n    /// If the first empty workspace should be next\n    pub next: bool,\n}\n\nimpl std::fmt::Display for FirstEmpty {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        let mut s = String::new();\n        if self.on_monitor {\n            s.push('m');\n        }\n        if self.next {\n            s.push('n');\n        }\n        write!(f, \"{s}\")\n    }\n}\n\n/// This enum is for identifying workspaces that also includes the special workspace\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Display)]\npub enum WorkspaceIdentifierWithSpecial<'a> {\n    /// The workspace Id\n    Id(WorkspaceId),\n    /// The workspace relative to the current workspace\n    #[display(\"{}\", format_relative(*_0, \"\"))]\n    Relative(i32),\n    /// The workspace on the monitor relative to the current workspace\n    #[display(\"{}\", format_relative(*_0, \"m\"))]\n    RelativeMonitor(i32),\n    /// The workspace on the monitor relative to the current workspace, including empty workspaces\n    #[display(\"{}\", format_relative(*_0, \"r\"))]\n    RelativeMonitorIncludingEmpty(i32),\n    /// The open workspace relative to the current workspace\n    #[display(\"{}\", format_relative(*_0, \"e\"))]\n    RelativeOpen(i32),\n    /// The previous Workspace\n    #[display(\"previous\")]\n    Previous,\n    /// The previous Workspace\n    #[display(\"previous_per_monitor\")]\n    PreviousPerMonitor,\n    /// The first available empty workspace\n    #[display(\"{}\", format!(\"empty{}\", _0))]\n    Empty(FirstEmpty),\n    /// The name of the workspace\n    #[display(\"name:{_0}\")]\n    Name(&'a str),\n    /// The special workspace\n    #[display(\"special{}\", format_special_workspace_ident(_0))]\n    Special(Option<&'a str>),\n}\n\npub(super) mod fmt {\n    #[inline(always)]\n    pub(super) fn format_special_workspace_ident<'a>(opt: &'a Option<&'a str>) -> String {\n        match opt {\n            Some(o) => \":\".to_owned() + o,\n            None => String::new(),\n        }\n    }\n\n    #[inline(always)]\n    pub(super) fn format_relative(int: i32, extra: &'_ str) -> String {\n        if int.is_positive() {\n            format!(\"{extra}+{int}\")\n        } else if int.is_negative() {\n            format!(\"{extra}-{}\", int.abs())\n        } else {\n            \"+0\".to_owned()\n        }\n    }\n}\n\n/// This enum is for identifying workspaces\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum WorkspaceIdentifier<'a> {\n    /// The workspace Id\n    Id(WorkspaceId),\n    /// The workspace relative to the current workspace\n    Relative(i32),\n    /// The workspace on the monitor relative to the current workspace\n    RelativeMonitor(i32),\n    /// The workspace on the monitor relative to the current workspace, including empty workspaces\n    RelativeMonitorIncludingEmpty(i32),\n    /// The open workspace relative to the current workspace\n    RelativeOpen(i32),\n    /// The previous Workspace\n    Previous,\n    /// The first available empty workspace\n    Empty,\n    /// The name of the workspace\n    Name(&'a str),\n}\n\nimpl std::fmt::Display for WorkspaceIdentifier<'_> {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        use WorkspaceIdentifier::*;\n        let out = match self {\n            Id(id) => format!(\"{id}\"),\n            Name(name) => format!(\"name:{name}\"),\n            Relative(int) => format_relative(*int, \"\"),\n            RelativeMonitor(int) => format_relative(*int, \"m\"),\n            RelativeMonitorIncludingEmpty(int) => format_relative(*int, \"r\"),\n            RelativeOpen(int) => format_relative(*int, \"e\"),\n            Previous => \"previous\".to_string(),\n            Empty => \"empty\".to_string(),\n        };\n\n        write!(f, \"{out}\")\n    }\n}\n\n/// This enum is the params to [DispatchType::MoveWindow] dispatcher\n#[derive(Debug, Clone)]\npub enum WindowMove<'a> {\n    /// Moves the window to a specified monitor\n    Monitor(MonitorIdentifier<'a>),\n    /// Moves the window in a specified direction\n    Direction(Direction),\n}\n\n/// This enum holds every dispatcher\n#[derive(Debug, Clone)]\npub enum DispatchType<'a> {\n    /// This lets you use dispatchers not supported by hyprland-rs yet, please make issues before\n    /// using\n    Custom(\n        /// Name of event\n        &'a str,\n        /// Args\n        &'a str,\n    ),\n    /// This dispatcher changes the current cursor\n    SetCursor(\n        /// The cursor theme\n        &'a str,\n        /// The size\n        u16,\n    ),\n    /// This dispatcher executes a program\n    Exec(&'a str),\n    /// This dispatcher passes a keybind to a window when called in a\n    /// keybind, its used for global keybinds. And should **ONLY** be used with keybinds\n    Pass(WindowIdentifier<'a>),\n    /// Executes a Global Shortcut using the GlobalShortcuts portal.\n    Global(&'a str),\n    /// This dispatcher kills the active window/client\n    KillActiveWindow,\n    /// This dispatcher closes the specified window\n    CloseWindow(WindowIdentifier<'a>),\n    /// This dispatcher changes the current workspace\n    Workspace(WorkspaceIdentifierWithSpecial<'a>),\n    /// This dispatcher moves a window (focused if not specified) to a workspace\n    MoveToWorkspace(\n        WorkspaceIdentifierWithSpecial<'a>,\n        Option<WindowIdentifier<'a>>,\n    ),\n    /// This dispatcher moves a window (focused if not specified) to a workspace, without switching to that\n    /// workspace\n    MoveToWorkspaceSilent(\n        WorkspaceIdentifierWithSpecial<'a>,\n        Option<WindowIdentifier<'a>>,\n    ),\n    /// This dispatcher floats a window (current if not specified)\n    ToggleFloating(Option<WindowIdentifier<'a>>),\n    /// This dispatcher toggles the current window fullscreen state\n    ToggleFullscreen(FullscreenType),\n    /// This dispatcher toggles the focused window’s internal\n    /// fullscreen state without altering the geometry\n    ToggleFakeFullscreen,\n    /// This dispatcher sets the DPMS status for all monitors\n    ToggleDPMS(bool, Option<&'a str>),\n    /// This dispatcher toggles pseudo tiling for the current window\n    TogglePseudo,\n    /// This dispatcher pins the active window to all workspaces\n    TogglePin,\n    /// This dispatcher moves the window focus in a specified direction\n    MoveFocus(Direction),\n    /// This dispatcher moves the current window to a monitor or in a specified direction\n    MoveWindow(WindowMove<'a>),\n    /// This dispatcher centers the active window\n    CenterWindow,\n    /// This dispatcher resizes the active window using a [Position] enum\n    ResizeActive(Position),\n    /// This dispatcher moves the active window using a [Position] enum\n    MoveActive(Position),\n    /// This dispatcher resizes the specified window using a [Position] enum\n    ResizeWindowPixel(Position, WindowIdentifier<'a>),\n    /// This dispatcher moves the specified window using a [Position] enum\n    MoveWindowPixel(Position, WindowIdentifier<'a>),\n    /// This dispatcher cycles windows using a specified direction\n    CycleWindow(CycleDirection),\n    /// This dispatcher swaps the focused window with the window on a workspace using a specified direction\n    SwapNext(CycleDirection),\n    /// This dispatcher swaps windows using a specified direction\n    SwapWindow(Direction),\n    /// This dispatcher focuses a specified window\n    FocusWindow(WindowIdentifier<'a>),\n    /// This dispatcher focuses a specified monitor\n    FocusMonitor(MonitorIdentifier<'a>),\n    /// This dispatcher changed the split ratio\n    ChangeSplitRatio(f32),\n    /// This dispatcher toggle opacity for the current window/client\n    ToggleOpaque,\n    /// This dispatcher moves the cursor to a specified corner of a window\n    MoveCursorToCorner(Corner),\n    /// This dispatcher moves the cursor to a specified position\n    /// (x, y) where x starts from left to right, and y starts from top to bottom\n    MoveCursor(i64, i64),\n    /// This dispatcher applied a option to all windows in a workspace\n    WorkspaceOption(WorkspaceOptions),\n    /// This dispatcher renames a workspace\n    RenameWorkspace(WorkspaceId, Option<&'a str>),\n    /// This exits Hyprland **(DANGEROUS)**\n    Exit,\n    /// This dispatcher forces the renderer to reload\n    ForceRendererReload,\n    /// This dispatcher moves the current workspace to a specified monitor\n    MoveCurrentWorkspaceToMonitor(MonitorIdentifier<'a>),\n    /// This dispatcher moves a specified workspace to a specified monitor\n    MoveWorkspaceToMonitor(WorkspaceIdentifier<'a>, MonitorIdentifier<'a>),\n    /// This dispatcher swaps the active workspaces of two monitors\n    SwapActiveWorkspaces(MonitorIdentifier<'a>, MonitorIdentifier<'a>),\n    /// This dispatcher brings the active window to the top of the stack\n    BringActiveToTop,\n    /// This toggles the special workspace (AKA scratchpad)\n    ToggleSpecialWorkspace(Option<String>),\n    /// This dispatcher jump to urgent or the last window\n    FocusUrgentOrLast,\n    /// Switch focus from current to previously focused window\n    FocusCurrentOrLast,\n\n    // LAYOUT DISPATCHERS\n    // DWINDLE\n    /// Toggles the split (top/side) of the current window. `preserve_split` must be enabled for toggling to work.\n    ToggleSplit,\n\n    // MASTER\n    /// Swaps the current window with master.\n    /// If the current window is the master,\n    /// swaps it with the first child.\n    SwapWithMaster(SwapWithMasterParam),\n    /// Focuses the master window.\n    FocusMaster(FocusMasterParam),\n    /// Adds a master to the master side. That will be the active window,\n    /// if it’s not a master, or the first non-master window.\n    AddMaster,\n    /// Removes a master from the master side. That will be the\n    /// active window, if it’s a master, or the last master window.\n    RemoveMaster,\n    /// Sets the orientation for the current workspace to left\n    /// (master area left, slave windows to the right, vertically stacked)\n    OrientationLeft,\n    /// Sets the orientation for the current workspace to right\n    /// (master area right, slave windows to the left, vertically stacked)\n    OrientationRight,\n    /// Sets the orientation for the current workspace to top\n    /// (master area top, slave windows to the bottom, horizontally stacked)\n    OrientationTop,\n    /// Sets the orientation for the current workspace to bottom\n    /// (master area bottom, slave windows to the top, horizontally stacked)\n    OrientationBottom,\n    /// Sets the orientation for the current workspace to center\n    /// (master area center, slave windows alternate to the left and right, vertically stacked)\n    OrientationCenter,\n    /// Cycle to the next orientation for the current workspace (clockwise)\n    OrientationNext,\n    /// Cycle to the previous orientation for the current workspace (counter-clockwise)\n    OrientationPrev,\n\n    // Group Dispatchers\n    /// Toggles the current active window into a group\n    ToggleGroup,\n    /// Switches to the next window in a group.\n    ChangeGroupActive(WindowSwitchDirection),\n    /// Locks the groups\n    LockGroups(LockType),\n    /// Moves the active window into a group in a specified direction\n    MoveIntoGroup(Direction),\n    /// Moves the active window out of a group.\n    MoveOutOfGroup,\n}\n\n/// Enum used with [DispatchType::LockGroups], to determine how to lock/unlock\n#[derive(Debug, Clone, Copy, Display, PartialEq, Eq, PartialOrd, Ord)]\npub enum LockType {\n    /// Lock Group\n    #[display(\"lock\")]\n    Lock,\n    /// Unlock Group\n    #[display(\"unlock\")]\n    Unlock,\n    /// Toggle lock state of Group\n    #[display(\"toggle\")]\n    ToggleLock,\n}\n\n/// Param for [DispatchType::SwapWithMaster] dispatcher\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Display)]\npub enum SwapWithMasterParam {\n    /// New focus is the new master window\n    #[display(\"master\")]\n    Master,\n    /// New focus is the new child\n    #[display(\"child\")]\n    Child,\n    /// Keep the focus of the previously focused window\n    #[display(\"auto\")]\n    Auto,\n}\n\n/// Param for [DispatchType::FocusMaster] dispatcher\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Display)]\npub enum FocusMasterParam {\n    /// Focus stays at master, (even if it was selected before)\n    #[display(\"master\")]\n    Master,\n    /// If the current window is the master, focuses the first child\n    #[display(\"auto\")]\n    Auto,\n}\n\npub(crate) fn gen_dispatch_str(cmd: DispatchType, dispatch: bool) -> crate::Result<CommandContent> {\n    use DispatchType::*;\n    let sep = if dispatch { \" \" } else { \",\" };\n    let string_to_pass = match &cmd {\n        Custom(name, args) => format!(\"{name}{sep}{args}\"),\n        Exec(sh) => format!(\"exec{sep}{sh}\"),\n        Pass(win) => format!(\"pass{sep}{win}\"),\n        Global(name) => format!(\"global{sep}{name}\"),\n        KillActiveWindow => \"killactive\".to_string(),\n        CloseWindow(win) => format!(\"closewindow{sep}{win}\"),\n        Workspace(work) => format!(\"workspace{sep}{work}\"),\n        MoveToWorkspace(work, Some(win)) => format!(\"movetoworkspace{sep}{work},{win}\"),\n        MoveToWorkspace(work, None) => format!(\"movetoworkspace{sep}{work}\"),\n        MoveToWorkspaceSilent(work, Some(win)) => format!(\"movetoworkspacesilent{sep}{work},{win}\"),\n        MoveToWorkspaceSilent(work, None) => format!(\"movetoworkspacesilent{sep}{work}\"),\n        ToggleFloating(Some(v)) => format!(\"togglefloating{sep}{v}\"),\n        ToggleFloating(None) => \"togglefloating\".to_string(),\n        ToggleFullscreen(ftype) => format!(\"fullscreen{sep}{ftype}\"),\n        ToggleFakeFullscreen => \"fakefullscreen\".to_string(),\n        ToggleDPMS(stat, mon) => {\n            format!(\n                \"dpms{sep}{} {}\",\n                if *stat { \"on\" } else { \"off\" },\n                mon.unwrap_or_default()\n            )\n        }\n        TogglePseudo => \"pseudo\".to_string(),\n        TogglePin => \"pin\".to_string(),\n        MoveFocus(dir) => format!(\"movefocus{sep}{dir}\",),\n        MoveWindow(ident) => format!(\n            \"movewindow{sep}{}\",\n            match ident {\n                WindowMove::Direction(dir) => dir.to_string(),\n                WindowMove::Monitor(mon) => format!(\"mon:{mon}\"),\n            }\n        ),\n        CenterWindow => \"centerwindow\".to_string(),\n        ResizeActive(pos) => format!(\"resizeactive{sep}{pos}\"),\n        MoveActive(pos) => format!(\"moveactive {pos}\"),\n        ResizeWindowPixel(pos, win) => format!(\"resizewindowpixel{sep}{pos},{win}\"),\n        MoveWindowPixel(pos, win) => format!(\"movewindowpixel{sep}{pos},{win}\"),\n        CycleWindow(dir) => format!(\"cyclenext{sep}{dir}\"),\n        SwapNext(dir) => format!(\"swapnext{sep}{dir}\"),\n        SwapWindow(dir) => format!(\"swapwindow{sep}{dir}\"),\n        FocusWindow(win) => format!(\"focuswindow{sep}{win}\"),\n        FocusMonitor(mon) => format!(\"focusmonitor{sep}{mon}\"),\n        ChangeSplitRatio(ratio) => format!(\"splitratio {ratio}\"),\n        ToggleOpaque => \"toggleopaque\".to_string(),\n        MoveCursorToCorner(corner) => format!(\"movecursortocorner{sep}{}\", corner.clone() as u8),\n        MoveCursor(x, y) => format!(\"movecursor{sep}{x} {y}\"),\n        WorkspaceOption(opt) => format!(\"workspaceopt{sep}{opt}\"),\n        Exit => \"exit\".to_string(),\n        ForceRendererReload => \"forcerendererreload\".to_string(),\n        MoveCurrentWorkspaceToMonitor(mon) => format!(\"movecurrentworkspacetomonitor{sep}{mon}\"),\n        MoveWorkspaceToMonitor(work, mon) => format!(\"moveworkspacetomonitor{sep}{work} {mon}\"),\n        ToggleSpecialWorkspace(Some(name)) => format!(\"togglespecialworkspace {name}\"),\n        ToggleSpecialWorkspace(None) => \"togglespecialworkspace\".to_string(),\n        RenameWorkspace(id, name) => {\n            format!(\n                \"renameworkspace{sep}{id} {}\",\n                name.unwrap_or(&id.to_string())\n            )\n        }\n        SwapActiveWorkspaces(mon, mon2) => format!(\"swapactiveworkspaces{sep}{mon} {mon2}\",),\n        BringActiveToTop => \"bringactivetotop\".to_string(),\n        SetCursor(theme, size) => format!(\"{theme} {}\", *size),\n        FocusUrgentOrLast => \"focusurgentorlast\".to_string(),\n        FocusCurrentOrLast => \"focuscurrentorlast\".to_string(),\n        ToggleSplit => \"togglesplit\".to_string(),\n        SwapWithMaster(param) => format!(\"swapwithmaster{sep}{param}\"),\n        FocusMaster(param) => format!(\"focusmaster{sep}{param}\"),\n        AddMaster => \"addmaster\".to_string(),\n        RemoveMaster => \"removemaster\".to_string(),\n        OrientationLeft => \"orientationleft\".to_string(),\n        OrientationRight => \"orientationright\".to_string(),\n        OrientationTop => \"orientationtop\".to_string(),\n        OrientationBottom => \"orientationbottom\".to_string(),\n        OrientationCenter => \"orientationcenter\".to_string(),\n        OrientationNext => \"orientationnext\".to_string(),\n        OrientationPrev => \"orientationprev\".to_string(),\n        ToggleGroup => \"togglegroup\".to_string(),\n        ChangeGroupActive(dir) => format!(\"changegroupactive{sep}{dir}\"),\n        LockGroups(how) => format!(\"lockgroups{sep}{how}\"),\n        MoveIntoGroup(dir) => format!(\"moveintogroup{sep}{dir}\"),\n        MoveOutOfGroup => \"moveoutofgroup\".to_string(),\n    };\n\n    if let SetCursor(_, _) = cmd {\n        Ok(command!(JSON, \"setcursor {string_to_pass}\"))\n    } else if dispatch {\n        Ok(command!(JSON, \"dispatch {string_to_pass}\"))\n    } else {\n        Ok(command!(Empty, \"{string_to_pass}\"))\n    }\n}\n\n/// The struct that provides all dispatching methods\npub struct Dispatch;\n\nimpl Dispatch {\n    /// This function calls a specified dispatcher (blocking)\n    ///\n    /// ```rust\n    /// # use hyprland::Result;\n    /// # fn main() -> Result<()> {\n    /// use hyprland::dispatch::{DispatchType,Dispatch};\n    /// // This is an example of just one dispatcher, there are many more!\n    /// Dispatch::call(DispatchType::Exec(\"kitty\"))\n    /// # }\n    /// ```\n    pub fn call(dispatch_type: DispatchType) -> crate::Result<()> {\n        Self::instance_call(default_instance()?, dispatch_type)\n    }\n\n    /// This function calls a specified dispatcher (blocking)\n    ///\n    /// ```rust\n    /// # use hyprland::Result;\n    /// # fn main() -> Result<()> {\n    /// use hyprland::dispatch::{DispatchType,Dispatch};\n    /// let instance = hyprland::instance::Instance::from_current_env()?;\n    /// // This is an example of just one dispatcher, there are many more!\n    /// Dispatch::instance_call(&instance, DispatchType::Exec(\"kitty\"))\n    /// # }\n    /// ```\n    pub fn instance_call(\n        instance: &crate::instance::Instance,\n        dispatch_type: DispatchType,\n    ) -> crate::Result<()> {\n        let output = instance.write_to_socket(gen_dispatch_str(dispatch_type, true)?);\n        match output {\n            Ok(msg) => match msg.as_str() {\n                \"ok\" => Ok(()),\n                msg => Err(HyprError::NotOkDispatch(msg.to_string())),\n            },\n            Err(error) => Err(error),\n        }\n    }\n\n    /// This function calls a specified dispatcher (async)\n    ///\n    /// ```rust\n    /// # use hyprland::Result;\n    /// # async fn main() -> Result<()> {\n    /// use hyprland::dispatch::{Dispatch,DispatchType};\n    /// // This is an example of just one dispatcher, there are many more!\n    /// Dispatch::call_async(DispatchType::Exec(\"kitty\")).await\n    /// # }\n    /// ```\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    pub async fn call_async(dispatch_type: DispatchType<'_>) -> crate::Result<()> {\n        Self::instance_call_async(default_instance()?, dispatch_type).await\n    }\n\n    /// This function calls a specified dispatcher (async)\n    ///\n    /// ```rust\n    /// # use hyprland::Result;\n    /// # async fn main() -> Result<()> {\n    /// use hyprland::dispatch::{Dispatch,DispatchType};\n    /// let instance = hyprland::instance::Instance::from_current_env()?;\n    /// // This is an example of just one dispatcher, there are many more!\n    /// Dispatch::instance_call_async(&instance, DispatchType::Exec(\"kitty\")).await\n    /// # }\n    /// ```\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    pub async fn instance_call_async(\n        instance: &crate::instance::Instance,\n        dispatch_type: DispatchType<'_>,\n    ) -> crate::Result<()> {\n        let output = instance\n            .write_to_socket_async(gen_dispatch_str(dispatch_type, true)?)\n            .await;\n        match output {\n            Ok(msg) => match msg.as_str() {\n                \"ok\" => Ok(()),\n                msg => Err(HyprError::NotOkDispatch(msg.to_string())),\n            },\n            Err(error) => Err(error),\n        }\n    }\n}\n\n/// Macro abstraction over [Dispatch::call]\n#[macro_export]\nmacro_rules! dispatch {\n    (async; $instance:expr, $dis:ident, $( $arg:expr ), *) => {\n        $crate::dispatch::Dispatch::instance_call_async($instance, $crate::dispatch::DispatchType::$dis($($arg), *))\n    };\n    (async; $dis:ident, $( $arg:expr ), *) => {\n        $crate::dispatch::Dispatch::call_async($crate::dispatch::DispatchType::$dis($($arg), *))\n    };\n    ($instance:expr, $dis:ident, $( $arg:expr ), *) => {\n        $crate::dispatch::Dispatch::instance_call($instance, $crate::dispatch::DispatchType::$dis($($arg), *))\n    };\n    ($dis:ident, $( $arg:expr ), *) => {\n        $crate::dispatch::Dispatch::call($crate::dispatch::DispatchType::$dis($($arg), *))\n    };\n}\n"
  },
  {
    "path": "dep-crates/hyprland-rs/src/error.rs",
    "content": "#[derive(Debug, derive_more::Display)]\n/// Error that unifies different error types used by Hyprland-rs\npub enum HyprError {\n    /// Error coming from serde\n    SerdeError(serde_json::Error),\n    /// Error coming from std::io\n    IoError(io::Error),\n    /// Error that occurs when parsing UTF-8 string\n    FromUtf8Error(std::string::FromUtf8Error),\n    /// Dispatcher returned non `ok` value\n    #[display(\"A dispatcher returned a non-`ok`, value which is probably an error: {_0}\")]\n    NotOkDispatch(String),\n    /// Error when interacting with Hyprpaper.\n    #[cfg(feature = \"hyprpaper\")]\n    Hyprpaper(crate::hyprpaper::Error),\n    /// Internal Hyprland error\n    Internal(String),\n    /// Error that occurs for other reasons. Avoid using this.\n    Other(String),\n}\nimpl HyprError {\n    /// Try to get an owned version of the internal error.\n    ///\n    /// Some dependencies of hyprland do not impl Clone in their error types. This is a partial workaround.\n    ///\n    /// If it succeeds, it returns the owned version of HyprError in Ok(). Otherwise, it returns a reference to the error type.\n    pub fn try_as_cloned(&self) -> Result<Self, &Self> {\n        match self {\n            Self::SerdeError(_) => Err(self),\n            Self::IoError(_) => Err(self),\n            Self::FromUtf8Error(e) => Ok(Self::FromUtf8Error(e.clone())),\n            Self::NotOkDispatch(s) => Ok(Self::NotOkDispatch(s.clone())),\n            #[cfg(feature = \"hyprpaper\")]\n            Self::Hyprpaper(_) => Err(self),\n            Self::Internal(s) => Ok(Self::Internal(s.clone())),\n            Self::Other(s) => Ok(Self::Other(s.clone())),\n        }\n    }\n    /// Create a Hyprland error with dynamic data.\n    #[inline(always)]\n    pub fn other<S: Into<String>>(other: S) -> Self {\n        Self::Other(other.into())\n    }\n}\n\nimpl From<io::Error> for HyprError {\n    fn from(error: io::Error) -> Self {\n        HyprError::IoError(error)\n    }\n}\n\nimpl From<serde_json::Error> for HyprError {\n    fn from(error: serde_json::Error) -> Self {\n        HyprError::SerdeError(error)\n    }\n}\n\nimpl From<std::string::FromUtf8Error> for HyprError {\n    fn from(error: std::string::FromUtf8Error) -> Self {\n        HyprError::FromUtf8Error(error)\n    }\n}\n\nimpl error::Error for HyprError {}\n\n/// Internal macro to return a Hyprland error\nmacro_rules! hypr_err {\n    ($fmt:literal) => {\n        return Err($crate::error::HyprError::Internal(format!($fmt)))\n    };\n    (other $fmt:literal) => {\n        return Err($crate::error::HyprError::Other(format!($fmt)))\n    };\n    ($fmt:literal $(, $value:expr)+) => {\n        return Err($crate::error::HyprError::Internal(format!($fmt $(, $value)+)))\n    };\n    (other $fmt:literal $(, $value:expr)+) => {\n        return Err($crate::error::HyprError::Other(format!($fmt $(, $value)+)))\n    };\n}\n\npub(crate) use hypr_err;\nuse std::{error, io};\n"
  },
  {
    "path": "dep-crates/hyprland-rs/src/event_listener/async_im.rs",
    "content": "use super::*;\nuse crate::default_instance;\nuse crate::instance::Instance;\n\n/// This struct is used for adding event handlers and executing them on events\n/// # The Event Listener\n///\n/// This struct holds what you need to create a event listener\n///\n/// ## Usage\n///\n/// ```rust, no_run\n/// # use hyprland::event_listener;\n/// # use hyprland_macros::async_closure;\n/// async fn function() -> std::io::Result<()> {\n///     let mut listener = event_listener::AsyncEventListener::new();\n///     listener.add_workspace_changed_handler(async_closure! { |id| println!(\"workspace changed to {id:?}\") });\n///     listener.start_listener_async().await?;\n///     Ok(())\n/// }\n/// ```\npub struct AsyncEventListener {\n    pub(crate) events: AsyncEvents,\n}\n\nimpl Default for AsyncEventListener {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl AsyncEventListener {\n    /// This method creates a new EventListener instance\n    ///\n    /// ```rust\n    /// use hyprland::event_listener;\n    /// let mut listener = event_listener::AsyncEventListener::new();\n    /// ```\n    pub fn new() -> Self {\n        Self {\n            events: create_events_async(),\n        }\n    }\n\n    /// This method starts the event listener (async)\n    ///\n    /// This should be ran after all of your handlers are defined\n    /// ```rust, no_run\n    /// # use hyprland::event_listener;\n    /// # use hyprland_macros::async_closure;\n    /// async fn function() -> std::io::Result<()> {\n    ///     let mut listener = event_listener::AsyncEventListener::new();\n    ///     listener.add_workspace_changed_handler(async_closure! { |id| println!(\"workspace changed to {id:?}\") });\n    ///     listener.start_listener_async().await?;\n    ///     Ok(())\n    /// }\n    /// ```\n    pub async fn start_listener_async(&mut self) -> crate::Result<()> {\n        self.instance_start_listener_async(default_instance()?)\n            .await\n    }\n\n    /// This method starts the event listener (async)\n    ///\n    /// This should be ran after all of your handlers are defined\n    /// ```rust, no_run\n    /// # use hyprland::{default_instance_panic, event_listener};\n    /// # use hyprland_macros::async_closure;\n    /// async fn function() -> std::io::Result<()> {\n    ///     let mut listener = event_listener::AsyncEventListener::new();\n    ///     listener.add_workspace_changed_handler(async_closure! { |id| println!(\"workspace changed to {id:?}\") });\n    ///     let instance = default_instance()?;\n    ///     listener.instance_start_listener_async(instance).await?;\n    ///     Ok(())\n    /// }\n    /// ```\n    pub async fn instance_start_listener_async(\n        &mut self,\n        instance: &Instance,\n    ) -> crate::Result<()> {\n        use crate::async_import::*;\n\n        let mut stream = instance.get_event_stream_async().await?;\n        let mut active_windows = vec![];\n        loop {\n            let mut buffer = [0; 4096];\n            let bytes_read = stream.read(&mut buffer).await?;\n            if bytes_read == 0 {\n                // If no bytes were read, we can assume the stream is closed\n                break;\n            }\n            let buf = &buffer[..bytes_read];\n            let string = String::from_utf8(buf.to_vec())?;\n            let parsed: Vec<Event> = event_parser(string)?;\n            for event in parsed {\n                self.event_primer_exec_async(event, &mut active_windows)\n                    .await?;\n            }\n        }\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "dep-crates/hyprland-rs/src/event_listener/immutable.rs",
    "content": "use super::*;\nuse crate::default_instance;\nuse crate::instance::Instance;\n\n/// This struct is used for adding event handlers and executing them on events\n/// # The Event Listener\n///\n/// This struct holds what you need to create a event listener\n///\n/// ## Usage\n///\n/// ```rust, no_run\n/// # use hyprland::event_listener;\n/// # use hyprland_macros::async_closure;\n/// async fn function() -> std::io::Result<()> {\n///     let mut listener = event_listener::EventListener::new();\n///     listener.add_workspace_changed_handler(|data| println!(\"{:#?}\", data));\n///     listener.start_listener_async().await?;\n///     Ok(())\n/// }\n/// ```\npub struct EventListener {\n    pub(crate) events: Events,\n}\n\nimpl Default for EventListener {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl EventListener {\n    /// This method creates a new EventListener instance\n    ///\n    /// ```rust\n    /// use hyprland::event_listener;\n    /// let mut listener = event_listener::EventListener::new();\n    /// ```\n    pub fn new() -> EventListener {\n        EventListener {\n            events: create_events(),\n        }\n    }\n\n    /// This method starts the event listener (async)\n    ///\n    /// This should be ran after all of your handlers are defined\n    /// ```rust, no_run\n    /// # use hyprland::event_listener;\n    /// # use hyprland_macros::async_closure;\n    /// async fn function() -> std::io::Result<()> {\n    ///     let mut listener = event_listener::EventListener::new();\n    ///     listener.add_workspace_changed_handler(|data| println!(\"{:#?}\", data));\n    ///     listener.start_listener_async().await?;\n    ///     Ok(())\n    /// }\n    /// ```\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    pub async fn start_listener_async(&mut self) -> crate::Result<()> {\n        self.instance_start_listener_async(default_instance()?)\n            .await\n    }\n\n    /// This method starts the event listener (async)\n    ///\n    /// This should be ran after all of your handlers are defined\n    /// ```rust, no_run\n    /// # use hyprland::{default_instance_panic, event_listener};\n    /// # use hyprland_macros::async_closure;\n    /// async fn function() -> std::io::Result<()> {\n    ///     let mut listener = event_listener::EventListener::new();\n    ///     listener.add_workspace_changed_handler(|data| println!(\"{:#?}\", data));\n    ///     let instance = default_instance()?;\n    ///     listener.instance_start_listener_async(instance).await?;\n    ///     Ok(())\n    /// }\n    /// ```\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    pub async fn instance_start_listener_async(\n        &mut self,\n        instance: &Instance,\n    ) -> crate::Result<()> {\n        use crate::async_import::*;\n        let mut stream = instance.get_event_stream_async().await?;\n\n        let mut active_windows = vec![];\n        loop {\n            let mut buffer = [0; 4096];\n            let bytes_read = stream.read(&mut buffer).await?;\n            if bytes_read == 0 {\n                // If no bytes were read, we can assume the stream is closed\n                break;\n            }\n            let buf = &buffer[..bytes_read];\n            let string = String::from_utf8(buf.to_vec())?;\n            let parsed: Vec<Event> = event_parser(string)?;\n            for event in parsed {\n                self.event_primer(event, &mut active_windows)?;\n            }\n        }\n        Ok(())\n    }\n\n    /// This method starts the event listener (blocking)\n    ///\n    /// This should be ran after all of your handlers are defined\n    /// ```rust, no_run\n    /// # use hyprland::{default_instance_panic, event_listener};\n    /// # use hyprland_macros::async_closure;\n    /// async fn function() -> std::io::Result<()> {\n    ///     let mut listener = event_listener::EventListener::new();\n    ///     listener.add_workspace_changed_handler(|data| println!(\"{:#?}\", data));\n    ///     listener.start_listener()?;\n    ///     Ok(())\n    /// }\n    /// ```\n    pub fn start_listener(&mut self) -> crate::Result<()> {\n        self.instance_start_listener(default_instance()?)\n    }\n\n    /// This method starts the event listener (blocking)\n    ///\n    /// This should be ran after all of your handlers are defined\n    /// ```rust, no_run\n    /// # use hyprland::{default_instance_panic, event_listener};\n    /// # use hyprland_macros::async_closure;\n    /// async fn function() -> std::io::Result<()> {\n    ///     let mut listener = event_listener::EventListener::new();\n    ///     listener.add_workspace_changed_handler(|data| println!(\"{:#?}\", data));\n    ///     let instance = default_instance()?;\n    ///     listener.instance_start_listener(instance)?;\n    ///     Ok(())\n    /// }\n    /// ```\n    pub fn instance_start_listener(&mut self, instance: &Instance) -> crate::Result<()> {\n        // use io::prelude::*;\n        use std::io::Read;\n        let mut stream = instance.get_event_stream()?;\n\n        let mut active_windows = vec![];\n        loop {\n            let mut buffer = [0; 4096];\n            let bytes_read = stream.read(&mut buffer)?;\n            if bytes_read == 0 {\n                // If no bytes were read, we can assume the stream is closed\n                break;\n            }\n            let buf = &buffer[..bytes_read];\n            let string = String::from_utf8(buf.to_vec())?;\n            let parsed: Vec<Event> = event_parser(string)?;\n            for event in parsed {\n                self.event_primer(event, &mut active_windows)?;\n            }\n        }\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "dep-crates/hyprland-rs/src/event_listener/macros.rs",
    "content": "macro_rules! events {\n    ($($name:ty => $data:ty,$descr1:literal,$descr2:literal => $id:ident);*) => {\n        paste! {\n            pub(crate) struct Events {\n                $(\n                    pub(crate) [<$name:snake _events>]: type_if! {(),$data,Vec<EmptyClosure>, Closures<$data>}\n                ),*\n            }\n\n            #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n            #[allow(clippy::type_complexity)]\n            pub(crate) struct AsyncEvents {\n                $(\n                    pub(crate) [<$name:snake _events>]: type_if! {(),$data,Vec<EmptyAsyncClosure>, AsyncClosures<$data>}\n                ),*\n            }\n            pub(crate) fn create_events() -> Events {\n                Events {\n                    $([<$name:snake _events>]: vec![]),*\n                }\n            }\n\n            #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n            pub(crate) fn create_events_async() -> AsyncEvents {\n                AsyncEvents {\n                    $([<$name:snake _events>]: vec![]),*\n                }\n            }\n\n            #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n            impl HasAsyncExecutor for AsyncEventListener {\n                async fn event_executor_async(&mut self, event: Event) -> crate::Result<()> {\n                    use Event::*;\n                    match event {\n                        $(\n                            expr_if! {(),$data, $name, $name($id)} => expr_if! {\n                                (),\n                                $data,\n                                arm_async!([<$name:snake _events>], self),\n                                arm_async!($id, [<$name:snake _events>], self)\n                            },\n                        )*\n                        _ => ()\n                    }\n                    Ok(())\n                }\n            }\n            impl HasExecutor for EventListener {\n                fn event_executor(&mut self, event: Event) -> crate::Result<()> {\n                    use Event::*;\n                    match event {\n                        $(\n                            expr_if! {(),$data, $name, $name($id)} => expr_if! {\n                                (),\n                                $data,\n                                arm!([<$name:snake _events>], self),\n                                arm!($id, [<$name:snake _events>], self)\n                            },\n                        )*\n                        _ => ()\n                    }\n                    Ok(())\n                }\n            }\n        }\n        $(\n            paste!{\n                block_if!{\n                    (),\n                    $data,\n                    {\n                        add_listener!{[<$name:snake>],$descr1,$descr2 => $id}\n                    },\n                    {\n                        add_listener!{[<$name:snake>],$data,$descr1,$descr2 => $id}\n                    }\n                }\n            }\n        )*\n    };\n}\n\nmacro_rules! add_listener {\n    ($name:ident,$f:ty,$c:literal,$c2:literal => $id:ident) => {\n        add_listener_reg!($name,$f,$c,$c2 => $id);\n        #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n        add_async_listener!($name,$f,$c,$c2 => $id);\n    };\n    ($name:ident,$c:literal,$c2:literal => $id:ident) => {\n        add_listener_reg!($name,$c,$c2 => $id);\n        #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n        add_async_listener!($name,$c,$c2 => $id);\n    };\n}\n\n#[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\nmacro_rules! add_async_listener {\n    ($name:ident,$f:ty,$c:literal,$c2:expr => $id:ident) => {\n        add_async_listener_raw!($name,$name,impl Fn($f) -> VoidFuture + Send + Sync + 'static,$c,$c2 => $id);\n    };\n    ($name:ident,$c:literal,$c2:expr => $id:ident) => {\n        add_async_listener_raw!($name,$name,impl Fn() -> VoidFuture + Send + Sync + 'static,$c,$c2 => $id);\n    };\n}\nmacro_rules! add_listener_reg {\n    ($name:ident,$f:ty,$c:literal,$c2:expr => $id:ident) => {\n        add_listener_reg_raw!($name,$name,impl Fn($f) + 'static,$c,$c2 => $id);\n    };\n    ($name:ident,$c:literal,$c2:expr => $id:ident) => {\n        add_listener_reg_raw!($name,$name,impl Fn() + 'static,$c,$c2 => $id);\n    };\n}\n\nmacro_rules! add_listener_reg_raw {\n    ($name:ident,$list_name:ident,$f:ty,$c:literal,$c2:expr => $id:ident) => {\n        paste! {\n            impl EventListener {\n                #[doc = concat!(\"This method adds an event which executes when\", stringify!($c), r#\"\n```rust, no_run\nuse hyprland::event_listener::EventListener;\nlet mut listener = EventListener::new();\nlistener.add_\"#, stringify!($name), r#\"_handler(\"#, handler_example_closure! { $f, $c2, $id }, r#\");\nlistener.start_listener();\"#)]\n                pub fn [<add_ $name _handler>](&mut self, f: $f) {\n                    self.events.[<$list_name _events>].push(Box::new(f));\n                }\n            }\n        }\n    };\n}\n\n#[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\nmacro_rules! add_async_listener_raw {\n    ($name:ident,$list_name:ident,$f:ty,$c:literal,$c2:expr => $id:ident) => {\n        paste! {\n            impl AsyncEventListener {\n                #[doc = concat!(\"This method adds an event which executes when \", $c, r#\"\n```rust, no_run\nuse hyprland::event_listener::EventListener;\nlet mut listener = EventListener::new();\nlistener.add_\"#, stringify!($name), r#\"_handler(\"#, handler_example_async_closure! { $f, $c2, $id }, r#\");\nlistener.start_listener();\"#)]\n                pub fn [<add_ $name _handler>](&mut self, f: $f) {\n                    self.events.[<$list_name _events>].push(Box::pin(f));\n                }\n            }\n        }\n    };\n}\n/// Expands to an example closure for documenting event listeners.\nmacro_rules! handler_example_closure {\n    ($f:ty, $c2:expr, $id:ident) => {\n        type_if! {\n            impl Fn() + 'static ,\n            $f,\n            concat!(\n                r#\"|| println!(\"\"#, $c2, r#\"\")\"#,\n            ),\n            concat!(\n                r#\"|\"#, stringify!($id), r#\"| println!(\"\"#, $c2, \": {\", stringify!($id), r#\":#?}\")\"#,\n            )\n        }\n    }\n}\n/// Expands to an example closure for documenting async event listeners.\n#[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\nmacro_rules! handler_example_async_closure {\n    ($f:ty, $c2:expr, $id:ident) => {\n        type_if! {\n            impl Fn() -> VoidFuture + Send + Sync + 'static ,\n            $f,\n            concat!(\n                r#\"|| println!(\"\"#, $c2, r#\"\")\"#,\n            ),\n            concat!(\n                r#\"|\"#, stringify!($id), r#\"| println!(\"\"#, $c2, \": {\", stringify!($id), r#\":#?}\")\"#,\n            )\n        }\n    }\n}\n\nmacro_rules! arm {\n    ($val:expr,$nam:ident,$se:ident) => {{\n        let events = &$se.events.$nam;\n        for item in events.iter() {\n            execute_closure(item, $val.clone());\n        }\n    }};\n    ($nam:ident,$se:ident) => {{\n        let events = &$se.events.$nam;\n        for item in events.iter() {\n            execute_empty_closure(item);\n        }\n    }};\n}\n\n#[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\nmacro_rules! arm_async {\n    ($val:expr,$nam:ident,$se:ident) => {{\n        let events = &$se.events.$nam;\n        for item in events.iter() {\n            execute_closure_async(item, $val.clone()).await;\n        }\n    }};\n    ($nam:ident,$se:ident) => {{\n        let events = &$se.events.$nam;\n        for item in events.iter() {\n            execute_empty_closure_async(item).await;\n        }\n    }};\n}\n"
  },
  {
    "path": "dep-crates/hyprland-rs/src/event_listener/mod.rs",
    "content": "//! # Event Listener Module\n//! for documentation go to:\n//! * [EventStream] for the event listener implementation based on the [futures_lite::Stream] api\n//! * [EventListener] for the normal [Fn] based event listener\n//! * [AsyncEventListener] for the [Fn] based event listener which uses closures that return [std::future::Future]s\n\n#[macro_use]\nmod macros;\n\nmod shared;\npub use crate::event_listener::shared::*;\n\nmod immutable;\npub use crate::event_listener::immutable::EventListener;\n\n#[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\nmod async_im;\n#[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\npub use crate::event_listener::async_im::AsyncEventListener;\n\n#[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\nmod stream;\n#[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\npub use crate::event_listener::stream::EventStream;\nuse crate::shared::Address;\n\n// generates code for the closure based event listeners\nevents! {\n    WorkspaceChanged => WorkspaceEventData, \"on workspace change\", \"changed workspace to\" => id;\n    WorkspaceAdded => WorkspaceEventData, \"a workspace is created\", \"workspace was added\" => id;\n    WorkspaceDeleted => WorkspaceEventData, \"a workspace is destroyed\", \"a workspace was destroyed\" => data;\n    WorkspaceMoved => WorkspaceMovedEventData, \"a workspace is moved\", \"workspace was moved\" => id;\n    WorkspaceRenamed => NonSpecialWorkspaceEventData, \"a workspace is renamed\", \"workspace was renamed\" => id;\n    ActiveMonitorChanged => MonitorEventData, \"the active monitor is changed\", \"Active monitor changed to\" => data;\n    ActiveWindowChanged => Option<WindowEventData>, \"the active window is changed\", \"Active window changed\" => data;\n    FullscreenStateChanged => bool, \"the fullscreen state is changed\", \"Fullscreen is on\" => state;\n    MonitorAdded => MonitorAddedEventData, \"a new monitor is added\", \"Monitor added\" => data;\n    MonitorRemoved => String, \"a monitor is removed\", \"Monitor removed\" => data;\n    WindowOpened => WindowOpenEvent, \"a window is opened\", \"Window opened\" => data;\n    WindowClosed => Address, \"a window is closed\", \"Window closed\" => data;\n    WindowMoved => WindowMoveEvent, \"a window is moved\", \"Window moved\" => data;\n    SpecialRemoved => String, \"a monitor's special workspace is removed\", \"Special Workspace removed\" => monitor;\n    ChangedSpecial => ChangedSpecialEventData, \"a monitor's special workspace is changed\", \"Special Workspace changed\" => data;\n    LayoutChanged => LayoutEvent, \"the keyboard layout is changed\", \"Layout changed\" => data;\n    SubMapChanged => String, \"the submap is changed\", \"Submap changed\" => data;\n    LayerOpened => String, \"a new layer is opened\", \"Layer opened\" => data;\n    LayerClosed => String, \"a layer is closed\", \"Layer closed\" => data;\n    FloatStateChanged => WindowFloatEventData, \"the float state of a window is changed\", \"Float state changed\" => data;\n    UrgentStateChanged => Address, \"the urgent state of a window is changed\", \"urgent state changed\" => data;\n    WindowTitleChanged => WindowTitleEventData, \"a window title is changed\", \"A window title changed\" => data;\n    Screencast => ScreencastEventData, \"the screencast state of a window is changed\", \"screencast state changed\" => data;\n    ConfigReloaded => (), \"the configuration of Hyprland is reloaded\", \"config reloaded\" => _empty;\n    IgnoreGroupLockStateChanged => bool, \"the state of ignore group lock is toggled\", \"ignore group lock toggled to\" => data;\n    LockGroupsStateChanged => bool, \"the state of lock groups is toggled\", \"lock group state toggled to\" => data;\n    WindowPinned => WindowPinEventData, \"the pinned state of a window is changed\", \"window pin was set to\" => state;\n    GroupToggled => GroupToggledEventData, \"a group was toggled\", \"the group toggle state was set to\" => data;\n    WindowMovedIntoGroup => Address, \"a window was moved into a group\", \"a window was moved into a group with the address of\" => addr;\n    WindowMovedOutOfGroup => Address, \"a window was moved out of a group\", \"a window was moved out of a group with the address of\" => addr;\n    Unknown => UnknownEventData, \"the state of some unknown event changed\", \"unknown state changed to\" => value\n}\n"
  },
  {
    "path": "dep-crates/hyprland-rs/src/event_listener/shared.rs",
    "content": "use crate::shared::*;\nuse std::{fmt::Debug, pin::Pin};\n\n#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]\npub(crate) enum ActiveWindowValue<T> {\n    Queued(T), // aka Some(T)\n    None,      // No current window\n    Empty,     // Empty queue\n}\n\nimpl<T> ActiveWindowValue<T> {\n    pub fn reset(&mut self) {\n        *self = Self::Empty;\n    }\n    pub fn is_empty(&self) -> bool {\n        matches!(self, Self::Empty)\n    }\n}\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub(crate) struct ActiveWindowState {\n    pub class: ActiveWindowValue<String>,\n    pub title: ActiveWindowValue<String>,\n    pub addr: ActiveWindowValue<Address>,\n}\n\npub(crate) trait HasExecutor {\n    fn event_executor(&mut self, event: Event) -> crate::Result<()>;\n\n    fn event_primer(&mut self, event: Event, abuf: &mut Vec<ActiveWindowState>) -> crate::Result<()>\n    where\n        Self: Sized,\n    {\n        if abuf.is_empty() {\n            abuf.push(ActiveWindowState::new());\n        }\n        if let Event::ActiveWindowChangedV1(data) = event {\n            let mut to_remove = vec![];\n            let data = into(data);\n            for (index, awin) in abuf.iter_mut().enumerate() {\n                if awin.title.is_empty() && awin.class.is_empty() {\n                    (awin.class, awin.title) = data.clone();\n                }\n                if awin.ready() {\n                    awin.execute(self)?;\n                    to_remove.push(index);\n                    break;\n                }\n            }\n            for index in to_remove.into_iter().rev() {\n                abuf.swap_remove(index);\n            }\n        } else if let Event::ActiveWindowChangedV2(data) = event {\n            let mut to_remove = vec![];\n            for (index, awin) in abuf.iter_mut().enumerate() {\n                if awin.addr.is_empty() {\n                    awin.addr = data.clone().into();\n                }\n                if awin.ready() {\n                    awin.execute(self)?;\n                    to_remove.push(index);\n                    break;\n                }\n            }\n            for index in to_remove.into_iter().rev() {\n                abuf.swap_remove(index);\n            }\n        } else {\n            self.event_executor(event)?;\n        }\n        Ok(())\n    }\n}\n\n#[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\npub(crate) fn event_primer_noexec(\n    event: Event,\n    abuf: &mut Vec<ActiveWindowState>,\n) -> crate::Result<Vec<Event>> {\n    if abuf.is_empty() {\n        abuf.push(ActiveWindowState::new());\n    }\n    let mut events: Vec<Event> = vec![];\n    if let Event::ActiveWindowChangedV1(data) = event {\n        let mut to_remove = vec![];\n        let data = into(data);\n        for (index, awin) in abuf.iter_mut().enumerate() {\n            if awin.title.is_empty() && awin.class.is_empty() {\n                (awin.class, awin.title) = data.clone();\n            }\n            if awin.ready() {\n                if let Some(event) = awin.get_event() {\n                    events.push(event);\n                };\n                to_remove.push(index);\n                break;\n            }\n        }\n        for index in to_remove.into_iter().rev() {\n            abuf.swap_remove(index);\n        }\n    } else if let Event::ActiveWindowChangedV2(data) = event {\n        let mut to_remove = vec![];\n        for (index, awin) in abuf.iter_mut().enumerate() {\n            if awin.addr.is_empty() {\n                awin.addr = data.clone().into();\n            }\n            if awin.ready() {\n                if let Some(event) = awin.get_event() {\n                    events.push(event);\n                };\n                to_remove.push(index);\n                break;\n            }\n        }\n        for index in to_remove.into_iter().rev() {\n            abuf.swap_remove(index);\n        }\n    } else {\n        events.push(event);\n    }\n    Ok(events)\n}\n\n#[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\npub(crate) trait HasAsyncExecutor {\n    async fn event_executor_async(&mut self, event: Event) -> crate::Result<()>;\n\n    async fn event_primer_exec_async(\n        &mut self,\n        event: Event,\n        abuf: &mut Vec<ActiveWindowState>,\n    ) -> crate::Result<()>\n    where\n        Self: Sized,\n    {\n        for x in event_primer_noexec(event, abuf)? {\n            self.event_executor_async(x).await?;\n        }\n        Ok(())\n    }\n}\n\nimpl ActiveWindowState {\n    pub fn execute<T: HasExecutor>(&mut self, listener: &mut T) -> crate::Result<()> {\n        use ActiveWindowValue::{None, Queued};\n        let data = (&self.title, &self.class, &self.addr);\n        if let (Queued(ref title), Queued(ref class), Queued(ref addr)) = data {\n            listener.event_executor(Event::ActiveWindowChanged(Some(WindowEventData {\n                class: class.to_string(),\n                title: title.to_string(),\n                address: addr.clone(),\n            })))?;\n            self.reset();\n        } else if let (None, None, None) = data {\n            listener.event_executor(Event::ActiveWindowChanged(Option::None))?;\n        }\n        Ok(())\n    }\n    pub fn get_event(&mut self) -> Option<Event> {\n        use ActiveWindowValue::{None, Queued};\n        let data = (&self.title, &self.class, &self.addr);\n        let mut event = Option::None;\n        if let (Queued(ref title), Queued(ref class), Queued(ref addr)) = data {\n            event = Some(Event::ActiveWindowChanged(Some(WindowEventData {\n                class: class.to_string(),\n                title: title.to_string(),\n                address: addr.clone(),\n            })));\n            self.reset();\n        } else if let (None, None, None) = data {\n            event = Some(Event::ActiveWindowChanged(Option::None));\n        }\n        event\n    }\n\n    pub fn ready(&self) -> bool {\n        !self.class.is_empty() && !self.title.is_empty() && !self.addr.is_empty()\n    }\n    pub fn reset(&mut self) {\n        self.class.reset();\n        self.title.reset();\n        self.addr.reset();\n    }\n    pub fn new() -> Self {\n        Self {\n            class: ActiveWindowValue::Empty,\n            title: ActiveWindowValue::Empty,\n            addr: ActiveWindowValue::Empty,\n        }\n    }\n}\n\nimpl<T> From<Option<T>> for ActiveWindowValue<T> {\n    fn from(value: Option<T>) -> Self {\n        match value {\n            Some(v) => ActiveWindowValue::Queued(v),\n            None => ActiveWindowValue::None,\n        }\n    }\n}\n\npub(crate) fn into<T>(from: Option<(T, T)>) -> (ActiveWindowValue<T>, ActiveWindowValue<T>) {\n    if let Some((first, second)) = from {\n        (\n            ActiveWindowValue::Queued(first),\n            ActiveWindowValue::Queued(second),\n        )\n    } else {\n        (ActiveWindowValue::None, ActiveWindowValue::None)\n    }\n}\n\npub(crate) type EventType<T> = Box<T>;\npub(crate) type AsyncEventType<T> = Pin<Box<T>>;\n\npub(crate) type VoidFuture = Pin<Box<dyn std::future::Future<Output = ()> + Send>>;\n\npub(crate) type EmptyClosure = EventType<dyn Fn()>;\npub(crate) type Closure<T> = EventType<dyn Fn(T)>;\npub(crate) type AsyncClosure<T> = AsyncEventType<dyn Sync + Send + Fn(T) -> VoidFuture>;\npub(crate) type EmptyAsyncClosure = AsyncEventType<dyn Sync + Send + Fn() -> VoidFuture>;\npub(crate) type Closures<T> = Vec<Closure<T>>;\npub(crate) type AsyncClosures<T> = Vec<AsyncClosure<T>>;\n\n/// Event data for screencast event\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub struct ScreencastEventData {\n    /// State/Is it turning on?\n    pub turning_on: bool,\n    /// Owner type, is it a monitor?\n    pub monitor: bool,\n}\n\n/// The data for the event executed when moving a window to a new workspace\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct WindowMoveEvent {\n    /// Window address\n    pub window_address: Address,\n    /// the workspace id\n    pub workspace_id: WorkspaceId,\n    /// The workspace name\n    pub workspace_name: WorkspaceType,\n}\n\n/// The data for the event executed when opening a new window\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct WindowOpenEvent {\n    /// Window address\n    pub window_address: Address,\n    /// The workspace name\n    pub workspace_name: String,\n    /// Window class\n    pub window_class: String,\n    /// Window title\n    pub window_title: String,\n}\n\n/// The data for the event executed when changing keyboard layouts\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct LayoutEvent {\n    /// Keyboard name\n    pub keyboard_name: String,\n    /// Layout name\n    pub layout_name: String,\n}\n\n/// The mutable state available to Closures\n#[derive(PartialEq, Eq, Clone, Debug)]\npub struct State {\n    /// The active workspace\n    pub active_workspace: WorkspaceType,\n    /// The active monitor\n    pub active_monitor: String,\n    /// The fullscreen state\n    pub fullscreen_state: bool,\n}\n\nimpl State {\n    /// Execute changes in state\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    pub async fn execute_state(self, old: State) -> crate::Result<Self> {\n        self.instance_execute_state(default_instance()?, old).await\n    }\n\n    /// Execute changes in state\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    pub async fn instance_execute_state(\n        self,\n        instance: &Instance,\n        old: State,\n    ) -> crate::Result<Self> {\n        let state = self.clone();\n        if self != old {\n            use crate::dispatch::{Dispatch, DispatchType};\n            if old.fullscreen_state != state.fullscreen_state {\n                use crate::dispatch::FullscreenType;\n                Dispatch::instance_call_async(\n                    instance,\n                    DispatchType::ToggleFullscreen(FullscreenType::NoParam),\n                )\n                .await?;\n            }\n            if old.active_workspace != state.active_workspace {\n                use crate::dispatch::WorkspaceIdentifierWithSpecial;\n                Dispatch::instance_call_async(\n                    instance,\n                    DispatchType::Workspace(match &state.active_workspace {\n                        WorkspaceType::Regular(name) => WorkspaceIdentifierWithSpecial::Name(name),\n                        WorkspaceType::Special(opt) => {\n                            WorkspaceIdentifierWithSpecial::Special(match opt {\n                                Some(name) => Some(name),\n                                None => None,\n                            })\n                        }\n                    }),\n                )\n                .await?;\n            }\n            if old.active_monitor != state.active_monitor {\n                use crate::dispatch::MonitorIdentifier;\n                Dispatch::instance_call_async(\n                    instance,\n                    DispatchType::FocusMonitor(MonitorIdentifier::Name(&state.active_monitor)),\n                )\n                .await?;\n            };\n        }\n        Ok(state)\n    }\n\n    /// Execute changes in state\n    pub fn execute_state_sync(self, old: State) -> crate::Result<Self> {\n        self.instance_execute_state_sync(default_instance()?, old)\n    }\n\n    /// Execute changes in state\n    pub fn instance_execute_state_sync(\n        self,\n        instance: &Instance,\n        old: State,\n    ) -> crate::Result<Self> {\n        let state = self.clone();\n        if self != old {\n            use crate::dispatch::{Dispatch, DispatchType};\n            if old.fullscreen_state != state.fullscreen_state {\n                use crate::dispatch::FullscreenType;\n                Dispatch::instance_call(\n                    instance,\n                    DispatchType::ToggleFullscreen(FullscreenType::NoParam),\n                )?;\n            }\n            if old.active_workspace != state.active_workspace {\n                use crate::dispatch::WorkspaceIdentifierWithSpecial;\n                Dispatch::instance_call(\n                    instance,\n                    DispatchType::Workspace(match &state.active_workspace {\n                        WorkspaceType::Regular(name) => WorkspaceIdentifierWithSpecial::Name(name),\n                        WorkspaceType::Special(opt) => {\n                            WorkspaceIdentifierWithSpecial::Special(match opt {\n                                Some(name) => Some(name),\n                                None => None,\n                            })\n                        }\n                    }),\n                )?;\n            }\n            if old.active_monitor != state.active_monitor {\n                use crate::dispatch::MonitorIdentifier;\n                Dispatch::instance_call(\n                    instance,\n                    DispatchType::FocusMonitor(MonitorIdentifier::Name(&state.active_monitor)),\n                )?;\n            };\n        }\n        Ok(state)\n    }\n}\n\npub(crate) fn execute_empty_closure(f: &EmptyClosure) {\n    f();\n}\n\npub(crate) fn execute_closure<T: Clone>(f: &Closure<T>, val: T) {\n    f(val);\n}\n\n#[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\npub(crate) async fn execute_empty_closure_async(f: &EmptyAsyncClosure) {\n    f().await;\n}\n\n#[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\npub(crate) async fn execute_closure_async<T>(f: &AsyncClosure<T>, val: T) {\n    f(val).await;\n}\n\n/// This struct holds workspace event data\n#[derive(Debug, Clone, PartialEq, Eq)]\npub struct WorkspaceEventData {\n    /// The workspace name\n    pub name: WorkspaceType,\n    /// The window id\n    pub id: WorkspaceId,\n}\n\n/// This struct holds workspace event data\n/// when the workspace cannot be special\n#[derive(Debug, Clone, PartialEq, Eq)]\npub struct NonSpecialWorkspaceEventData {\n    /// The workspace name\n    pub name: String,\n    /// The window id\n    pub id: WorkspaceId,\n}\n\n/// This struct holds workspace moved event data\n#[derive(Debug, Clone, PartialEq, Eq)]\npub struct WorkspaceMovedEventData {\n    /// The workspace name\n    pub name: WorkspaceType,\n    /// The window id\n    pub id: WorkspaceId,\n    /// The monitor name\n    pub monitor: String,\n}\n\n/// This struct holds window event data\n#[derive(Debug, Clone, PartialEq, Eq)]\npub struct WindowEventData {\n    /// The window class\n    pub class: String,\n    /// The window title\n    pub title: String,\n    /// The window address\n    pub address: Address,\n}\n\n/// This struct holds monitor event data\n#[derive(Debug, Clone, PartialEq, Eq)]\npub struct MonitorEventData {\n    /// The monitor name\n    pub monitor_name: String,\n    /// The workspace name\n    pub workspace_name: Option<WorkspaceType>,\n}\n\n/// This struct holds changed special event data\n#[derive(Debug, Clone, PartialEq, Eq)]\npub struct ChangedSpecialEventData {\n    /// The monitor name\n    pub monitor_name: String,\n    /// The workspace name\n    pub workspace_name: String,\n}\n\n/// This struct holds monitor event data\n#[derive(Debug, Clone, PartialEq, Eq)]\npub struct MonitorAddedEventData {\n    /// The monitor's id\n    pub id: MonitorId,\n    /// The monitor's name\n    pub name: String,\n    /// the monitor's description\n    pub description: String,\n}\n\n/// This struct holds window float event data\n#[derive(Debug, Clone, PartialEq, Eq)]\npub struct WindowFloatEventData {\n    /// The window address\n    pub address: Address,\n    /// The float state\n    pub floating: bool,\n}\n\n/// This struct holds window pin event data\n#[derive(Debug, Clone, PartialEq, Eq)]\npub struct WindowPinEventData {\n    /// The window address\n    pub address: Address,\n    /// The pin state\n    pub pinned: bool,\n}\n\n/// This struct holds the event data for the windowtitle changed event\n#[derive(Debug, Clone, PartialEq, Eq)]\npub struct WindowTitleEventData {\n    /// The window address\n    pub address: Address,\n    /// The window title\n    pub title: String,\n}\n\n/// This struct represents an unknown event to hyprland-rs\n/// this allows you to use events that haven't been implemented in hyprland-rs.\n/// To use this use the [UnknownEventData::parse_args] method to properly get the args\n#[derive(Debug, Clone, PartialEq, Eq)]\npub struct UnknownEventData {\n    /// The event's name\n    pub name: String,\n    /// The args as a string\n    pub args: String,\n}\n\nimpl UnknownEventData {\n    /// Takes the amount of args, and splits the string correctly\n    pub fn parse_args(self, count: usize) -> Vec<String> {\n        self.args\n            .splitn(count, \",\")\n            .map(|x| x.to_string())\n            .collect()\n    }\n}\n/// This struct holds the data for the [Event::GroupToggled] event\n#[derive(Debug, Clone, PartialEq, Eq)]\npub struct GroupToggledEventData {\n    /// The toggle status, `false` means the group was destroyed\n    pub toggled: bool,\n    /// The window addresses associated with the group\n    pub window_addresses: Vec<Address>,\n}\n\n/// This enum holds every event type\n#[derive(Debug, Clone)]\npub enum Event {\n    /// An unknown event\n    Unknown(UnknownEventData),\n    /// An event that emits when the current workspace is changed,\n    /// it is the equivelant of the `workspacev2` event\n    WorkspaceChanged(WorkspaceEventData),\n    /// An event that emits when a workspace is deleted,\n    /// it is the equivelant of the `destroyworkspacev2` event\n    WorkspaceDeleted(WorkspaceEventData),\n    /// An event that emits when a workspace is created,\n    /// it is the equivelant of the `createworkspacev2` event\n    WorkspaceAdded(WorkspaceEventData),\n    /// An event that emits when a workspace is moved to another monitor,\n    /// it is the equivelant of the `moveworkspacev2` event\n    WorkspaceMoved(WorkspaceMovedEventData),\n    /// An event that emits when a workspace is renamed,\n    /// it is the equivelant of the `renameworkspace` event\n    WorkspaceRenamed(NonSpecialWorkspaceEventData),\n    #[doc(hidden)]\n    ActiveWindowChangedV1(Option<(String, String)>), // internal intermediary event\n    #[doc(hidden)]\n    ActiveWindowChangedV2(Option<Address>), // internal intermediary event\n    /// An event that emits when the active window is changed\n    /// Unlike the other events, this is a combination of 2 events\n    /// Those being `activewindow` and `activewindowv2`,\n    /// it waits for both, and then sends one unified event :)\n    ActiveWindowChanged(Option<WindowEventData>),\n    /// An event that emits when the active monitor is changed,\n    /// it is the equivelant of the `focusedmon` event\n    ActiveMonitorChanged(MonitorEventData),\n    /// An event that emits when the current fullscreen state is changed,\n    /// it is the equivelant of the `fullscreen` event\n    FullscreenStateChanged(bool),\n    /// An event that emits when a new monitor is added/connected,\n    /// it is the equivelant of the `monitoraddedv2` event\n    MonitorAdded(MonitorAddedEventData),\n    /// An event that emits when a monitor is removed/disconnected,\n    /// it is the equivelant of the `monitorremoved` event\n    MonitorRemoved(String),\n    /// An event that emits when a window is opened,\n    /// it is the equivelant of the `openwindow` event\n    WindowOpened(WindowOpenEvent),\n    /// An event that emits when a window is closed,\n    /// it is the equivelant of the `closewindow` event\n    WindowClosed(Address),\n    /// An event that emits when a window is moved to a different workspace,\n    /// it is the equivelant of the `movewindowv2` event\n    WindowMoved(WindowMoveEvent),\n    /// An event that emits when a special workspace is closed on the current monitor,\n    /// it is the equivelant of the `activespecial` event\n    SpecialRemoved(String),\n    /// An event that emits when the current special workspace is changed on a monitor,\n    /// it is the equivelant of the `activespecial` event\n    ChangedSpecial(ChangedSpecialEventData),\n    /// An event that emits when the layout of a keyboard changes,\n    /// it is the equivelant of the `activelayout` event\n    LayoutChanged(LayoutEvent),\n    /// An event that emits when the current keybind submap changes,\n    /// it is the equivelant of the `submap` event\n    SubMapChanged(String),\n    /// An event that emits when a layer shell surface is opened/mapped,\n    /// it is the equivelant of the `openlayer` event\n    LayerOpened(String),\n    /// An event that emits when a layer shell surface is closed/unmapped,\n    /// it is the equivelant of the `closelayer` event\n    LayerClosed(String),\n    /// An event that emits when the floating state of a window changes,\n    /// it is the equivelant of the `changefloatingmode` event\n    FloatStateChanged(WindowFloatEventData),\n    /// An event that emits when the a window requests the urgent state,\n    /// it is the equivelant of the `urgent` event\n    UrgentStateChanged(Address),\n    /// An event that emits when the title of a window changes,\n    /// it is the equivelant of the `windowtitlev2` event\n    WindowTitleChanged(WindowTitleEventData),\n    /// An event that emits when the screencopy state of a client changes\n    /// AKA, a process wants to capture/record your screen,\n    /// it is the equivelant of the `screencast` event\n    Screencast(ScreencastEventData),\n    /// An event that emits when hyprland is reloaded,\n    /// it is the equivelant of the `configreloaded` event\n    ConfigReloaded,\n    /// An event that emits when `ignoregrouplock` is toggled,\n    /// it is the equivelant of the `ignoregrouplock` event\n    IgnoreGroupLockStateChanged(bool),\n    /// An event that emits when `lockgroups` is toggled,\n    /// it is the equivelant of the `lockgroups` event\n    LockGroupsStateChanged(bool),\n    /// An event that emits when a window is pinned or unpinned,\n    /// it is the equivelant of the `pin` event\n    WindowPinned(WindowPinEventData),\n    /// And event that emits when a group is toggled,\n    /// it is the equivelant of the `togglegroup`\n    GroupToggled(GroupToggledEventData),\n    /// And event that emits when a window is moved into a group,\n    /// it is the equivelant of the `moveintogroup`\n    WindowMovedIntoGroup(Address),\n    /// And event that emits when a window is moved out of a group,\n    /// it is the equivelant of the `moveoutofgroup`\n    WindowMovedOutOfGroup(Address),\n}\n\nfn parse_string_as_work(str: String) -> WorkspaceType {\n    if str == \"special\" {\n        WorkspaceType::Special(None)\n    } else if str.starts_with(\"special:\") {\n        {\n            let mut iter = str.split(':');\n            iter.next();\n            match iter.next() {\n                Some(name) => WorkspaceType::Special(Some(name.to_string())),\n                None => WorkspaceType::Special(None),\n            }\n        }\n    } else {\n        WorkspaceType::Regular(str)\n    }\n}\n\n#[derive(PartialEq, Eq, Clone, Copy, Debug, Hash)]\npub(crate) enum ParsedEventType {\n    WorkspaceChangedV2,\n    WorkspaceDeletedV2,\n    WorkspaceAddedV2,\n    WorkspaceMovedV2,\n    WorkspaceRename,\n    ActiveWindowChangedV1,\n    ActiveWindowChangedV2,\n    ActiveMonitorChanged,\n    FullscreenStateChanged,\n    MonitorAddedV2,\n    MonitorRemoved,\n    WindowOpened,\n    WindowClosed,\n    WindowMovedV2,\n    ActiveSpecial,\n    LayoutChanged,\n    SubMapChanged,\n    LayerOpened,\n    LayerClosed,\n    FloatStateChanged,\n    UrgentStateChanged,\n    WindowTitleChangedV2,\n    Screencast,\n    ConfigReloaded,\n    IgnoreGroupLock,\n    LockGroups,\n    Pin,\n    ToggleGroup,\n    MoveIntoGroup,\n    MoveOutOfGroup,\n}\n\n/// All Hyprland events's arg count and enum variant.\n/// The first item of the tuple is a usize of the argument count\n/// This allows for easy parsing because the last arg in a Hyprland event\n/// has the ability to have extra `,`s\npub(crate) static EVENTS: &[(&str, (usize, ParsedEventType))] = &[\n    (\"workspacev2\", (2, ParsedEventType::WorkspaceChangedV2)),\n    (\n        \"destroyworkspacev2\",\n        (2, ParsedEventType::WorkspaceDeletedV2),\n    ),\n    (\"createworkspacev2\", (2, ParsedEventType::WorkspaceAddedV2)),\n    (\"moveworkspacev2\", (3, ParsedEventType::WorkspaceMovedV2)),\n    (\"renameworkspace\", (2, ParsedEventType::WorkspaceRename)),\n    (\"focusedmon\", (2, ParsedEventType::ActiveMonitorChanged)),\n    (\"activewindow\", (2, ParsedEventType::ActiveWindowChangedV1)),\n    (\n        \"activewindowv2\",\n        (1, ParsedEventType::ActiveWindowChangedV2),\n    ),\n    (\"fullscreen\", (1, ParsedEventType::FullscreenStateChanged)),\n    (\"monitorremoved\", (1, ParsedEventType::MonitorRemoved)),\n    (\"monitoraddedv2\", (3, ParsedEventType::MonitorAddedV2)),\n    (\"openwindow\", (4, ParsedEventType::WindowOpened)),\n    (\"closewindow\", (1, ParsedEventType::WindowClosed)),\n    (\"movewindowv2\", (3, ParsedEventType::WindowMovedV2)),\n    (\"activelayout\", (2, ParsedEventType::LayoutChanged)),\n    (\"activespecial\", (2, ParsedEventType::ActiveSpecial)),\n    (\"submap\", (1, ParsedEventType::SubMapChanged)),\n    (\"openlayer\", (1, ParsedEventType::LayerOpened)),\n    (\"closelayer\", (1, ParsedEventType::LayerClosed)),\n    (\n        \"changefloatingmode\",\n        (2, ParsedEventType::FloatStateChanged),\n    ),\n    (\"screencast\", (2, ParsedEventType::Screencast)),\n    (\"urgent\", (1, ParsedEventType::UrgentStateChanged)),\n    (\"windowtitlev2\", (2, ParsedEventType::WindowTitleChangedV2)),\n    (\"configreloaded\", (0, ParsedEventType::ConfigReloaded)),\n    (\"ignoregrouplock\", (1, ParsedEventType::IgnoreGroupLock)),\n    (\"lockgroups\", (1, ParsedEventType::LockGroups)),\n    (\"pin\", (2, ParsedEventType::Pin)),\n    (\"togglegroup\", (2, ParsedEventType::ToggleGroup)),\n    (\"moveintogroup\", (1, ParsedEventType::MoveIntoGroup)),\n    (\"moveoutofgroup\", (1, ParsedEventType::MoveOutOfGroup)),\n];\n\nuse crate::default_instance;\nuse crate::error::HyprError;\nuse crate::instance::Instance;\nuse either::Either;\n\ntype KnownEvent = (ParsedEventType, Vec<String>);\ntype UnknownEvent = (String, String);\n\nfn new_event_parser(input: &str) -> crate::Result<Either<KnownEvent, UnknownEvent>> {\n    input\n        .to_string()\n        .split_once(\">>\")\n        .ok_or(HyprError::Other(\n            \"could not get event name from Hyprland IPC data (not hyprland-rs)\".to_string(),\n        ))\n        .map(|(name, x)| {\n            if let Some(event) = EVENTS\n                .iter()\n                .find(|(i_name, _)| *i_name == name)\n                .map(|(_, event)| event)\n            {\n                Either::Left((\n                    event.1,\n                    x.splitn(event.0, \",\").map(|y| y.to_string()).collect(),\n                ))\n            } else {\n                Either::Right((name.to_string(), x.to_string()))\n            }\n        })\n}\n\nmacro_rules! parse_int {\n    ($int:expr, event: $event:literal) => {\n        parse_int!($int, event: $event => WorkspaceId)\n    };\n    ($int:expr, event: $event:literal => $int_type:ty) => {\n        ($int\n            .parse::<$int_type>()\n            .map_err(|e|\n                HyprError::Internal(format!(concat!($event, \": invalid integer error: {}\"), e))\n             )?\n        )\n    };\n\n}\n\nmacro_rules! get {\n    ($args:expr ; $id:literal) => {\n        get![ref $args;$id].clone()\n    };\n    (ref $args:expr ; $id:literal) => {\n        $args\n            .get($id)\n            .ok_or(HyprError::Internal(\n                concat!(\"could not get the event arg of index \", stringify!($id)).to_string(),\n            ))?\n    };\n}\n\n/// This internal function parses event strings\npub(crate) fn event_parser(event: String) -> crate::Result<Vec<Event>> {\n    // TODO: Optimize nested looped regex capturing. Maybe pull in rayon if possible.\n    let event_iter = event.trim().lines().filter_map(|event_line| {\n        if event_line.is_empty() {\n            None\n        } else {\n            Some(new_event_parser(event_line))\n        }\n    });\n\n    let parsed_events = event_iter.map(|event| match event {\n        Err(x) => Err(x),\n        Ok(Either::Right((name, args))) => Ok(Event::Unknown(UnknownEventData { name, args })),\n        Ok(Either::Left((event_type, args))) => match event_type {\n            ParsedEventType::WorkspaceChangedV2 => {\n                Ok(Event::WorkspaceChanged(WorkspaceEventData {\n                    id: parse_int!(get![ref args;0], event: \"WorkspaceChangedV2\"),\n                    name: parse_string_as_work(get![args;1]),\n                }))\n            }\n            ParsedEventType::WorkspaceDeletedV2 => {\n                Ok(Event::WorkspaceDeleted(WorkspaceEventData {\n                    id: parse_int!(get![ref args;0], event: \"WorkspaceDeletedV2\"),\n                    name: parse_string_as_work(get![args;1]),\n                }))\n            }\n            ParsedEventType::WorkspaceAddedV2 => Ok(Event::WorkspaceAdded(WorkspaceEventData {\n                id: parse_int!(get![ref args;0], event: \"WorkspaceAddedV2\"),\n                name: parse_string_as_work(get![args;1]),\n            })),\n            ParsedEventType::WorkspaceMovedV2 => {\n                Ok(Event::WorkspaceMoved(WorkspaceMovedEventData {\n                    id: parse_int!(get![ref args;0], event: \"WorkspaceMovedV2\"),\n                    name: parse_string_as_work(get![args;1]),\n                    monitor: get![args;2],\n                }))\n            }\n            ParsedEventType::WorkspaceRename => {\n                Ok(Event::WorkspaceRenamed(NonSpecialWorkspaceEventData {\n                    id: parse_int!(get![args;0], event: \"WorkspaceRenamed\"),\n                    name: get![args;1],\n                }))\n            }\n            ParsedEventType::ActiveMonitorChanged => {\n                Ok(Event::ActiveMonitorChanged(MonitorEventData {\n                    monitor_name: get![args;0],\n                    workspace_name: if get![args;1] == \"?\" {\n                        None\n                    } else {\n                        Some(parse_string_as_work(get![args;1]))\n                    },\n                }))\n            }\n            ParsedEventType::ActiveWindowChangedV1 => {\n                let class = get![args;0];\n                let title = get![args;1];\n                let event = if !class.is_empty() && !title.is_empty() {\n                    Event::ActiveWindowChangedV1(Some((class, title)))\n                } else {\n                    Event::ActiveWindowChangedV1(None)\n                };\n\n                Ok(event)\n            }\n            ParsedEventType::ActiveWindowChangedV2 => {\n                let addr = get![ref args;0];\n                let event = if addr != \",\" {\n                    Event::ActiveWindowChangedV2(Some(Address::new(addr)))\n                } else {\n                    Event::ActiveWindowChangedV2(None)\n                };\n                Ok(event)\n            }\n            ParsedEventType::FullscreenStateChanged => {\n                Ok(Event::FullscreenStateChanged(get![ref args;0] != \"0\"))\n            }\n            ParsedEventType::MonitorRemoved => Ok(Event::MonitorRemoved(get![args;0])),\n            ParsedEventType::MonitorAddedV2 => Ok(Event::MonitorAdded(MonitorAddedEventData {\n                id: parse_int!(get![ref args;0], event: \"MonitorAddedV2\" => MonitorId),\n                name: get![args;1],\n                description: get![args;2],\n            })),\n            ParsedEventType::WindowOpened => Ok(Event::WindowOpened(WindowOpenEvent {\n                window_address: Address::new(get![ref args;0]),\n                workspace_name: get![args;1],\n                window_class: get![args;2],\n                window_title: get![args;3],\n            })),\n            ParsedEventType::WindowClosed => Ok(Event::WindowClosed(Address::new(get![args;0]))),\n            ParsedEventType::WindowMovedV2 => Ok(Event::WindowMoved(WindowMoveEvent {\n                window_address: Address::fmt_new(get![ref args;0]),\n                workspace_id: parse_int!(get![ref args;1], event: \"WindowMoved\"),\n                workspace_name: parse_string_as_work(get![args;2]),\n            })),\n            ParsedEventType::ActiveSpecial => {\n                let workspace_name = get![args;0];\n                let monitor_name = get![args;1];\n                if workspace_name.is_empty() {\n                    Ok(Event::SpecialRemoved(monitor_name))\n                } else {\n                    Ok(Event::ChangedSpecial(ChangedSpecialEventData {\n                        monitor_name,\n                        workspace_name,\n                    }))\n                }\n            }\n            ParsedEventType::LayoutChanged => Ok(Event::LayoutChanged(LayoutEvent {\n                keyboard_name: get![args;0],\n                layout_name: get![args;1],\n            })),\n            ParsedEventType::SubMapChanged => Ok(Event::SubMapChanged(get![args;0])),\n            ParsedEventType::LayerOpened => Ok(Event::LayerOpened(get![args;0])),\n            ParsedEventType::LayerClosed => Ok(Event::LayerClosed(get![args;0])),\n            ParsedEventType::FloatStateChanged => {\n                let state = get![ref args;1] == \"0\"; // FIXME: does 0 mean it's floating?\n                Ok(Event::FloatStateChanged(WindowFloatEventData {\n                    address: Address::new(get![ref args;0]),\n                    floating: state,\n                }))\n            }\n            ParsedEventType::Screencast => {\n                let state = get![ref args;0] == \"1\";\n                let owner = get![ref args;1] == \"1\";\n                Ok(Event::Screencast(ScreencastEventData {\n                    turning_on: state,\n                    monitor: owner,\n                }))\n            }\n            ParsedEventType::UrgentStateChanged => {\n                Ok(Event::UrgentStateChanged(Address::new(get![ref args;0])))\n            }\n            ParsedEventType::WindowTitleChangedV2 => {\n                Ok(Event::WindowTitleChanged(WindowTitleEventData {\n                    address: Address::new(get![ref args;0]),\n                    title: get![args;1],\n                }))\n            }\n            ParsedEventType::ConfigReloaded => Ok(Event::ConfigReloaded),\n            ParsedEventType::IgnoreGroupLock => {\n                Ok(Event::IgnoreGroupLockStateChanged(get![ref args;0] == \"1\"))\n            }\n            ParsedEventType::LockGroups => {\n                Ok(Event::LockGroupsStateChanged(get![ref args;0] == \"1\"))\n            }\n            ParsedEventType::Pin => Ok(Event::WindowPinned(WindowPinEventData {\n                address: Address::new(get![ref args;0]),\n                pinned: get![ref args;1] == \"1\",\n            })),\n            ParsedEventType::ToggleGroup => Ok(Event::GroupToggled(GroupToggledEventData {\n                toggled: get![ref args;0] == \"1\",\n                window_addresses: get![ref args;1].split(\",\").map(Address::new).collect(),\n            })),\n            ParsedEventType::MoveIntoGroup => {\n                Ok(Event::WindowMovedIntoGroup(Address::new(get![ref args;0])))\n            }\n            ParsedEventType::MoveOutOfGroup => {\n                Ok(Event::WindowMovedOutOfGroup(Address::new(get![ref args;0])))\n            }\n        },\n    });\n\n    let mut events: Vec<Event> = Vec::new();\n\n    for event in parsed_events {\n        events.push(event?);\n    }\n\n    Ok(events)\n}\n"
  },
  {
    "path": "dep-crates/hyprland-rs/src/event_listener/stream.rs",
    "content": "use super::*;\nuse std::{\n    pin::Pin,\n    task::{Context, Poll},\n};\n\nuse crate::default_instance;\nuse crate::instance::Instance;\nuse futures_lite::{Stream, StreamExt};\n\n/// Event listener, but [Stream]\n/// This is the new prefered way of listening for events\n/// as its more idiomatic, and allows for more efficient memory management\n///\n/// # Examples\n/// ```rust, no_run\n/// use hyprland::prelude::*;\n/// use hyprland::event_listener::EventStream;\n/// use hyprland::Result as HResult;\n///\n/// #[tokio::main]\n/// async fn main() -> HResult<()> {\n///     use futures_lite::StreamExt;\n///     use hyprland::instance::Instance;\n///     let mut stream = EventStream::new();\n///     while let Some(Ok(event)) = stream.next().await {\n///          println!(\"{event:?}\");\n///     }\n/// }\n/// ```\n#[must_use = \"streams nothing unless polled\"]\npub struct EventStream {\n    stream: Pin<Box<dyn Stream<Item = crate::Result<Event>> + Send>>,\n}\nimpl Default for EventStream {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl EventStream {\n    /// Creates a new [EventStream]\n    pub fn new() -> Self {\n        use crate::async_import::*;\n        let stream = async_stream::try_stream! {\n        let mut stream: UnixStream = default_instance()?.get_event_stream_async().await?;\n            let mut active_windows = vec![];\n            loop {\n                let mut buffer = [0; 4096];\n                let bytes_read = stream.read(&mut buffer).await?;\n                if bytes_read == 0 {\n                    // If no bytes were read, we can assume the stream is closed\n                    break;\n                }\n                let buf = &buffer[..bytes_read];\n                let string = String::from_utf8(buf.to_vec())?;\n                let parsed: Vec<Event> = event_parser(string)?;\n                for event in parsed {\n                    for primed_event in event_primer_noexec(event, &mut active_windows)? {\n                        yield primed_event;\n                    }\n                }\n            }\n        };\n        Self {\n            stream: Box::pin(stream),\n        }\n    }\n\n    /// Creates a new [EventStream]\n    pub fn instance_new(instance: Instance) -> Self {\n        use crate::async_import::*;\n        let stream = async_stream::try_stream! {\n        let mut stream: UnixStream = instance.get_event_stream_async().await?;\n            let mut active_windows = vec![];\n            loop {\n                let mut buffer = [0; 4096];\n                let bytes_read = stream.read(&mut buffer).await?;\n                if bytes_read == 0 {\n                    // If no bytes were read, we can assume the stream is closed\n                    break;\n                }\n                let buf = &buffer[..bytes_read];\n                let string = String::from_utf8(buf.to_vec())?;\n                let parsed: Vec<Event> = event_parser(string)?;\n                for event in parsed {\n                    for primed_event in event_primer_noexec(event, &mut active_windows)? {\n                        yield primed_event;\n                    }\n                }\n            }\n        };\n        Self {\n            stream: Box::pin(stream),\n        }\n    }\n}\n\nimpl Stream for EventStream {\n    type Item = crate::Result<Event>;\n\n    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {\n        self.as_mut().stream.poll_next(cx)\n    }\n}\n"
  },
  {
    "path": "dep-crates/hyprland-rs/src/hyprpaper/error.rs",
    "content": "/// An unexpected response from interacting with hyprpaper.\n#[derive(Debug, derive_more::Display)]\npub enum Error {\n    /// The keyword was not executed correctly, for example by misformed input\n    /// or a path that does not exist.\n    NotOk(String),\n    /// When we failed to parse the active wallpapers response from hyprpaper.\n    FailedToParseActiveWallpapers(String),\n    /// There are no active wallpapers when asking list the active ones.\n    NoWallpapersActive,\n    /// There are no loaded wallpapers when asking list the loaded ones.\n    NoWallpapersLoaded,\n}\n\nimpl std::error::Error for Error {}\n"
  },
  {
    "path": "dep-crates/hyprland-rs/src/hyprpaper/keyword.rs",
    "content": "use super::{Error, Preload, Reload, Response, Unload, Wallpaper, WallpaperListing};\nuse crate::error::HyprError;\n\n/// The hyprpaper keyword, used to interact with hyprpaper.\npub enum Keyword {\n    /// Preload a wallpaper into memory.\n    Preload(Preload),\n    /// Reload hyprpaper with another wallpaper.\n    Reload(Reload),\n    /// Unload a wallpaper from memory.\n    Unload(Unload),\n    /// Set an already preloaded wallpaper.\n    Wallpaper(Wallpaper),\n    /// Request a list of active wallpapers.\n    ListActive,\n    /// Request a list of loaded wallpapers.\n    ListLoaded,\n}\n\npub(super) enum ExpectedResponse {\n    Ok,\n    Active,\n    Loaded,\n}\n\nimpl ExpectedResponse {\n    pub(super) fn is_expected(&self, response: String) -> crate::Result<Response> {\n        match self {\n            ExpectedResponse::Ok => {\n                if response.trim() == \"ok\" {\n                    Ok(Response::Ok)\n                } else {\n                    Err(HyprError::Hyprpaper(Error::NotOk(response)))\n                }\n            }\n            ExpectedResponse::Active => {\n                if response.trim() == \"no wallpapers active\" {\n                    return Err(HyprError::Hyprpaper(Error::NoWallpapersActive));\n                }\n                let wallpaper_listings = response\n                    .lines()\n                    .map(WallpaperListing::try_from)\n                    .collect::<crate::Result<_>>()?;\n                Ok(Response::ActiveWallpapers(wallpaper_listings))\n            }\n            ExpectedResponse::Loaded => {\n                if response.trim() == \"no wallpapers loaded\" {\n                    return Err(HyprError::Hyprpaper(Error::NoWallpapersLoaded));\n                }\n                let wallpapers = response.lines().map(ToOwned::to_owned).collect();\n                Ok(Response::LoadedWallpapers(wallpapers))\n            }\n        }\n    }\n}\n\nimpl Keyword {\n    pub(super) fn expected_response(&self) -> ExpectedResponse {\n        match &self {\n            Keyword::Preload(_)\n            | Keyword::Reload(_)\n            | Keyword::Unload(_)\n            | Keyword::Wallpaper(_) => ExpectedResponse::Ok,\n            Keyword::ListActive => ExpectedResponse::Active,\n            Keyword::ListLoaded => ExpectedResponse::Loaded,\n        }\n    }\n}\n\nimpl std::fmt::Display for Keyword {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            Self::Preload(preload) => write!(f, \"{preload}\"),\n            Self::Reload(reload) => write!(f, \"{reload}\"),\n            Self::Unload(unload) => write!(f, \"{unload}\"),\n            Self::Wallpaper(wallpaper) => write!(f, \"{wallpaper}\"),\n            Self::ListActive => write!(f, \"listactive\"),\n            Self::ListLoaded => write!(f, \"listloaded\"),\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::hyprpaper::*;\n\n    #[track_caller]\n    fn check(command: Keyword, expected_string: &str) {\n        let actual = command.to_string();\n        assert_eq!(&actual, expected_string);\n    }\n\n    #[test]\n    fn test_preload_string() {\n        let command = Keyword::Preload(Preload {\n            path: \"/foo/bar\".into(),\n        });\n        check(command, \"preload /foo/bar\");\n    }\n\n    #[test]\n    fn test_wallpaper() {\n        let command = Keyword::Wallpaper(Wallpaper {\n            monitor: None,\n            mode: None,\n            path: \"/foo/bar\".into(),\n        });\n        check(command, \"wallpaper ,/foo/bar\");\n\n        let command = Keyword::Wallpaper(Wallpaper {\n            monitor: Some(Monitor::Port(\"DP-1\".into())),\n            mode: None,\n            path: \"/foo/bar\".into(),\n        });\n        check(command, \"wallpaper DP-1,/foo/bar\");\n\n        let command = Keyword::Wallpaper(Wallpaper {\n            monitor: Some(Monitor::Description(\"some monitor desc\".into())),\n            mode: None,\n            path: \"/foo/bar\".into(),\n        });\n        check(command, \"wallpaper desc:some monitor desc,/foo/bar\");\n\n        let command = Keyword::Wallpaper(Wallpaper {\n            monitor: Some(Monitor::Description(\"some monitor desc\".into())),\n            mode: Some(WallpaperMode::Contain),\n            path: \"/foo/bar\".into(),\n        });\n        check(command, \"wallpaper desc:some monitor desc,contain:/foo/bar\");\n\n        let command = Keyword::Wallpaper(Wallpaper {\n            monitor: Some(Monitor::Description(\"some monitor desc\".into())),\n            mode: Some(WallpaperMode::Tile),\n            path: \"/foo/bar\".into(),\n        });\n        check(command, \"wallpaper desc:some monitor desc,tile:/foo/bar\");\n    }\n\n    #[test]\n    fn test_unload() {\n        let command = Keyword::Unload(Unload::Path(\"/foo/bar\".into()));\n        check(command, \"unload /foo/bar\");\n\n        let command = Keyword::Unload(Unload::All);\n        check(command, \"unload all\");\n    }\n}\n"
  },
  {
    "path": "dep-crates/hyprland-rs/src/hyprpaper/monitor.rs",
    "content": "/// A monitor on which to apply a wallpaper, see [`crate::hyprpaper::Wallpaper`].\npub enum Monitor {\n    /// A monitor port, such as \"DP-1\".\n    Port(String),\n    /// A monitor description, such as \"Dell Inc. DELL P2419HC GNJJJ73\".\n    Description(String),\n}\n\nimpl std::fmt::Display for Monitor {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            Self::Port(port) => write!(f, \"{port}\"),\n            Self::Description(description) => write!(f, \"desc:{description}\"),\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[track_caller]\n    fn check(monitor: Monitor, expected: &str) {\n        assert_eq!(monitor.to_string(), expected);\n    }\n\n    #[test]\n    fn test_port() {\n        let monitor = Monitor::Port(\"DP-1\".into());\n        check(monitor, \"DP-1\");\n    }\n\n    #[test]\n    fn test_description() {\n        let monitor = Monitor::Description(\"Dell Inc. DELL P2419HC GNJJJ73\".into());\n        check(monitor, \"desc:Dell Inc. DELL P2419HC GNJJJ73\");\n    }\n}\n"
  },
  {
    "path": "dep-crates/hyprland-rs/src/hyprpaper/preload.rs",
    "content": "/// Preload a wallpaper into memory.\npub struct Preload {\n    /// Path of the wallpaper to preload.\n    pub path: String,\n}\n\nimpl std::fmt::Display for Preload {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"preload {}\", self.path)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_preload() {\n        let preload = Preload {\n            path: \"/foo/bar.jpg\".into(),\n        };\n        assert_eq!(preload.to_string(), \"preload /foo/bar.jpg\");\n    }\n}\n"
  },
  {
    "path": "dep-crates/hyprland-rs/src/hyprpaper/reload.rs",
    "content": "use super::{Monitor, WallpaperMode};\n\n/// Reload hyprpaper with the given wallpaper configuration, effectively swap\n/// the wallpaper with this new one.\n///\n/// This is equivalent to the \"preload, set new, unload old\" process.\npub struct Reload {\n    /// The monitor on which to apply the new wallpaper.\n    ///\n    /// All monitors, if `None`.\n    pub monitor: Option<Monitor>,\n    /// The wallpaper mode (how it will fill the screen).\n    pub mode: Option<WallpaperMode>,\n    /// Path to the wallpaper.\n    pub path: String,\n}\n\nimpl std::fmt::Display for Reload {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"reload \")?;\n        if let Some(monitor) = &self.monitor {\n            write!(f, \"{monitor}\")?;\n        }\n        write!(f, \",\")?;\n        if let Some(mode) = &self.mode {\n            write!(f, \"{mode}\")?;\n        }\n        write!(f, \"{}\", self.path)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[track_caller]\n    fn check(reload: Reload, expected: &str) {\n        assert_eq!(reload.to_string(), expected);\n    }\n\n    #[test]\n    fn test_reload_format_no_monitor_no_mode() {\n        let reload = Reload {\n            monitor: None,\n            mode: None,\n            path: \"/foo/bar.jpg\".into(),\n        };\n        check(reload, \"reload ,/foo/bar.jpg\");\n    }\n\n    #[test]\n    fn test_reload_format_with_monitor_with_mode() {\n        let reload = Reload {\n            monitor: Some(Monitor::Port(\"DP-1\".into())),\n            mode: Some(WallpaperMode::Contain),\n            path: \"/foo/bar.jpg\".into(),\n        };\n        check(reload, \"reload DP-1,contain:/foo/bar.jpg\");\n    }\n}\n"
  },
  {
    "path": "dep-crates/hyprland-rs/src/hyprpaper/unload.rs",
    "content": "/// Unload a wallpaper from memory.\npub enum Unload {\n    /// Unload the wallpaper at this path.\n    Path(String),\n    /// Unload all wallpapers.\n    All,\n}\n\nimpl std::fmt::Display for Unload {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"unload \")?;\n        match self {\n            Self::Path(path) => write!(f, \"{path}\"),\n            Self::All => write!(f, \"all\"),\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[track_caller]\n    fn check(unload: Unload, expected: &str) {\n        assert_eq!(unload.to_string(), expected);\n    }\n\n    #[test]\n    fn test_unload_path() {\n        let unload = Unload::Path(\"/foo/bar.jpg\".into());\n        check(unload, \"unload /foo/bar.jpg\");\n    }\n\n    #[test]\n    fn test_unload_all() {\n        let unload = Unload::All;\n        check(unload, \"unload all\");\n    }\n}\n"
  },
  {
    "path": "dep-crates/hyprland-rs/src/hyprpaper/wallpaper.rs",
    "content": "use super::{Monitor, WallpaperMode};\n\n/// Set a wallpaper, optionally on a specific monitor.\npub struct Wallpaper {\n    /// The monitor on which to apply the wallpaper.\n    ///\n    /// All monitors, if `None`.\n    pub monitor: Option<Monitor>,\n    /// The wallpaper mode (how it will fill the screen).\n    pub mode: Option<WallpaperMode>,\n    /// Path to the wallpaper.\n    pub path: String,\n}\n\nimpl std::fmt::Display for Wallpaper {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"wallpaper \")?;\n        if let Some(monitor) = &self.monitor {\n            write!(f, \"{monitor}\")?;\n        }\n        write!(f, \",\")?;\n        if let Some(mode) = &self.mode {\n            write!(f, \"{mode}\")?;\n        }\n        write!(f, \"{}\", self.path)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[track_caller]\n    fn check(wallpaper: Wallpaper, expected: &str) {\n        assert_eq!(wallpaper.to_string(), expected);\n    }\n\n    #[test]\n    fn test_wallpaper_no_monitor_no_mode() {\n        let wallpaper = Wallpaper {\n            monitor: None,\n            mode: None,\n            path: \"/foo/bar.jpg\".into(),\n        };\n        check(wallpaper, \"wallpaper ,/foo/bar.jpg\");\n    }\n\n    #[test]\n    fn test_wallpaper_with_monitor_with_mode() {\n        let wallpaper = Wallpaper {\n            monitor: Some(Monitor::Port(\"DP-1\".into())),\n            mode: Some(WallpaperMode::Tile),\n            path: \"/foo/bar.jpg\".into(),\n        };\n        check(wallpaper, \"wallpaper DP-1,tile:/foo/bar.jpg\");\n    }\n}\n"
  },
  {
    "path": "dep-crates/hyprland-rs/src/hyprpaper/wallpaper_listing.rs",
    "content": "use super::Error;\nuse crate::error::HyprError;\n\n/// A listing of an active wallpaper.\n#[derive(Debug, PartialEq)]\npub struct WallpaperListing {\n    /// The monitor that the wallpaper is active on, or `None` if no specific\n    /// monitor.\n    pub monitor: Option<String>,\n    /// The active wallpaper.\n    pub wallpaper_path: String,\n}\n\nimpl TryFrom<&str> for WallpaperListing {\n    type Error = HyprError;\n\n    fn try_from(s: &str) -> Result<Self, Self::Error> {\n        let (monitor, wallpaper_path) = s.split_once('=').ok_or_else(|| {\n            HyprError::Hyprpaper(Error::FailedToParseActiveWallpapers(s.to_owned()))\n        })?;\n        let monitor = {\n            let monitor = monitor.trim();\n            let has_monitor = !monitor.is_empty();\n            has_monitor.then(|| monitor.to_owned())\n        };\n        let wallpaper_path = wallpaper_path.trim().to_owned();\n        Ok(Self {\n            monitor,\n            wallpaper_path,\n        })\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[track_caller]\n    fn check_ok(s: &str, expected_monitor: Option<&str>, expected_wallpaper_path: &str) {\n        #[allow(clippy::unwrap_used)]\n        let wallpaper_listing = WallpaperListing::try_from(s).unwrap();\n        assert_eq!(wallpaper_listing.monitor.as_deref(), expected_monitor);\n        assert_eq!(wallpaper_listing.wallpaper_path, expected_wallpaper_path);\n    }\n\n    #[test]\n    fn test_ok_no_monitor() {\n        let s = \" = /foo/bar.jpg\";\n        check_ok(s, None, \"/foo/bar.jpg\");\n    }\n\n    #[test]\n    fn test_ok_with_monitor() {\n        let s = \"DP-1 = /foo/bar.jpg\";\n        check_ok(s, Some(\"DP-1\"), \"/foo/bar.jpg\");\n    }\n\n    #[test]\n    fn test_err() {\n        let s = \"DP-1 /foo/bar.jpg\";\n        assert!(matches!(\n            WallpaperListing::try_from(s),\n            Err(HyprError::Hyprpaper(Error::FailedToParseActiveWallpapers(\n                err_str\n            ))) if s == err_str\n        ));\n    }\n}\n"
  },
  {
    "path": "dep-crates/hyprland-rs/src/hyprpaper/wallpaper_mode.rs",
    "content": "/// The desired fill mode of the wallpaper.\n// #[derive(Default)]\npub enum WallpaperMode {\n    // /// Cover all of the screen, keeping aspect ratio but potentially cutting of\n    // /// at some edges.\n    // #[default]\n    // Cover,\n    /// Contain the wallpaper in the screen, potentially changing aspect ratio.\n    Contain,\n    /// Tile the wallpaper to fill the entire screen.\n    Tile,\n}\n\nimpl std::fmt::Display for WallpaperMode {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            // Self::Cover => Ok(()), // Default behavior, don't need to write anything.\n            Self::Contain => write!(f, \"contain:\"),\n            Self::Tile => write!(f, \"tile:\"),\n        }\n    }\n}\n"
  },
  {
    "path": "dep-crates/hyprland-rs/src/hyprpaper.rs",
    "content": "mod error;\nmod keyword;\nmod monitor;\nmod preload;\nmod reload;\nmod unload;\nmod wallpaper;\nmod wallpaper_listing;\nmod wallpaper_mode;\n\nuse crate::default_instance;\nuse crate::instance::Instance;\nuse crate::shared::CommandContent;\npub use error::Error;\npub use keyword::Keyword;\npub use monitor::Monitor;\npub use preload::Preload;\npub use reload::Reload;\npub use unload::Unload;\npub use wallpaper::Wallpaper;\npub use wallpaper_listing::WallpaperListing;\npub use wallpaper_mode::WallpaperMode;\n\n/// Response from hyprpaper.\npub enum Response {\n    /// Keyword was accepted.\n    Ok,\n    /// A list of active wallpapers.\n    ActiveWallpapers(Vec<WallpaperListing>),\n    /// A list of loaded wallpapers.\n    LoadedWallpapers(Vec<String>),\n}\n\n/// Send a keyword to hyprpaper using IPC.\npub fn hyprpaper(keyword: Keyword) -> crate::Result<Response> {\n    instance_hyprpaper(default_instance()?, keyword)\n}\n\n/// Send a keyword to hyprpaper using IPC.\npub fn instance_hyprpaper(instance: &Instance, keyword: Keyword) -> crate::Result<Response> {\n    let expected_response = keyword.expected_response();\n\n    let content = CommandContent {\n        flag: crate::shared::CommandFlag::Empty,\n        data: keyword.to_string(),\n    };\n    let response = instance.write_to_hyprpaper_socket(content)?;\n    expected_response.is_expected(response)\n}\n\n/// Send a keyword to hyprpaper using IPC.\npub async fn hyprpaper_async(keyword: Keyword) -> crate::Result<Response> {\n    instance_hyprpaper_async(default_instance()?, keyword).await\n}\n\n/// Send a keyword to hyprpaper using IPC.\npub async fn instance_hyprpaper_async(\n    instance: &Instance,\n    keyword: Keyword,\n) -> crate::Result<Response> {\n    let expected_response = keyword.expected_response();\n\n    let content = CommandContent {\n        flag: crate::shared::CommandFlag::Empty,\n        data: keyword.to_string(),\n    };\n    let response = instance.write_to_hyprpaper_socket_async(content).await?;\n    expected_response.is_expected(response)\n}\n"
  },
  {
    "path": "dep-crates/hyprland-rs/src/instance.rs",
    "content": "use crate::error::hypr_err;\nuse crate::shared::{get_hypr_path, CommandContent};\nuse std::path::{Path, PathBuf};\n\n/// This is the sync version of the Hyprland Instance.\n/// It holds the event streams connected to the sockets of one running Hyprland instance.\n#[derive(Debug, Clone)]\npub struct Instance {\n    instance: String,\n    /// .socket.sock\n    stream: Box<Path>,\n    /// .hyprpaper.sock\n    #[cfg(feature = \"hyprpaper\")]\n    hyprpaper_stream: Box<Path>,\n    /// .socket2.sock\n    #[cfg(feature = \"listener\")]\n    event_socket_path: Box<Path>,\n}\n\nimpl PartialEq<Self> for Instance {\n    fn eq(&self, other: &Self) -> bool {\n        self.instance == other.instance\n    }\n}\n\nimpl Instance {\n    /// uses the $HYPRLAND_INSTANCE_SIGNATURE env variable\n    pub fn from_current_env() -> crate::Result<Self> {\n        let mut path = get_hypr_path()?;\n        let name = get_env_name()?;\n        path.push(&name);\n        Self::from_base_socket_path(path)\n    }\n\n    /// instance: 9958d297641b5c84dcff93f9039d80a5ad37ab00_1752788564_214680212\n    pub fn from_instance(name: String) -> crate::Result<Self> {\n        let mut path = get_hypr_path()?;\n        path.push(&name);\n        Self::from_base_socket_path(path)\n    }\n\n    /// /run/user/$UID/hypr/9958d297641b5c84dcff93f9039d80a5ad37ab00_1752788564_214680212\n    pub fn from_base_socket_path(path: PathBuf) -> crate::Result<Self> {\n        let Some(name) = path.file_name().map(|n| n.to_string_lossy().to_string()) else {\n            hypr_err!(\"Could not get instance name from path: {}\", path.display());\n        };\n        if !path.exists() {\n            hypr_err!(\"Hyprland instance path does not exist: {}\", path.display());\n        }\n        Ok(Self {\n            instance: name,\n            stream: path.join(\".socket.sock\").into_boxed_path(),\n            #[cfg(feature = \"listener\")]\n            event_socket_path: path.join(\".socket2.sock\").into_boxed_path(),\n            #[cfg(feature = \"hyprpaper\")]\n            hyprpaper_stream: path.join(\".hyprpaper.sock\").into_boxed_path(),\n        })\n    }\n}\n\nimpl Instance {\n    pub(crate) fn write_to_socket(&self, content: CommandContent) -> crate::Result<String> {\n        use std::io::{Read, Write};\n        let mut stream = std::os::unix::net::UnixStream::connect(&self.stream)?;\n        stream.write_all(&content.as_bytes())?;\n        let mut response = Vec::new();\n        stream.read_to_end(&mut response)?;\n        drop(stream);\n        Ok(String::from_utf8(response)?)\n    }\n\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    pub(crate) async fn write_to_socket_async(\n        &self,\n        content: CommandContent,\n    ) -> crate::Result<String> {\n        use crate::async_import::{AsyncReadExt, AsyncWriteExt};\n        let mut stream = crate::async_import::UnixStream::connect(&self.stream).await?;\n        stream.write_all(&content.as_bytes()).await?;\n        let mut response = Vec::new();\n        stream.read_to_end(&mut response).await?;\n        drop(stream);\n        Ok(String::from_utf8(response)?)\n    }\n\n    #[cfg(feature = \"hyprpaper\")]\n    pub(crate) fn write_to_hyprpaper_socket(\n        &self,\n        content: CommandContent,\n    ) -> crate::Result<String> {\n        use std::io::{Read, Write};\n        let mut stream = std::os::unix::net::UnixStream::connect(&self.hyprpaper_stream)?;\n        stream.write_all(content.data.as_bytes())?;\n        let mut response = Vec::new();\n        stream.read_to_end(&mut response)?;\n        drop(stream);\n        Ok(String::from_utf8(response)?)\n    }\n\n    #[cfg(all(feature = \"hyprpaper\", any(feature = \"async-lite\", feature = \"tokio\")))]\n    pub(crate) async fn write_to_hyprpaper_socket_async(\n        &self,\n        content: CommandContent,\n    ) -> crate::Result<String> {\n        use crate::async_import::{AsyncReadExt, AsyncWriteExt};\n        let mut stream = crate::async_import::UnixStream::connect(&self.hyprpaper_stream).await?;\n        stream.write_all(content.data.as_bytes()).await?;\n        let mut response = Vec::new();\n        stream.read_to_end(&mut response).await?;\n        drop(stream);\n        Ok(String::from_utf8(response)?)\n    }\n\n    #[cfg(feature = \"listener\")]\n    pub(crate) fn get_event_stream(&self) -> crate::Result<std::os::unix::net::UnixStream> {\n        let stream = std::os::unix::net::UnixStream::connect(&self.event_socket_path)?;\n        Ok(stream)\n    }\n\n    #[cfg(all(feature = \"listener\", any(feature = \"async-lite\", feature = \"tokio\")))]\n    pub(crate) async fn get_event_stream_async(\n        &self,\n    ) -> crate::Result<crate::async_import::UnixStream> {\n        let stream = crate::async_import::UnixStream::connect(&self.event_socket_path).await?;\n        Ok(stream)\n    }\n}\n\nfn get_env_name() -> crate::Result<String> {\n    let instance = match std::env::var(\"HYPRLAND_INSTANCE_SIGNATURE\") {\n        Ok(var) => var,\n        Err(std::env::VarError::NotPresent) => {\n            hypr_err!(\"Could not get socket path! (Is Hyprland running??)\")\n        }\n        Err(std::env::VarError::NotUnicode(_)) => {\n            hypr_err!(\"Corrupted Hyprland socket variable: Invalid unicode!\")\n        }\n    };\n    Ok(instance)\n}\n"
  },
  {
    "path": "dep-crates/hyprland-rs/src/keyword.rs",
    "content": "//! # Keyword module\n//!\n//! This module is used for setting, and getting keywords\n//!\n//! ## Usage\n//!\n//! ```rust, no_run\n//! use hyprland::keyword::Keyword;\n//! fn main() -> hyprland::Result<()> {\n//!     Keyword::get(\"some_keyword\")?;\n//!     Keyword::set(\"another_keyword\", \"the value to set it to\")?;\n//!\n//!     Ok(())\n//! }\n//! ```\n\nuse crate::default_instance;\nuse crate::error::hypr_err;\nuse crate::instance::Instance;\nuse crate::shared::*;\nuse derive_more::Display;\nuse serde::{Deserialize, Serialize};\n\n#[derive(Serialize, Deserialize, Debug, Clone)]\npub(crate) struct OptionRaw {\n    pub option: String,\n    pub int: Option<i64>,\n    pub float: Option<f64>,\n    pub str: Option<String>,\n    pub set: bool,\n}\n\n/// This enum holds the possible values of a keyword/option\n#[derive(Serialize, Deserialize, Debug, Clone, Display)]\npub enum OptionValue {\n    /// A integer (64-bit)\n    #[display(\"{_0}\")]\n    Int(i64),\n    /// A floating point (64-point)\n    #[display(\"{_0}\")]\n    Float(f64),\n    /// A string\n    #[display(\"{_0}\")]\n    String(String),\n}\n\nimpl From<OptionValue> for String {\n    fn from(opt: OptionValue) -> Self {\n        opt.to_string()\n    }\n}\n\ntrait IsString {}\nimpl IsString for String {}\nimpl IsString for &str {}\n\nimpl<Str: ToString + IsString> From<Str> for OptionValue {\n    fn from(str: Str) -> Self {\n        OptionValue::String(str.to_string())\n    }\n}\n\nmacro_rules! ints_to_opt {\n    ($($ty:ty), *) => {\n        $(\n            impl From<$ty> for OptionValue {\n                fn from(num: $ty) -> Self {\n                    OptionValue::Int(num as i64)\n                }\n            }\n        )*\n    };\n}\n\nints_to_opt!(u8, i8, u16, i16, u32, i32, u64, i64);\n\nmacro_rules! floats_to_opt {\n    ($($ty:ty),*) => {\n        $(\n            impl From<$ty> for OptionValue {\n                fn from(num: $ty) -> Self {\n                    OptionValue::Float(num as f64)\n                }\n            }\n        )*\n    };\n}\n\nfloats_to_opt!(f32, f64);\n\n/// This struct holds a keyword\n#[derive(Serialize, Deserialize, Debug, Clone)]\npub struct Keyword {\n    /// The identifier (or name) of the keyword\n    pub option: String,\n    /// The value of the keyword/option\n    pub value: OptionValue,\n    /// Is value overriden or not\n    pub set: bool,\n}\n\nimpl Keyword {\n    fn parse_opts(\n        OptionRaw {\n            option,\n            int,\n            float,\n            str,\n            set,\n        }: OptionRaw,\n    ) -> crate::Result<Keyword> {\n        let int_exists = int.is_some() as u8;\n        let float_exists = float.is_some() as u8;\n        let str_exists = str.is_some() as u8;\n\n        // EXPLANATION: if at least two types of value is exists then we stop execution.\n        if int_exists + float_exists + str_exists > 1 {\n            hypr_err!(\"Expected single value type, but received more than one! Please open an issue with hyprland-rs with the information: Option {{ option: {option}, int: {int:?}, float: {float:?}, str: {str:?}, set: {set} }}!\");\n        }\n\n        let value = match (int, float, str) {\n            (Some(int), _, _) => OptionValue::Int(int),\n            (_, Some(float), _) => OptionValue::Float(float),\n            (_, _, Some(str)) => OptionValue::String(str),\n            (int, float, str) => hypr_err!(\"Expected either an 'int', a 'float' or a 'str', but none of them is not received! Please open an issue with hyprland-rs with the information: Option {{ option: {option}, int: {int:?}, float: {float:?}, str: {str:?}, set: {set} }}!\"),\n        };\n\n        Ok(Keyword { option, value, set })\n    }\n\n    /// This function sets a keyword's value\n    pub fn set<Str: ToString, Opt: Into<OptionValue>>(key: Str, value: Opt) -> crate::Result<()> {\n        Self::instance_set(default_instance()?, key, value)\n    }\n\n    /// This function sets a keyword's value\n    pub fn instance_set<Str: ToString, Opt: Into<OptionValue>>(\n        instance: &Instance,\n        key: Str,\n        value: Opt,\n    ) -> crate::Result<()> {\n        instance.write_to_socket(command!(\n            Empty,\n            \"keyword {} {}\",\n            key.to_string(),\n            value.into().to_string()\n        ))?;\n        Ok(())\n    }\n\n    /// This function sets a keyword's value (async)\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    pub async fn set_async<Str: ToString, Opt: Into<OptionValue>>(\n        key: Str,\n        value: Opt,\n    ) -> crate::Result<()> {\n        Self::instance_set_async(default_instance()?, key, value).await\n    }\n\n    /// This function sets a keyword's value (async)\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    pub async fn instance_set_async<Str: ToString, Opt: Into<OptionValue>>(\n        instance: &Instance,\n        key: Str,\n        value: Opt,\n    ) -> crate::Result<()> {\n        instance\n            .write_to_socket_async(command!(\n                Empty,\n                \"keyword {} {}\",\n                key.to_string(),\n                value.into().to_string()\n            ))\n            .await?;\n        Ok(())\n    }\n\n    /// This function returns the value of a keyword\n    pub fn get<Str: ToString>(key: Str) -> crate::Result<Self> {\n        Self::instance_get(default_instance()?, key)\n    }\n\n    /// This function returns the value of a keyword\n    pub fn instance_get<Str: ToString>(instance: &Instance, key: Str) -> crate::Result<Self> {\n        let data = instance.write_to_socket(command!(JSON, \"getoption {}\", key.to_string()))?;\n        let deserialized: OptionRaw = serde_json::from_str(&data)?;\n        let keyword = Keyword::parse_opts(deserialized)?;\n        Ok(keyword)\n    }\n\n    /// This function returns the value of a keyword (async)\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    pub async fn get_async<Str: ToString>(key: Str) -> crate::Result<Self> {\n        Self::instance_get_async(default_instance()?, key).await\n    }\n\n    /// This function returns the value of a keyword (async)\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    pub async fn instance_get_async<Str: ToString>(\n        instance: &Instance,\n        key: Str,\n    ) -> crate::Result<Self> {\n        let data = instance\n            .write_to_socket_async(command!(JSON, \"getoption {}\", key.to_string()))\n            .await?;\n        let deserialized: OptionRaw = serde_json::from_str(&data)?;\n        let keyword = Keyword::parse_opts(deserialized)?;\n        Ok(keyword)\n    }\n}\n"
  },
  {
    "path": "dep-crates/hyprland-rs/src/lib.rs",
    "content": "#![doc = include_str!(\"../README.md\")]\n#![warn(missing_docs)]\n#![allow(clippy::all)]\n#![allow(async_fn_in_trait)]\n#![cfg_attr(feature = \"unsafe-impl\", allow(unsafe_code))]\n#![cfg_attr(not(feature = \"unsafe-impl\"), forbid(unsafe_code))]\n\n#[macro_use]\nextern crate hyprland_macros;\n#[macro_use]\nextern crate paste;\n\nuse crate::error::HyprError;\nuse crate::instance::Instance;\nuse std::sync::OnceLock;\n\n/// This module provides several impls that are unsafe, for FFI purposes. Only use if you know what you are doing.\n#[cfg(feature = \"unsafe-impl\")]\npub mod unsafe_impl;\n\n/// This module provides shared things throughout the crate\npub mod shared;\n\n/// This module provides functions for getting information on the compositor\n#[cfg(feature = \"data\")]\npub mod data;\n\n/// This module provides the EventListener struct for listening and acting upon for events\n#[cfg(feature = \"listener\")]\npub mod event_listener;\n\n/// This module is for calling dispatchers and changing keywords\n#[cfg(feature = \"dispatch\")]\npub mod dispatch;\n\n/// This module is for calling hyprctl **commands**, for getting data use [data]\n#[cfg(feature = \"ctl\")]\npub mod ctl;\n\n/// This module provides the stuff needed to mutate, and read Hyprland config values\n#[cfg(feature = \"keyword\")]\npub mod keyword;\n\n/// This module provides helpers to easily config Hyprland\n#[cfg(feature = \"config\")]\npub mod config;\n\n/// Holds the error type used throughout the crate\npub mod error;\n/// Used to generate the Instances to interface with Hyprland\npub mod instance;\n\n/// This module is for interacting with [hyprpaper] using its IPC feature\n///\n/// [hyprpaper]: https://wiki.hyprland.org/Hypr-Ecosystem/hyprpaper/\n#[cfg(feature = \"hyprpaper\")]\npub mod hyprpaper;\n\n/// The prelude module, this is to import all traits\npub mod prelude {\n    pub use crate::shared::{HyprData, HyprDataActive, HyprDataActiveOptional, HyprDataVec};\n    pub use hyprland_macros::async_closure;\n}\n\nmod async_import {\n    #[cfg(all(feature = \"async-lite\", not(feature = \"tokio\")))]\n    pub use async_net::unix::UnixStream;\n    #[cfg(all(feature = \"async-lite\", not(feature = \"tokio\")))]\n    pub use futures_lite::io::{AsyncReadExt, AsyncWriteExt};\n    #[cfg(feature = \"tokio\")]\n    pub use tokio::{io::AsyncReadExt, io::AsyncWriteExt, net::UnixStream};\n}\n\n/// This type provides the result type used everywhere in Hyprland-rs\npub type Result<T> = std::result::Result<T, HyprError>;\n\nstatic DEFAULT_INSTANCE: OnceLock<Instance> = OnceLock::new();\n\n/// Returns the result of the DEFAULT_INSTANCE OnceLock\npub fn default_instance() -> std::result::Result<&'static Instance, HyprError> {\n    if let Some(i) = DEFAULT_INSTANCE.get() {\n        return Ok(i);\n    }\n    let instance = Instance::from_current_env()?;\n    let _ = DEFAULT_INSTANCE.set(instance);\n    #[allow(clippy::unwrap_used)] // We just set the instance, so it can never fail\n    Ok(DEFAULT_INSTANCE.get().unwrap())\n}\n\n/// Returns the result of the DEFAULT_INSTANCE OnceLock\npub fn default_instance_panic() -> &'static Instance {\n    #[allow(clippy::expect_used)]\n    default_instance().expect(\n        \"Default instance could not get initialized, use `Instance::from_instance()` instead.\",\n    )\n}\n"
  },
  {
    "path": "dep-crates/hyprland-rs/src/shared.rs",
    "content": "//! # The Shared Module\n//!\n//! This module provides shared private and public functions, structs, enum, and types\nuse derive_more::Display;\nuse serde::{Deserialize, Serialize};\nuse std::hash::{Hash, Hasher};\nuse std::path::PathBuf;\nuse std::{env, fmt};\n\n/// The address struct holds a address as a tuple with a single value\n/// and has methods to reveal the address in different data formats\n#[derive(\n    Debug, Deserialize, Serialize, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, derive_more::Display,\n)]\npub struct Address(String);\nimpl Address {\n    #[inline(always)]\n    pub(crate) fn fmt_new(address: &str) -> Self {\n        // this way is faster than std::fmt\n        Self(\"0x\".to_owned() + address)\n    }\n    /// This creates a new address from a value that implements [ToString]\n    pub fn new<T: ToString>(string: T) -> Self {\n        let str = string.to_string();\n        if str.starts_with(\"0x\") {\n            Self(str)\n        } else {\n            Self(\"0x\".to_owned() + str.as_str())\n        }\n    }\n}\n\n/// This trait provides a standardized way to get data\npub trait HyprData {\n    /// This method gets the data\n    fn get() -> crate::Result<Self>\n    where\n        Self: Sized;\n    /// This method gets the data (async)\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    async fn get_async() -> crate::Result<Self>\n    where\n        Self: Sized;\n    /// This method gets the data\n    fn instance_get(instance: &Instance) -> crate::Result<Self>\n    where\n        Self: Sized;\n    /// This method gets the data (async)\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    async fn instance_get_async(instance: &Instance) -> crate::Result<Self>\n    where\n        Self: Sized;\n}\n\n/// This trait provides a standardized way to get data in a from of a vector\npub trait HyprDataVec<T>: HyprData {\n    /// This method returns a vector of data\n    fn to_vec(self) -> Vec<T>;\n}\n\n/// Trait for helper functions to get the active of the implementor\npub trait HyprDataActive {\n    /// This method gets the active data\n    fn get_active() -> crate::Result<Self>\n    where\n        Self: Sized;\n    /// This method gets the active data (async)\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    async fn get_active_async() -> crate::Result<Self>\n    where\n        Self: Sized;\n    /// This method gets the active data\n    fn instance_get_active(instance: &Instance) -> crate::Result<Self>\n    where\n        Self: Sized;\n    /// This method gets the active data (async)\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    async fn instance_get_active_async(instance: &Instance) -> crate::Result<Self>\n    where\n        Self: Sized;\n}\n\n/// Trait for helper functions to get the active of the implementor, but for optional ones\npub trait HyprDataActiveOptional {\n    /// This method gets the active data\n    fn get_active() -> crate::Result<Option<Self>>\n    where\n        Self: Sized;\n    /// This method gets the active data (async)\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    async fn get_active_async() -> crate::Result<Option<Self>>\n    where\n        Self: Sized;\n    /// This method gets the active data\n    fn instance_get_active(instance: &Instance) -> crate::Result<Option<Self>>\n    where\n        Self: Sized;\n    /// This method gets the active data (async)\n    #[cfg(any(feature = \"async-lite\", feature = \"tokio\"))]\n    async fn instance_get_active_async(instance: &Instance) -> crate::Result<Option<Self>>\n    where\n        Self: Sized;\n}\n\n/// This type provides the id used to identify workspaces\n/// > its a type because it might change at some point\npub type WorkspaceId = i32;\n\n/// This type provides the id used to identify monitors\n/// > its a type because it might change at some point\npub type MonitorId = i128;\n\n#[inline]\nfn ser_spec_opt(opt: &Option<String>) -> String {\n    match opt {\n        Some(name) => \"special:\".to_owned() + name,\n        None => \"special\".to_owned(),\n    }\n}\n\n/// This enum holds workspace data\n#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Display, PartialOrd, Ord)]\n#[serde(untagged)]\npub enum WorkspaceType {\n    /// A named workspace\n    Regular(\n        /// The name\n        String,\n    ),\n    /// The special workspace\n    #[display(\"{}\", ser_spec_opt(_0))]\n    Special(\n        /// The name, if exists\n        Option<String>,\n    ),\n}\n\nimpl From<&WorkspaceType> for String {\n    fn from(value: &WorkspaceType) -> Self {\n        value.to_string()\n    }\n}\nmacro_rules! from {\n    ($($ty:ty),+$(,)?) => {\n        $(\n            impl TryFrom<$ty> for WorkspaceType {\n                type Error = crate::error::HyprError;\n                fn try_from(int: $ty) -> Result<Self, Self::Error> {\n                    match int {\n                        1.. => Ok(WorkspaceType::Regular(int.to_string())),\n                        _ => crate::error::hypr_err!(\"Conversion error: Unrecognised id\"),\n                    }\n                }\n            }\n        )+\n    };\n}\nfrom![u8, u16, u32, u64, usize, i8, i16, i32, i64, isize];\n\nimpl Hash for WorkspaceType {\n    fn hash<H: Hasher>(&self, state: &mut H) {\n        match self {\n            WorkspaceType::Regular(name) => name.hash(state),\n            WorkspaceType::Special(value) => match value {\n                Some(name) => name.hash(state),\n                None => \"\".hash(state),\n            },\n        }\n    }\n}\n\npub(crate) fn get_hypr_path() -> crate::Result<PathBuf> {\n    let mut buf = if let Some(runtime_path) = env::var_os(\"XDG_RUNTIME_DIR\") {\n        std::path::PathBuf::from(runtime_path)\n    } else if let Ok(uid) = env::var(\"UID\") {\n        std::path::PathBuf::from(\"/run/user/\".to_owned() + &uid)\n    } else {\n        hypr_err!(\"Could not find XDG_RUNTIME_DIR or UID\");\n    };\n    buf.push(\"hypr\");\n    Ok(buf)\n}\n\n/// This enum defines the possible command flags that can be used.\n#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]\npub enum CommandFlag {\n    /// The JSON flag.\n    #[default]\n    JSON,\n    /// An empty flag.\n    Empty,\n}\n\n/// This struct defines the content of a command, which consists of a flag and a data string.\n#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]\npub struct CommandContent {\n    /// The flag for the command.\n    pub flag: CommandFlag,\n    /// The data string for the command.\n    pub data: String,\n}\n\nimpl CommandContent {\n    /// Converts the command content to a byte vector.\n    ///\n    /// # Examples\n    ///\n    /// ```\n    /// use hyprland::shared::*;\n    ///\n    /// let content = CommandContent { flag: CommandFlag::JSON, data: \"foo\".to_string() };\n    /// let bytes = content.as_bytes();\n    /// assert_eq!(bytes, b\"j/foo\");\n    /// ```\n    pub fn as_bytes(&self) -> Vec<u8> {\n        self.to_string().into_bytes()\n    }\n}\n\nimpl fmt::Display for CommandContent {\n    /// Formats the command content as a string for display.\n    ///\n    /// # Examples\n    ///\n    /// ```\n    /// use hyprland::shared::*;\n    ///\n    /// let content = CommandContent { flag: CommandFlag::JSON, data: \"foo\".to_string() };\n    /// let s = format!(\"{}\", content);\n    /// assert_eq!(s, \"j/foo\");\n    /// ```\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        match &self.flag {\n            CommandFlag::JSON => write!(f, \"j/{}\", &self.data),\n            CommandFlag::Empty => write!(f, \"/{}\", &self.data),\n        }\n    }\n}\n\n/// Creates a `CommandContent` instance with the given flag and formatted data.\n///\n/// # Arguments\n///\n/// * `$flag` - A `CommandFlag` variant (`JSON` or `Empty`) that represents the flag for the command.\n/// * `$($k:tt)*` - A format string and its arguments to be used as the data in the `CommandContent` instance.\n#[macro_export]\nmacro_rules! command {\n    ($flag:ident, $($k:tt)*) => {{\n        $crate::shared::CommandContent {\n            flag: $crate::shared::CommandFlag::$flag,\n            data: format!($($k)*),\n        }\n    }};\n}\nuse crate::error::hypr_err;\nuse crate::instance::Instance;\npub use command;\n"
  },
  {
    "path": "dep-crates/hyprland-rs/src/unsafe_impl.rs",
    "content": "//! This module provides unsafe impls for several types, mainly for FFI purposes. Do not use unless you know what you are doing.\n\n/// unsafe implementations for event listener structs\n#[cfg(feature = \"listener\")]\npub mod listeners {\n    use crate::event_listener::*;\n\n    unsafe impl Send for AsyncEventListener {}\n    unsafe impl Sync for AsyncEventListener {}\n\n    unsafe impl Send for EventListener {}\n    unsafe impl Sync for EventListener {}\n\n    unsafe impl Send for WindowMoveEvent {}\n    unsafe impl Sync for WindowMoveEvent {}\n\n    unsafe impl Send for WindowOpenEvent {}\n    unsafe impl Sync for WindowOpenEvent {}\n\n    unsafe impl Send for LayoutEvent {}\n    unsafe impl Sync for LayoutEvent {}\n\n    unsafe impl Send for State {}\n    unsafe impl Sync for State {}\n\n    unsafe impl Send for WindowEventData {}\n    unsafe impl Sync for WindowEventData {}\n\n    unsafe impl Send for MonitorEventData {}\n    unsafe impl Sync for MonitorEventData {}\n\n    unsafe impl Send for WindowFloatEventData {}\n    unsafe impl Sync for WindowFloatEventData {}\n}\n"
  },
  {
    "path": "dep-crates/hyprland-rs/test.sh",
    "content": "#!/usr/bin/env bash\nset -euo pipefail\n\nexport RUSTFLAGS=-Awarnings\n\n# Define the features as an array\ndeclare -a features=(\"listener\" \"dispatch\" \"data\" \"keyword\" \"config\" \"ctl\")\ndeclare -a async=(\"tokio\" \"async-lite\" \"\")\n# Get the total number of features\nnum_features=${#features[@]}\nnum_async=${#async[@]}\n\n# print total number of features and async options (num_features * num_async)\necho \"Total number of features: $num_features, combination: $((1 << num_features))\"\necho \"Total number of async options: $num_async, combination: $((num_async * (1 << num_features)))\"\n\n# Function to build with a specific combination of features\nbuild_with_features() {\n  local feature_combination=\"$1\"\n  local async_feature=\"$2\"\n  local iteration=\"$3\"\n\n  if [[ -z \"$feature_combination\" ]]; then\n    echo \"[$iteration/$async_feature] Building without any features...\"\n    cargo build --no-default-features --quiet\n  else\n    echo \"[$iteration/$async_feature] Building with features: $feature_combination\"\n    cargo build --no-default-features --quiet --features=\"$feature_combination\" --features=\"$async_feature\"\n  fi\n}\n\n# Generate all combinations of features\nfor async_feature in \"${async[@]}\"; do\n  echo \"\"\n  echo \"async: $async_feature, num_features: $num_features, iterations: $((1 << num_features))\"\n  for ((i = 0; i < (1 << num_features); i++)); do\n    combination=()\n    for ((j = num_features - 1; j >= 0; j--)); do\n      if ((i & (1 << j))); then\n        combination+=(\"${features[j]}\")\n      fi\n    done\n    build_with_features \"$(IFS=,; printf '%s' \"${combination[*]}\")\" \"$async_feature\" \"$i\"\n  done\ndone\n\n# some special cases\nbuild_with_features \"ahash\" \"\" \"special-case-1\"\nbuild_with_features \"ahash, listener, dispatch, data, keyword, config, ctl\" \"\" \"special-case-1\"\nbuild_with_features \"parking_lot\" \"\" \"special-case-2\"\nbuild_with_features \"parking_lot\" \"tokio\" \"special-case-3\"\nbuild_with_features \"unsafe-impl\" \"\" \"special-case-4\"\n\necho \"all features tested with all async tested\""
  },
  {
    "path": "dep-crates/hyprland-rs/treefmt.toml",
    "content": "[formatter.rust]\ncommand = \"rustfmt\"\noptions = [ \"--edition\", \"2021\" ]\nincludes = [\"*.rs\"]\n\n[formatter.markdown]\ncommand = \"cbfmt\"\nincludes = [\"*.md\"]\n"
  },
  {
    "path": "dep-crates/wl-clipboard-rs/.github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"cargo\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n    groups:\n      rust-dependencies:\n        update-types:\n          - \"minor\"\n          - \"patch\"\n\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n"
  },
  {
    "path": "dep-crates/wl-clipboard-rs/.github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  push:\n  pull_request:\n  workflow_dispatch:\n  schedule:\n    - cron: '0 0 1 * *' # Monthly\n\njobs:\n  build:\n    strategy:\n      fail-fast: false\n\n      matrix:\n        rust: [stable, beta]\n        features: ['', dlopen]\n\n    name: ${{ matrix.rust }} - ${{ matrix.features }}\n    runs-on: ubuntu-24.04\n\n    steps:\n      - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0\n        with:\n          show-progress: false\n\n      - name: Install Rust\n        uses: dtolnay/rust-toolchain@master\n        with:\n          toolchain: ${{ matrix.rust }}\n\n      - uses: Swatinem/rust-cache@v2\n\n      - name: Build\n        run: cargo build --all --features=${{ matrix.features }}\n\n      - name: Set up XDG_RUNTIME_DIR\n        run: |\n          mkdir .runtime\n          echo \"XDG_RUNTIME_DIR=$PWD/.runtime\" >> \"$GITHUB_ENV\"\n\n      - name: Test\n        run: cargo test --all --features=${{ matrix.features }}\n\n      - name: Generate documentation\n        run: cargo doc --features=${{ matrix.features }}\n\n      - name: Copy documentation index\n        run: cp doc/index.html target/doc/\n\n      - name: Deploy documentation\n        if: >\n          matrix.rust == 'stable' &&\n          matrix.features == '' &&\n          github.event_name == 'push' &&\n          github.ref == 'refs/heads/master'\n        uses: peaceiris/actions-gh-pages@v4\n        with:\n          github_token: ${{ secrets.GITHUB_TOKEN }}\n          publish_dir: ./target/doc\n\n  clippy:\n    name: clippy\n    runs-on: ubuntu-24.04\n\n    steps:\n      - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0\n        with:\n          show-progress: false\n\n      - name: Install Rust\n        uses: dtolnay/rust-toolchain@stable\n        with:\n          components: clippy\n\n      - uses: Swatinem/rust-cache@v2\n\n      - name: Run clippy\n        run: cargo clippy --all --all-targets\n\n  rustfmt:\n    runs-on: ubuntu-24.04\n\n    steps:\n      - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0\n        with:\n          show-progress: false\n\n      - name: Install Rust\n        uses: dtolnay/rust-toolchain@nightly\n        with:\n          components: rustfmt\n\n      - name: Run rustfmt\n        run: cargo fmt --all -- --check\n"
  },
  {
    "path": "dep-crates/wl-clipboard-rs/.gitignore",
    "content": "/target\n**/*.rs.bk\n"
  },
  {
    "path": "dep-crates/wl-clipboard-rs/CHANGELOG.md",
    "content": "# Changelog\n\n## Unreleased\n\n## v0.9.2 (14th Mar 2025)\n\n- Added support for the `ext-data-control` protocol. It will be used instead of `wlr-data-control` when available.\n- Updated dependencies.\n\n## v0.9.1 (6th Oct 2024)\n\n- Added man page and shell completion generation to `wl-clipboard-rs-tools`.\n- Updated dependencies.\n\n## v0.9.0 (19th June 2024)\n\n- **Breaking** Removed `utils::copy_data`. It forked into a `/usr/bin/env cat`\n  for copying. All internal uses of the function have been changed to simply\n  use `std::io::copy` instead.\n- Replaced `nix` with `rustix`, following `wayland-rs`.\n- Replaced the deprecated `structopt` with `clap` itself.\n- Updated dependencies.\n\n## v0.8.1 (7th Mar 2024)\n\n- Updated dependencies, notably `nix`, which fixes building on LoongArch.\n\n## v0.8.0 (3rd Oct 2023)\n\n- Added `copy::Options::omit_additional_text_mime_types` to disable\n  wl-clipboard-rs offering several known text MIME types when a text MIME type\n  is copied.\n- Updated `wayland-rs` to 0.31.\n  - **Breaking** This changed the error types slightly. However, most uses of\n    wl-clipboard-rs should be completely unaffected.\n- Updated other dependencies.\n\n## v0.7.0 (23rd Sep 2022)\n\n- Fixed `paste::get_contents()` leaving behind zombie `cat` processes.\n- Changed debug logging from `info!` to `trace!`.\n- Bumped `nix` dependency to `0.24` to match that of the wayland-rs crates.\n- Replaced `derive_more` with `thiserror`.\n\n## v0.6.0 (20th Mar 2022)\n\n- Fixed `wl-copy` and `wl-clip` hangs when followed by a pipe (e.g. `wl-copy\n  hello | cat`).\n- Removed the deprecated `failure` dependency from both the library and the\n  tools. The standard `Error` trait is now used.\n- Replaced underscores back with dashes in the tool binary names.\n- Renamed `wl-clipboard-tools` subcrate to `wl-clipboard-rs-tools`.\n\n## v0.5.0 (13th Mar 2022)\n\n- Split binaries from the main crate `wl-clipboard-rs` into a new sub-crate\n  `wl-clipboard-tools`. This removes a few dependencies that were only used in\n  the binaries (like `structopt`).\n  - This change also unintentionally replaced dashes with underscores in tool\n    binary names.\n- Replaced `tree_magic` (which went unmaintained) with `tree_magic_mini`.\n- Changed the `fork` code which runs during the copy operation to exec\n  `/usr/bin/env cat` instead of just `cat`. This was done to remove\n  a non-async-signal-safe call in the child process.\n- Updated dependencies.\n\n## v0.4.1 (1st Sep 2020)\n\n- Updated `nix` to 0.18 and `wayland-rs` to 0.27.\n\n## v0.4.0 (13th Dec 2019)\n\n- **Breaking** Copying in non-foreground mode no longer forks (which was\n  **unsafe** in multi-threaded programs). Instead, it spawns a background\n  thread to serve copy requests.\n- Added `copy::prepare_copy()` and `copy::prepare_copy_multi()` (and respective\n  functions in `copy::Options`) to accommodate workflows which depended on the\n  forking behavior, such as `wl-copy`. See `wl-copy` for example usage.\n- **Breaking** Changed `copy::Source` and `copy::Seat` to own the contained\n  data rather than borrow it. As a consequence, those types, as well as\n  `copy::MimeSource` and `copy::Options`, have dropped their lifetime generic\n  parameter.\n\n## v0.3.1 (27th Nov 2019)\n\n- Reduced the `wl_seat` version requirement from 6 to 2.\n- Added `copy::copy_multi()` for offering multiple data sources under multiple\n  different MIME types.\n\n## v0.3.0 (4th Apr 2019)\n\n- **Breaking** Moved `ClipboardType` into `copy::` and `paste::`.\n- **Breaking** Renamed `utils::Error` into `utils::CopyDataError`.\n- Added `copy::ClipboardType::Both` for operating both clipboards at once.\n- Added `utils::is_primary_selection_supported()`.\n- [wl-copy]: added `--regular`, which, when set together with `--primary`,\n  makes `wl-copy` operate on both clipboards at once.\n\n## v0.2.0 (17th Feb 2019)\n\n- **Breaking** Changed `copy::Options::paste_once` to `serve_requests` which\n  allows to specify the number of paste requests to serve.\n- Marked `copy::Seat` and `copy::Options` as `Copy`.\n- Updated `data-control`, it's now merged into `wlr-protocols` so no further\n  changes without a version bump.\n- [wl-copy, wl-paste]: replaced `env_logger` with `stderrlog` which made the\n  binaries much smaller.\n- Implemented `wl-clip`, a Wayland version of `xclip`.\n\n## v0.1.0 (12th Feb 2019)\n\n- Initial release.\n"
  },
  {
    "path": "dep-crates/wl-clipboard-rs/Cargo.toml",
    "content": "[package]\nversion = \"4.9.5\" # remember to update html_root_url\nauthors = [\"Ivan Molodetskikh <yalterz@gmail.com>\"]\nedition = \"2021\"\nlicense = \"MIT/Apache-2.0\"\nrepository = \"https://github.com/YaLTeR/wl-clipboard-rs\"\nkeywords = [\"wayland\", \"clipboard\"]\nname = \"hyprshell-wl-clipboard-rs\"\ndescription = \"Access to the Wayland clipboard for terminal and other window-less applications.\"\n\nreadme = \"README.md\"\ndocumentation = \"https://docs.rs/wl-clipboard-rs\"\ncategories = [\"os\"]\n\n[dependencies]\nlibc = \"0.2.170\"\nlog = \"0.4.26\"\nos_pipe = { version = \"1.2.1\", features = [\"io_safety\"] }\nrustix = { version = \"0.38.44\", features = [\"fs\", \"event\"] }\ntempfile = \"3.17.1\"\nthiserror = \"2\"\ntree_magic_mini = \"3.1.6\"\nwayland-backend = \"0.3.8\"\nwayland-client = \"0.31.8\"\nwayland-protocols = { version = \"0.32.6\", features = [\"client\", \"staging\"] }\nwayland-protocols-wlr = { version = \"0.3.6\", features = [\"client\"] }\n\n[dev-dependencies]\nwayland-server = \"0.31.7\"\nwayland-protocols = { version = \"0.32.6\", features = [\"server\", \"staging\"] }\nwayland-protocols-wlr = { version = \"0.3.6\", features = [\"server\"] }\nproptest = \"1.6.0\"\nproptest-derive = \"0.5.1\"\n\n[features]\n# Link to libwayland-client.so instead of using the Rust implementation.\nnative_lib = [\"wayland-backend/client_system\", \"wayland-backend/server_system\"]\n\ndlopen = [\"native_lib\", \"wayland-backend/dlopen\", \"wayland-backend/dlopen\"]\n"
  },
  {
    "path": "dep-crates/wl-clipboard-rs/LICENSE-APACHE",
    "content": "                              Apache License\n                        Version 2.0, January 2004\n                     http://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n   \"License\" shall mean the terms and conditions for use, reproduction,\n   and distribution as defined by Sections 1 through 9 of this document.\n\n   \"Licensor\" shall mean the copyright owner or entity authorized by\n   the copyright owner that is granting the License.\n\n   \"Legal Entity\" shall mean the union of the acting entity and all\n   other entities that control, are controlled by, or are under common\n   control with that entity. For the purposes of this definition,\n   \"control\" means (i) the power, direct or indirect, to cause the\n   direction or management of such entity, whether by contract or\n   otherwise, or (ii) ownership of fifty percent (50%) or more of the\n   outstanding shares, or (iii) beneficial ownership of such entity.\n\n   \"You\" (or \"Your\") shall mean an individual or Legal Entity\n   exercising permissions granted by this License.\n\n   \"Source\" form shall mean the preferred form for making modifications,\n   including but not limited to software source code, documentation\n   source, and configuration files.\n\n   \"Object\" form shall mean any form resulting from mechanical\n   transformation or translation of a Source form, including but\n   not limited to compiled object code, generated documentation,\n   and conversions to other media types.\n\n   \"Work\" shall mean the work of authorship, whether in Source or\n   Object form, made available under the License, as indicated by a\n   copyright notice that is included in or attached to the work\n   (an example is provided in the Appendix below).\n\n   \"Derivative Works\" shall mean any work, whether in Source or Object\n   form, that is based on (or derived from) the Work and for which the\n   editorial revisions, annotations, elaborations, or other modifications\n   represent, as a whole, an original work of authorship. For the purposes\n   of this License, Derivative Works shall not include works that remain\n   separable from, or merely link (or bind by name) to the interfaces of,\n   the Work and Derivative Works thereof.\n\n   \"Contribution\" shall mean any work of authorship, including\n   the original version of the Work and any modifications or additions\n   to that Work or Derivative Works thereof, that is intentionally\n   submitted to Licensor for inclusion in the Work by the copyright owner\n   or by an individual or Legal Entity authorized to submit on behalf of\n   the copyright owner. For the purposes of this definition, \"submitted\"\n   means any form of electronic, verbal, or written communication sent\n   to the Licensor or its representatives, including but not limited to\n   communication on electronic mailing lists, source code control systems,\n   and issue tracking systems that are managed by, or on behalf of, the\n   Licensor for the purpose of discussing and improving the Work, but\n   excluding communication that is conspicuously marked or otherwise\n   designated in writing by the copyright owner as \"Not a Contribution.\"\n\n   \"Contributor\" shall mean Licensor and any individual or Legal Entity\n   on behalf of whom a Contribution has been received by Licensor and\n   subsequently incorporated within the Work.\n\n2. Grant of Copyright License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   copyright license to reproduce, prepare Derivative Works of,\n   publicly display, publicly perform, sublicense, and distribute the\n   Work and such Derivative Works in Source or Object form.\n\n3. Grant of Patent License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   (except as stated in this section) patent license to make, have made,\n   use, offer to sell, sell, import, and otherwise transfer the Work,\n   where such license applies only to those patent claims licensable\n   by such Contributor that are necessarily infringed by their\n   Contribution(s) alone or by combination of their Contribution(s)\n   with the Work to which such Contribution(s) was submitted. If You\n   institute patent litigation against any entity (including a\n   cross-claim or counterclaim in a lawsuit) alleging that the Work\n   or a Contribution incorporated within the Work constitutes direct\n   or contributory patent infringement, then any patent licenses\n   granted to You under this License for that Work shall terminate\n   as of the date such litigation is filed.\n\n4. Redistribution. You may reproduce and distribute copies of the\n   Work or Derivative Works thereof in any medium, with or without\n   modifications, and in Source or Object form, provided that You\n   meet the following conditions:\n\n   (a) You must give any other recipients of the Work or\n       Derivative Works a copy of this License; and\n\n   (b) You must cause any modified files to carry prominent notices\n       stating that You changed the files; and\n\n   (c) You must retain, in the Source form of any Derivative Works\n       that You distribute, all copyright, patent, trademark, and\n       attribution notices from the Source form of the Work,\n       excluding those notices that do not pertain to any part of\n       the Derivative Works; and\n\n   (d) If the Work includes a \"NOTICE\" text file as part of its\n       distribution, then any Derivative Works that You distribute must\n       include a readable copy of the attribution notices contained\n       within such NOTICE file, excluding those notices that do not\n       pertain to any part of the Derivative Works, in at least one\n       of the following places: within a NOTICE text file distributed\n       as part of the Derivative Works; within the Source form or\n       documentation, if provided along with the Derivative Works; or,\n       within a display generated by the Derivative Works, if and\n       wherever such third-party notices normally appear. The contents\n       of the NOTICE file are for informational purposes only and\n       do not modify the License. You may add Your own attribution\n       notices within Derivative Works that You distribute, alongside\n       or as an addendum to the NOTICE text from the Work, provided\n       that such additional attribution notices cannot be construed\n       as modifying the License.\n\n   You may add Your own copyright statement to Your modifications and\n   may provide additional or different license terms and conditions\n   for use, reproduction, or distribution of Your modifications, or\n   for any such Derivative Works as a whole, provided Your use,\n   reproduction, and distribution of the Work otherwise complies with\n   the conditions stated in this License.\n\n5. Submission of Contributions. Unless You explicitly state otherwise,\n   any Contribution intentionally submitted for inclusion in the Work\n   by You to the Licensor shall be under the terms and conditions of\n   this License, without any additional terms or conditions.\n   Notwithstanding the above, nothing herein shall supersede or modify\n   the terms of any separate license agreement you may have executed\n   with Licensor regarding such Contributions.\n\n6. Trademarks. This License does not grant permission to use the trade\n   names, trademarks, service marks, or product names of the Licensor,\n   except as required for reasonable and customary use in describing the\n   origin of the Work and reproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty. Unless required by applicable law or\n   agreed to in writing, Licensor provides the Work (and each\n   Contributor provides its Contributions) on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n   implied, including, without limitation, any warranties or conditions\n   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n   PARTICULAR PURPOSE. You are solely responsible for determining the\n   appropriateness of using or redistributing the Work and assume any\n   risks associated with Your exercise of permissions under this License.\n\n8. Limitation of Liability. In no event and under no legal theory,\n   whether in tort (including negligence), contract, or otherwise,\n   unless required by applicable law (such as deliberate and grossly\n   negligent acts) or agreed to in writing, shall any Contributor be\n   liable to You for damages, including any direct, indirect, special,\n   incidental, or consequential damages of any character arising as a\n   result of this License or out of the use or inability to use the\n   Work (including but not limited to damages for loss of goodwill,\n   work stoppage, computer failure or malfunction, or any and all\n   other commercial damages or losses), even if such Contributor\n   has been advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability. While redistributing\n   the Work or Derivative Works thereof, You may choose to offer,\n   and charge a fee for, acceptance of support, warranty, indemnity,\n   or other liability obligations and/or rights consistent with this\n   License. However, in accepting such obligations, You may act only\n   on Your own behalf and on Your sole responsibility, not on behalf\n   of any other Contributor, and only if You agree to indemnify,\n   defend, and hold each Contributor harmless for any liability\n   incurred by, or claims asserted against, such Contributor by reason\n   of your accepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n\nAPPENDIX: How to apply the Apache License to your work.\n\n   To apply the Apache License to your work, attach the following\n   boilerplate notice, with the fields enclosed by brackets \"[]\"\n   replaced with your own identifying information. (Don't include\n   the brackets!)  The text should be enclosed in the appropriate\n   comment syntax for the file format. We also recommend that a\n   file or class name and description of purpose be included on the\n   same \"printed page\" as the copyright notice for easier\n   identification within third-party archives.\n\nCopyright [yyyy] [name of copyright owner]\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n\thttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n"
  },
  {
    "path": "dep-crates/wl-clipboard-rs/LICENSE-MIT",
    "content": "Copyright (c) 2019 Ivan Molodetskikh\n\nPermission is hereby granted, free of charge, to any\nperson obtaining a copy of this software and associated\ndocumentation files (the \"Software\"), to deal in the\nSoftware without restriction, including without\nlimitation the rights to use, copy, modify, merge,\npublish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software\nis furnished to do so, subject to the following\nconditions:\n\nThe above copyright notice and this permission notice\nshall be included in all copies or substantial portions\nof the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF\nANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED\nTO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\nPARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT\nSHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR\nIN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "dep-crates/wl-clipboard-rs/README.md",
    "content": "# wl-clipboard-rs\n\n[![crates.io](https://img.shields.io/crates/v/wl-clipboard-rs.svg)](https://crates.io/crates/wl-clipboard-rs)\n[![Build Status](https://github.com/YaLTeR/wl-clipboard-rs/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/YaLTeR/wl-clipboard-rs/actions/workflows/ci.yml?query=branch%3Amaster)\n[![Documentation](https://docs.rs/wl-clipboard-rs/badge.svg)](https://docs.rs/wl-clipboard-rs)\n\n[Documentation (master)](https://yalter.github.io/wl-clipboard-rs/wl_clipboard_rs/)\n\nA safe Rust crate for working with the Wayland clipboard.\n\nThis crate is intended to be used by terminal applications, clipboard managers and other\nutilities which don't spawn Wayland surfaces (windows). If your application has a window,\nplease use the appropriate Wayland protocols for interacting with the Wayland clipboard\n(`wl_data_device` from the core Wayland protocol, the `primary_selection` protocol for the\nprimary selection), for example via the\n[smithay-clipboard](https://crates.io/crates/smithay-clipboard) crate.\n\nThe protocol used for clipboard interaction is `ext-data-control` or `wlr-data-control`. When\nusing the regular clipboard, the compositor must support any version of either protocol. When\nusing the \"primary\" clipboard, the compositor must support any version of `ext-data-control`,\nor the second version of the `wlr-data-control` protocol.\n\nFor example applications using these features, see `wl-clipboard-rs-tools/src/bin/wl_copy.rs`\nand `wl-clipboard-rs-tools/src/bin/wl_paste.rs` which implement terminal apps similar to\n[wl-clipboard](https://github.com/bugaevc/wl-clipboard) or\n`wl-clipboard-rs-tools/src/bin/wl_clip.rs` which implements a Wayland version of `xclip`.\n\nThe Rust implementation of the Wayland client is used by default; use the `native_lib` feature\nto link to `libwayland-client.so` for communication instead. A `dlopen` feature is also\navailable for loading `libwayland-client.so` dynamically at runtime rather than linking to it.\n\nThe code of the crate itself (and the code of the example utilities) is 100% safe Rust. This\ndoesn't include the dependencies.\n\n## Examples\n\nCopying to the regular clipboard:\n```rust\nuse wl_clipboard_rs::copy::{MimeType, Options, Source};\n\nlet opts = Options::new();\nopts.copy(Source::Bytes(\"Hello world!\".to_string().into_bytes().into()), MimeType::Autodetect)?;\n```\n\nPasting plain text from the regular clipboard:\n```rust\nuse std::io::Read;\nuse wl_clipboard_rs::{paste::{get_contents, ClipboardType, Error, MimeType, Seat}};\n\nlet result = get_contents(ClipboardType::Regular, Seat::Unspecified, MimeType::Text);\nmatch result {\n    Ok((mut pipe, _)) => {\n        let mut contents = vec![];\n        pipe.read_to_end(&mut contents)?;\n        println!(\"Pasted: {}\", String::from_utf8_lossy(&contents));\n    }\n\n    Err(Error::NoSeats) | Err(Error::ClipboardEmpty) | Err(Error::NoMimeType) => {\n        // The clipboard is empty or doesn't contain text, nothing to worry about.\n    }\n\n    Err(err) => Err(err)?\n}\n```\n\nChecking if the \"primary\" clipboard is supported (note that this might be unnecessary depending\non your crate usage, the regular copying and pasting functions do report if the primary\nselection is unsupported when it is requested):\n\n```rust\nuse wl_clipboard_rs::utils::{is_primary_selection_supported, PrimarySelectionCheckError};\n\nmatch is_primary_selection_supported() {\n    Ok(supported) => {\n        // We have our definitive result. False means that ext/wlr-data-control is present\n        // and did not signal the primary selection support, or that only wlr-data-control\n        // version 1 is present (which does not support primary selection).\n    },\n    Err(PrimarySelectionCheckError::NoSeats) => {\n        // Impossible to give a definitive result. Primary selection may or may not be\n        // supported.\n\n        // The required protocol (ext-data-control, or wlr-data-control version 2) is there,\n        // but there are no seats. Unfortunately, at least one seat is needed to check for the\n        // primary clipboard support.\n    },\n    Err(PrimarySelectionCheckError::MissingProtocol) => {\n        // The data-control protocol (required for wl-clipboard-rs operation) is not\n        // supported by the compositor.\n    },\n    Err(_) => {\n        // Some communication error occurred.\n    }\n}\n```\n\n## Included terminal utilities\n\n- `wl-paste`: implements `wl-paste` from\n  [wl-clipboard](https://github.com/bugaevc/wl-clipboard).\n- `wl-copy`: implements `wl-copy` from [wl-clipboard](https://github.com/bugaevc/wl-clipboard).\n- `wl-clip`: a Wayland version of `xclip`.\n\nStuff that would be neat to add:\n- Utility that mimics `xsel` commandline flags.\n\nLicense: MIT/Apache-2.0\n"
  },
  {
    "path": "dep-crates/wl-clipboard-rs/README.tpl",
    "content": "# {{crate}}\n\n[![crates.io](https://img.shields.io/crates/v/wl-clipboard-rs.svg)](https://crates.io/crates/wl-clipboard-rs)\n[![Build Status](https://github.com/YaLTeR/wl-clipboard-rs/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/YaLTeR/wl-clipboard-rs/actions/workflows/ci.yml?query=branch%3Amaster)\n[![Documentation](https://docs.rs/wl-clipboard-rs/badge.svg)](https://docs.rs/wl-clipboard-rs)\n\n[Documentation (master)](https://yalter.github.io/wl-clipboard-rs/wl_clipboard_rs/)\n\n{{readme}}\n\nStuff that would be neat to add:\n- Utility that mimics `xsel` commandline flags.\n\nLicense: {{license}}\n"
  },
  {
    "path": "dep-crates/wl-clipboard-rs/doc/index.html",
    "content": "<!DOCTYPE HTML>\n<html lang=\"en-US\">\n    <head>\n        <meta charset=\"UTF-8\">\n        <meta http-equiv=\"refresh\" content=\"0; url=wl_clipboard_rs/index.html\">\n        <title>Page Redirection</title>\n    </head>\n    <body>\n        If you are not redirected automatically, follow this <a href='wl_clipboard_rs/index.html'>link</a>.\n    </body>\n</html>\n"
  },
  {
    "path": "dep-crates/wl-clipboard-rs/rustfmt.toml",
    "content": "imports_granularity = \"Module\"\ngroup_imports = \"StdExternalCrate\"\n"
  },
  {
    "path": "dep-crates/wl-clipboard-rs/src/common.rs",
    "content": "use std::collections::HashMap;\nuse std::ffi::OsString;\nuse std::os::unix::net::UnixStream;\nuse std::path::PathBuf;\nuse std::{env, io};\n\nuse wayland_backend::client::WaylandError;\nuse wayland_client::globals::{registry_queue_init, GlobalError, GlobalListContents};\nuse wayland_client::protocol::wl_registry::WlRegistry;\nuse wayland_client::protocol::wl_seat::{self, WlSeat};\nuse wayland_client::{ConnectError, Connection, Dispatch, EventQueue, Proxy};\nuse wayland_protocols::ext::data_control::v1::client::ext_data_control_manager_v1::ExtDataControlManagerV1;\nuse wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_manager_v1::ZwlrDataControlManagerV1;\n\nuse crate::data_control::Manager;\nuse crate::seat_data::SeatData;\n\npub struct State {\n    pub seats: HashMap<WlSeat, SeatData>,\n    pub clipboard_manager: Manager,\n}\n\n#[derive(thiserror::Error, Debug)]\npub enum Error {\n    #[error(\"Couldn't open the provided Wayland socket\")]\n    SocketOpenError(#[source] io::Error),\n\n    #[error(\"Couldn't connect to the Wayland compositor\")]\n    WaylandConnection(#[source] ConnectError),\n\n    #[error(\"Wayland compositor communication error\")]\n    WaylandCommunication(#[source] WaylandError),\n\n    #[error(\n        \"A required Wayland protocol ({name} version {version}) is not supported by the compositor\"\n    )]\n    MissingProtocol { name: &'static str, version: u32 },\n}\n\nimpl<S> Dispatch<WlSeat, (), S> for State\nwhere\n    S: Dispatch<WlSeat, ()> + AsMut<State>,\n{\n    fn event(\n        parent: &mut S,\n        seat: &WlSeat,\n        event: <WlSeat as wayland_client::Proxy>::Event,\n        _data: &(),\n        _conn: &wayland_client::Connection,\n        _qh: &wayland_client::QueueHandle<S>,\n    ) {\n        let state = parent.as_mut();\n\n        if let wl_seat::Event::Name { name } = event {\n            state.seats.get_mut(seat).unwrap().set_name(name);\n        }\n    }\n}\n\npub fn initialize<S>(\n    primary: bool,\n    socket_name: Option<OsString>,\n) -> Result<(EventQueue<S>, State), Error>\nwhere\n    S: Dispatch<WlRegistry, GlobalListContents> + 'static,\n    S: Dispatch<ZwlrDataControlManagerV1, ()>,\n    S: Dispatch<ExtDataControlManagerV1, ()>,\n    S: Dispatch<WlSeat, ()>,\n    S: AsMut<State>,\n{\n    // Connect to the Wayland compositor.\n    let conn = match socket_name {\n        Some(name) => {\n            let mut socket_path = env::var_os(\"XDG_RUNTIME_DIR\")\n                .map(Into::<PathBuf>::into)\n                .ok_or(ConnectError::NoCompositor)\n                .map_err(Error::WaylandConnection)?;\n            if !socket_path.is_absolute() {\n                return Err(Error::WaylandConnection(ConnectError::NoCompositor));\n            }\n            socket_path.push(name);\n\n            let stream = UnixStream::connect(socket_path).map_err(Error::SocketOpenError)?;\n            Connection::from_socket(stream)\n        }\n        None => Connection::connect_to_env(),\n    }\n    .map_err(Error::WaylandConnection)?;\n\n    // Retrieve the global interfaces.\n    let (globals, queue) =\n        registry_queue_init::<S>(&conn).map_err(|err| match err {\n                                           GlobalError::Backend(err) => Error::WaylandCommunication(err),\n                                           GlobalError::InvalidId(err) => panic!(\"How's this possible? \\\n                                                                                  Is there no wl_registry? \\\n                                                                                  {:?}\",\n                                                                                 err),\n                                       })?;\n    let qh = &queue.handle();\n\n    // Verify that we got the clipboard manager.\n    let ext_manager = globals.bind(qh, 1..=1, ()).ok().map(Manager::Ext);\n\n    let wlr_v = if primary { 2 } else { 1 };\n    let wlr_manager = || globals.bind(qh, wlr_v..=wlr_v, ()).ok().map(Manager::Zwlr);\n\n    let clipboard_manager = match ext_manager.or_else(wlr_manager) {\n        Some(manager) => manager,\n        None => {\n            return Err(Error::MissingProtocol {\n                name: \"ext-data-control, or wlr-data-control\",\n                version: wlr_v,\n            })\n        }\n    };\n\n    let registry = globals.registry();\n    let seats = globals.contents().with_list(|globals| {\n        globals\n            .iter()\n            .filter(|global| global.interface == WlSeat::interface().name && global.version >= 2)\n            .map(|global| {\n                let seat = registry.bind(global.name, 2, qh, ());\n                (seat, SeatData::default())\n            })\n            .collect()\n    });\n\n    let state = State {\n        seats,\n        clipboard_manager,\n    };\n\n    Ok((queue, state))\n}\n"
  },
  {
    "path": "dep-crates/wl-clipboard-rs/src/copy.rs",
    "content": "//! Copying and clearing clipboard contents.\n\nuse std::collections::hash_map::Entry;\nuse std::collections::{HashMap, HashSet};\nuse std::ffi::OsString;\nuse std::fs::{remove_dir, remove_file, File, OpenOptions};\nuse std::io::{self, Read, Seek, SeekFrom, Write};\nuse std::path::PathBuf;\nuse std::sync::mpsc::sync_channel;\nuse std::{iter, thread};\n\nuse log::trace;\nuse rustix::fs::{fcntl_setfl, OFlags};\nuse wayland_client::globals::GlobalListContents;\nuse wayland_client::protocol::wl_registry::WlRegistry;\nuse wayland_client::protocol::wl_seat::WlSeat;\nuse wayland_client::{\n    delegate_dispatch, event_created_child, ConnectError, Dispatch, DispatchError, EventQueue,\n};\n\nuse crate::common::{self, initialize};\nuse crate::data_control::{\n    self, impl_dispatch_device, impl_dispatch_manager, impl_dispatch_offer, impl_dispatch_source,\n};\nuse crate::seat_data::SeatData;\nuse crate::utils::is_text;\n\n/// The clipboard to operate on.\n#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash, PartialOrd, Ord, Default)]\n#[cfg_attr(test, derive(proptest_derive::Arbitrary))]\npub enum ClipboardType {\n    /// The regular clipboard.\n    #[default]\n    Regular,\n    /// The \"primary\" clipboard.\n    ///\n    /// Working with the \"primary\" clipboard requires the compositor to support ext-data-control,\n    /// or wlr-data-control version 2 or above.\n    Primary,\n    /// Operate on both clipboards at once.\n    ///\n    /// Useful for atomically setting both clipboards at once. This option requires the \"primary\"\n    /// clipboard to be supported.\n    Both,\n}\n\n/// MIME type to offer the copied data under.\n#[derive(Clone, Eq, PartialEq, Debug, Hash, PartialOrd, Ord)]\n#[cfg_attr(test, derive(proptest_derive::Arbitrary))]\npub enum MimeType {\n    /// Detect the MIME type automatically from the data.\n    #[cfg_attr(test, proptest(skip))]\n    Autodetect,\n    /// Offer a number of common plain text MIME types.\n    Text,\n    /// Offer a specific MIME type.\n    Specific(String),\n}\n\n/// Source for copying.\n#[derive(Clone, Eq, PartialEq, Debug, Hash, PartialOrd, Ord)]\n#[cfg_attr(test, derive(proptest_derive::Arbitrary))]\npub enum Source {\n    /// Copy contents of the standard input.\n    #[cfg_attr(test, proptest(skip))]\n    StdIn,\n    /// Copy the given bytes.\n    Bytes(Box<[u8]>),\n}\n\n/// Source for copying, with a MIME type.\n///\n/// Used for [`copy_multi`].\n///\n/// [`copy_multi`]: fn.copy_multi.html\n#[derive(Clone, Eq, PartialEq, Debug, Hash, PartialOrd, Ord)]\npub struct MimeSource {\n    pub source: Source,\n    pub mime_type: MimeType,\n}\n\n/// Seat to operate on.\n#[derive(Clone, Eq, PartialEq, Debug, Hash, PartialOrd, Ord, Default)]\npub enum Seat {\n    /// Operate on all existing seats at once.\n    #[default]\n    All,\n    /// Operate on a seat with the given name.\n    Specific(String),\n}\n\n/// Number of paste requests to serve.\n#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash, PartialOrd, Ord, Default)]\npub enum ServeRequests {\n    /// Serve requests indefinitely.\n    #[default]\n    Unlimited,\n    /// Serve only the given number of requests.\n    Only(usize),\n}\n\n/// Options and flags that are used to customize the copying.\n#[derive(Clone, Eq, PartialEq, Debug, Default, Hash, PartialOrd, Ord)]\npub struct Options {\n    /// The clipboard to work with.\n    clipboard: ClipboardType,\n\n    /// The seat to work with.\n    seat: Seat,\n\n    /// Trim the trailing newline character before copying.\n    ///\n    /// This flag is only applied for text MIME types.\n    trim_newline: bool,\n\n    /// Do not spawn a separate thread for serving copy requests.\n    ///\n    /// Setting this flag will result in the call to `copy()` **blocking** until all data sources\n    /// it creates are destroyed, e.g. until someone else copies something into the clipboard.\n    foreground: bool,\n\n    /// Number of paste requests to serve.\n    ///\n    /// Limiting the number of paste requests to one effectively clears the clipboard after the\n    /// first paste. It can be used when copying e.g. sensitive data, like passwords. Note however\n    /// that certain apps may have issues pasting when this option is used, in particular XWayland\n    /// clients are known to suffer from this.\n    serve_requests: ServeRequests,\n\n    /// Omit additional text mime types which are offered by default if at least one text mime type is provided.\n    ///\n    /// Omits additionally offered `text/plain;charset=utf-8`, `text/plain`, `STRING`, `UTF8_STRING` and\n    /// `TEXT` mime types which are offered by default if at least one text mime type is provided.\n    omit_additional_text_mime_types: bool,\n}\n\n/// A copy operation ready to start serving requests.\npub struct PreparedCopy {\n    queue: EventQueue<State>,\n    state: State,\n    sources: Vec<data_control::Source>,\n}\n\n/// Errors that can occur for copying the source data to a temporary file.\n#[derive(thiserror::Error, Debug)]\npub enum SourceCreationError {\n    #[error(\"Couldn't create a temporary directory\")]\n    TempDirCreate(#[source] io::Error),\n\n    #[error(\"Couldn't create a temporary file\")]\n    TempFileCreate(#[source] io::Error),\n\n    #[error(\"Couldn't copy data to the temporary file\")]\n    DataCopy(#[source] io::Error),\n\n    #[error(\"Couldn't write to the temporary file\")]\n    TempFileWrite(#[source] io::Error),\n\n    #[error(\"Couldn't open the temporary file for newline trimming\")]\n    TempFileOpen(#[source] io::Error),\n\n    #[error(\"Couldn't get the temporary file metadata for newline trimming\")]\n    TempFileMetadata(#[source] io::Error),\n\n    #[error(\"Couldn't seek the temporary file for newline trimming\")]\n    TempFileSeek(#[source] io::Error),\n\n    #[error(\"Couldn't read the last byte of the temporary file for newline trimming\")]\n    TempFileRead(#[source] io::Error),\n\n    #[error(\"Couldn't truncate the temporary file for newline trimming\")]\n    TempFileTruncate(#[source] io::Error),\n}\n\n/// Errors that can occur for copying and clearing the clipboard.\n#[derive(thiserror::Error, Debug)]\npub enum Error {\n    #[error(\"There are no seats\")]\n    NoSeats,\n\n    #[error(\"Couldn't open the provided Wayland socket\")]\n    SocketOpenError(#[source] io::Error),\n\n    #[error(\"Couldn't connect to the Wayland compositor\")]\n    WaylandConnection(#[source] ConnectError),\n\n    #[error(\"Wayland compositor communication error\")]\n    WaylandCommunication(#[source] DispatchError),\n\n    #[error(\n        \"A required Wayland protocol ({} version {}) is not supported by the compositor\",\n        name,\n        version\n    )]\n    MissingProtocol { name: &'static str, version: u32 },\n\n    #[error(\"The compositor does not support primary selection\")]\n    PrimarySelectionUnsupported,\n\n    #[error(\"The requested seat was not found\")]\n    SeatNotFound,\n\n    #[error(\"Error copying the source into a temporary file\")]\n    TempCopy(#[source] SourceCreationError),\n\n    #[error(\"Couldn't remove the temporary file\")]\n    TempFileRemove(#[source] io::Error),\n\n    #[error(\"Couldn't remove the temporary directory\")]\n    TempDirRemove(#[source] io::Error),\n\n    #[error(\"Error satisfying a paste request\")]\n    Paste(#[source] DataSourceError),\n}\n\nimpl From<common::Error> for Error {\n    fn from(x: common::Error) -> Self {\n        use common::Error::*;\n\n        match x {\n            SocketOpenError(err) => Error::SocketOpenError(err),\n            WaylandConnection(err) => Error::WaylandConnection(err),\n            WaylandCommunication(err) => Error::WaylandCommunication(err.into()),\n            MissingProtocol { name, version } => Error::MissingProtocol { name, version },\n        }\n    }\n}\n\n#[derive(thiserror::Error, Debug)]\npub enum DataSourceError {\n    #[error(\"Couldn't open the data file\")]\n    FileOpen(#[source] io::Error),\n\n    #[error(\"Couldn't copy the data to the target file descriptor\")]\n    Copy(#[source] io::Error),\n}\n\nstruct State {\n    common: common::State,\n    got_primary_selection: bool,\n    // This bool can be set to true when serving a request: either if an error occurs, or if the\n    // number of requests to serve was limited and the last request was served.\n    should_quit: bool,\n    data_paths: HashMap<String, PathBuf>,\n    serve_requests: ServeRequests,\n    // An error that occurred while serving a request, if any.\n    error: Option<DataSourceError>,\n}\n\ndelegate_dispatch!(State: [WlSeat: ()] => common::State);\n\nimpl AsMut<common::State> for State {\n    fn as_mut(&mut self) -> &mut common::State {\n        &mut self.common\n    }\n}\n\nimpl Dispatch<WlRegistry, GlobalListContents> for State {\n    fn event(\n        _state: &mut Self,\n        _proxy: &WlRegistry,\n        _event: <WlRegistry as wayland_client::Proxy>::Event,\n        _data: &GlobalListContents,\n        _conn: &wayland_client::Connection,\n        _qhandle: &wayland_client::QueueHandle<Self>,\n    ) {\n    }\n}\n\nimpl_dispatch_manager!(State);\n\nimpl_dispatch_device!(State, WlSeat, |state: &mut Self, event, seat| {\n    match event {\n        Event::DataOffer { id } => id.destroy(),\n        Event::Finished => {\n            state.common.seats.get_mut(seat).unwrap().set_device(None);\n        }\n        Event::PrimarySelection { .. } => {\n            state.got_primary_selection = true;\n        }\n        _ => (),\n    }\n});\n\nimpl_dispatch_offer!(State);\n\nimpl_dispatch_source!(State, |state: &mut Self,\n                              source: data_control::Source,\n                              event| {\n    match event {\n        Event::Send { mime_type, fd } => {\n            // Check if some other source already handled a paste request and indicated that we should\n            // quit.\n            if state.should_quit {\n                source.destroy();\n                return;\n            }\n\n            // I'm not sure if it's the compositor's responsibility to check that the mime type is\n            // valid. Let's check here just in case.\n            if !state.data_paths.contains_key(&mime_type) {\n                return;\n            }\n\n            let data_path = &state.data_paths[&mime_type];\n\n            let file = File::open(data_path).map_err(DataSourceError::FileOpen);\n            let result = file.and_then(|mut data_file| {\n                // Clear O_NONBLOCK, otherwise io::copy() will stop halfway.\n                fcntl_setfl(&fd, OFlags::empty())\n                    .map_err(io::Error::from)\n                    .map_err(DataSourceError::Copy)?;\n\n                let mut target_file = File::from(fd);\n                io::copy(&mut data_file, &mut target_file).map_err(DataSourceError::Copy)\n            });\n\n            if let Err(err) = result {\n                state.error = Some(err);\n            }\n\n            let done = if let ServeRequests::Only(left) = state.serve_requests {\n                let left = left.checked_sub(1).unwrap();\n                state.serve_requests = ServeRequests::Only(left);\n                left == 0\n            } else {\n                false\n            };\n\n            if done || state.error.is_some() {\n                state.should_quit = true;\n                source.destroy();\n            }\n        }\n        Event::Cancelled => source.destroy(),\n        _ => (),\n    }\n});\n\nimpl Options {\n    /// Creates a blank new set of options ready for configuration.\n    #[inline]\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    /// Sets the clipboard to work with.\n    #[inline]\n    pub fn clipboard(&mut self, clipboard: ClipboardType) -> &mut Self {\n        self.clipboard = clipboard;\n        self\n    }\n\n    /// Sets the seat to use for copying.\n    #[inline]\n    pub fn seat(&mut self, seat: Seat) -> &mut Self {\n        self.seat = seat;\n        self\n    }\n\n    /// Sets the flag for trimming the trailing newline.\n    ///\n    /// This flag is only applied for text MIME types.\n    #[inline]\n    pub fn trim_newline(&mut self, trim_newline: bool) -> &mut Self {\n        self.trim_newline = trim_newline;\n        self\n    }\n\n    /// Sets the flag for not spawning a separate thread for serving copy requests.\n    ///\n    /// Setting this flag will result in the call to `copy()` **blocking** until all data sources\n    /// it creates are destroyed, e.g. until someone else copies something into the clipboard.\n    #[inline]\n    pub fn foreground(&mut self, foreground: bool) -> &mut Self {\n        self.foreground = foreground;\n        self\n    }\n\n    /// Sets the number of requests to serve.\n    ///\n    /// Limiting the number of requests to one effectively clears the clipboard after the first\n    /// paste. It can be used when copying e.g. sensitive data, like passwords. Note however that\n    /// certain apps may have issues pasting when this option is used, in particular XWayland\n    /// clients are known to suffer from this.\n    #[inline]\n    pub fn serve_requests(&mut self, serve_requests: ServeRequests) -> &mut Self {\n        self.serve_requests = serve_requests;\n        self\n    }\n\n    /// Sets the flag for omitting additional text mime types which are offered by default if at least one text mime type is provided.\n    ///\n    /// Omits additionally offered `text/plain;charset=utf-8`, `text/plain`, `STRING`, `UTF8_STRING` and\n    /// `TEXT` mime types which are offered by default if at least one text mime type is provided.\n    #[inline]\n    pub fn omit_additional_text_mime_types(\n        &mut self,\n        omit_additional_text_mime_types: bool,\n    ) -> &mut Self {\n        self.omit_additional_text_mime_types = omit_additional_text_mime_types;\n        self\n    }\n\n    /// Invokes the copy operation. See `copy()`.\n    ///\n    /// # Examples\n    ///\n    /// ```no_run\n    /// # extern crate wl_clipboard_rs;\n    /// # use wl_clipboard_rs::copy::Error;\n    /// # fn foo() -> Result<(), Error> {\n    /// use wl_clipboard_rs::copy::{MimeType, Options, Source};\n    ///\n    /// let opts = Options::new();\n    /// opts.copy(Source::Bytes([1, 2, 3][..].into()), MimeType::Autodetect)?;\n    /// # Ok(())\n    /// # }\n    /// ```\n    #[inline]\n    pub fn copy(self, source: Source, mime_type: MimeType) -> Result<(), Error> {\n        copy(self, source, mime_type)\n    }\n\n    /// Invokes the copy_multi operation. See `copy_multi()`.\n    ///\n    /// # Examples\n    ///\n    /// ```no_run\n    /// # extern crate wl_clipboard_rs;\n    /// # use wl_clipboard_rs::copy::Error;\n    /// # fn foo() -> Result<(), Error> {\n    /// use wl_clipboard_rs::copy::{MimeSource, MimeType, Options, Source};\n    ///\n    /// let opts = Options::new();\n    /// opts.copy_multi(vec![MimeSource { source: Source::Bytes([1, 2, 3][..].into()),\n    ///                                   mime_type: MimeType::Autodetect },\n    ///                      MimeSource { source: Source::Bytes([7, 8, 9][..].into()),\n    ///                                   mime_type: MimeType::Text }])?;\n    /// # Ok(())\n    /// # }\n    /// ```\n    #[inline]\n    pub fn copy_multi(self, sources: Vec<MimeSource>) -> Result<(), Error> {\n        copy_multi(self, sources)\n    }\n\n    /// Invokes the prepare_copy operation. See `prepare_copy()`.\n    ///\n    /// # Panics\n    ///\n    /// Panics if `foreground` is `false`.\n    ///\n    /// # Examples\n    ///\n    /// ```no_run\n    /// # extern crate wl_clipboard_rs;\n    /// # use wl_clipboard_rs::copy::Error;\n    /// # fn foo() -> Result<(), Error> {\n    /// use wl_clipboard_rs::copy::{MimeSource, MimeType, Options, Source};\n    ///\n    /// let mut opts = Options::new();\n    /// opts.foreground(true);\n    /// let prepared_copy = opts.prepare_copy(Source::Bytes([1, 2, 3][..].into()),\n    ///                                       MimeType::Autodetect)?;\n    /// prepared_copy.serve()?;\n    ///\n    /// # Ok(())\n    /// # }\n    /// ```\n    #[inline]\n    pub fn prepare_copy(self, source: Source, mime_type: MimeType) -> Result<PreparedCopy, Error> {\n        prepare_copy(self, source, mime_type)\n    }\n\n    /// Invokes the prepare_copy_multi operation. See `prepare_copy_multi()`.\n    ///\n    /// # Panics\n    ///\n    /// Panics if `foreground` is `false`.\n    ///\n    /// # Examples\n    ///\n    /// ```no_run\n    /// # extern crate wl_clipboard_rs;\n    /// # use wl_clipboard_rs::copy::Error;\n    /// # fn foo() -> Result<(), Error> {\n    /// use wl_clipboard_rs::copy::{MimeSource, MimeType, Options, Source};\n    ///\n    /// let mut opts = Options::new();\n    /// opts.foreground(true);\n    /// let prepared_copy =\n    ///     opts.prepare_copy_multi(vec![MimeSource { source: Source::Bytes([1, 2, 3][..].into()),\n    ///                                               mime_type: MimeType::Autodetect },\n    ///                                  MimeSource { source: Source::Bytes([7, 8, 9][..].into()),\n    ///                                               mime_type: MimeType::Text }])?;\n    /// prepared_copy.serve()?;\n    ///\n    /// # Ok(())\n    /// # }\n    /// ```\n    #[inline]\n    pub fn prepare_copy_multi(self, sources: Vec<MimeSource>) -> Result<PreparedCopy, Error> {\n        prepare_copy_multi(self, sources)\n    }\n}\n\nimpl PreparedCopy {\n    /// Starts serving copy requests.\n    ///\n    /// This function **blocks** until all requests are served or the clipboard is taken over by\n    /// some other application.\n    pub fn serve(mut self) -> Result<(), Error> {\n        // Loop until we're done.\n        while !self.state.should_quit {\n            self.queue\n                .blocking_dispatch(&mut self.state)\n                .map_err(Error::WaylandCommunication)?;\n\n            // Check if all sources have been destroyed.\n            let all_destroyed = self.sources.iter().all(|x| !x.is_alive());\n            if all_destroyed {\n                self.state.should_quit = true;\n            }\n        }\n\n        // Clean up the temp file and directory.\n        //\n        // We want to try cleaning up all files and folders, so if any errors occur in process,\n        // collect them into a vector without interruption, and then return the first one.\n        let mut results = Vec::new();\n        let mut dropped = HashSet::new();\n        for data_path in self.state.data_paths.values_mut() {\n            // data_paths can contain duplicate items, we want to free each only once.\n            if dropped.contains(data_path) {\n                continue;\n            };\n            dropped.insert(data_path.clone());\n\n            match remove_file(&data_path).map_err(Error::TempFileRemove) {\n                Ok(()) => {\n                    data_path.pop();\n                    results.push(remove_dir(&data_path).map_err(Error::TempDirRemove));\n                }\n                result @ Err(_) => results.push(result),\n            }\n        }\n\n        // Return the error, if any.\n        let result: Result<(), _> = results.into_iter().collect();\n        result?;\n\n        // Check if an error occurred during data transfer.\n        if let Some(err) = self.state.error.take() {\n            return Err(Error::Paste(err));\n        }\n\n        Ok(())\n    }\n}\n\nfn make_source(\n    source: Source,\n    mime_type: MimeType,\n    trim_newline: bool,\n) -> Result<(String, PathBuf), SourceCreationError> {\n    let temp_dir = tempfile::tempdir().map_err(SourceCreationError::TempDirCreate)?;\n    let mut temp_filename = temp_dir.keep();\n    temp_filename.push(\"stdin\");\n    trace!(\"Temp filename: {}\", temp_filename.to_string_lossy());\n    let mut temp_file =\n        File::create(&temp_filename).map_err(SourceCreationError::TempFileCreate)?;\n\n    if let Source::Bytes(data) = source {\n        temp_file\n            .write_all(&data)\n            .map_err(SourceCreationError::TempFileWrite)?;\n    } else {\n        // Copy the standard input into the target file.\n        io::copy(&mut io::stdin(), &mut temp_file).map_err(SourceCreationError::DataCopy)?;\n    }\n\n    let mime_type = match mime_type {\n        MimeType::Autodetect => match tree_magic_mini::from_filepath(&temp_filename) {\n            Some(magic) => Ok(magic),\n            None => Err(SourceCreationError::TempFileOpen(std::io::Error::other(\n                \"problem with temp file\",\n            ))),\n        }?\n        .to_string(),\n        MimeType::Text => \"text/plain\".to_string(),\n        MimeType::Specific(mime_type) => mime_type,\n    };\n\n    trace!(\"Base MIME type: {}\", mime_type);\n\n    // Trim the trailing newline if needed.\n    if trim_newline && is_text(&mime_type) {\n        let mut temp_file = OpenOptions::new()\n            .read(true)\n            .write(true)\n            .open(&temp_filename)\n            .map_err(SourceCreationError::TempFileOpen)?;\n        let metadata = temp_file\n            .metadata()\n            .map_err(SourceCreationError::TempFileMetadata)?;\n        let length = metadata.len();\n        if length > 0 {\n            temp_file\n                .seek(SeekFrom::End(-1))\n                .map_err(SourceCreationError::TempFileSeek)?;\n\n            let mut buf = [0];\n            temp_file\n                .read_exact(&mut buf)\n                .map_err(SourceCreationError::TempFileRead)?;\n            if buf[0] == b'\\n' {\n                temp_file\n                    .set_len(length - 1)\n                    .map_err(SourceCreationError::TempFileTruncate)?;\n            }\n        }\n    }\n\n    Ok((mime_type, temp_filename))\n}\n\nfn get_devices(\n    primary: bool,\n    seat: Seat,\n    socket_name: Option<OsString>,\n) -> Result<(EventQueue<State>, State, Vec<data_control::Device>), Error> {\n    let (mut queue, mut common) = initialize(primary, socket_name)?;\n\n    // Check if there are no seats.\n    if common.seats.is_empty() {\n        return Err(Error::NoSeats);\n    }\n\n    // Go through the seats and get their data devices.\n    for (seat, data) in &mut common.seats {\n        let device = common\n            .clipboard_manager\n            .get_data_device(seat, &queue.handle(), seat.clone());\n        data.set_device(Some(device));\n    }\n\n    let mut state = State {\n        common,\n        got_primary_selection: false,\n        should_quit: false,\n        data_paths: HashMap::new(),\n        serve_requests: ServeRequests::default(),\n        error: None,\n    };\n\n    // Retrieve all seat names.\n    queue\n        .roundtrip(&mut state)\n        .map_err(Error::WaylandCommunication)?;\n\n    // Check if the compositor supports primary selection.\n    if primary && !state.got_primary_selection {\n        return Err(Error::PrimarySelectionUnsupported);\n    }\n\n    // Figure out which devices we're interested in.\n    let devices = state\n        .common\n        .seats\n        .values()\n        .filter_map(|data| {\n            let SeatData { name, device, .. } = data;\n\n            let device = device.clone();\n\n            match seat {\n                Seat::All => {\n                    // If no seat was specified, handle all of them.\n                    return device;\n                }\n                Seat::Specific(ref desired_name) => {\n                    if name.as_deref() == Some(desired_name) {\n                        return device;\n                    }\n                }\n            }\n\n            None\n        })\n        .collect::<Vec<_>>();\n\n    // If we didn't find the seat, print an error message and exit.\n    //\n    // This also triggers when we found the seat but it had no data device; is this what we want?\n    if devices.is_empty() {\n        return Err(Error::SeatNotFound);\n    }\n\n    Ok((queue, state, devices))\n}\n\n/// Clears the clipboard for the given seat.\n///\n/// If `seat` is `None`, clears clipboards of all existing seats.\n///\n/// # Examples\n///\n/// ```no_run\n/// # extern crate wl_clipboard_rs;\n/// # use wl_clipboard_rs::copy::Error;\n/// # fn foo() -> Result<(), Error> {\n/// use wl_clipboard_rs::{copy::{clear, ClipboardType, Seat}};\n///\n/// clear(ClipboardType::Regular, Seat::All)?;\n/// # Ok(())\n/// # }\n/// ```\n#[inline]\npub fn clear(clipboard: ClipboardType, seat: Seat) -> Result<(), Error> {\n    clear_internal(clipboard, seat, None)\n}\n\npub(crate) fn clear_internal(\n    clipboard: ClipboardType,\n    seat: Seat,\n    socket_name: Option<OsString>,\n) -> Result<(), Error> {\n    let primary = clipboard != ClipboardType::Regular;\n    let (mut queue, mut state, devices) = get_devices(primary, seat, socket_name)?;\n\n    for device in devices {\n        if clipboard == ClipboardType::Primary || clipboard == ClipboardType::Both {\n            device.set_primary_selection(None);\n        }\n        if clipboard == ClipboardType::Regular || clipboard == ClipboardType::Both {\n            device.set_selection(None);\n        }\n    }\n\n    // We're clearing the clipboard so just do one roundtrip and quit.\n    queue\n        .roundtrip(&mut state)\n        .map_err(Error::WaylandCommunication)?;\n\n    Ok(())\n}\n\n/// Prepares a data copy to the clipboard.\n///\n/// The data is copied from `source` and offered in the `mime_type` MIME type. See `Options` for\n/// customizing the behavior of this operation.\n///\n/// This function can be used instead of `copy()` when it's desirable to separately prepare the\n/// copy operation, handle any errors that this may produce, and then start the serving loop,\n/// potentially past a fork (which is how `wl-copy` uses it). It is meant to be used in the\n/// foreground mode and does not spawn any threads.\n///\n/// # Panics\n///\n/// Panics if `foreground` is `false`.\n///\n/// # Examples\n///\n/// ```no_run\n/// # extern crate wl_clipboard_rs;\n/// # use wl_clipboard_rs::copy::Error;\n/// # fn foo() -> Result<(), Error> {\n/// use wl_clipboard_rs::copy::{MimeSource, MimeType, Options, Source};\n///\n/// let mut opts = Options::new();\n/// opts.foreground(true);\n/// let prepared_copy = opts.prepare_copy(Source::Bytes([1, 2, 3][..].into()),\n///                                       MimeType::Autodetect)?;\n/// prepared_copy.serve()?;\n///\n/// # Ok(())\n/// # }\n/// ```\n#[inline]\npub fn prepare_copy(\n    options: Options,\n    source: Source,\n    mime_type: MimeType,\n) -> Result<PreparedCopy, Error> {\n    assert!(options.foreground);\n\n    let sources = vec![MimeSource { source, mime_type }];\n\n    prepare_copy_internal(options, sources, None)\n}\n\n/// Prepares a data copy to the clipboard, offering multiple data sources.\n///\n/// The data from each source in `sources` is copied and offered in the corresponding MIME type.\n/// See `Options` for customizing the behavior of this operation.\n///\n/// If multiple sources specify the same MIME type, the first one is offered. If one of the MIME\n/// types is text, all automatically added plain text offers will fall back to the first source\n/// with a text MIME type.\n///\n/// This function can be used instead of `copy()` when it's desirable to separately prepare the\n/// copy operation, handle any errors that this may produce, and then start the serving loop,\n/// potentially past a fork (which is how `wl-copy` uses it). It is meant to be used in the\n/// foreground mode and does not spawn any threads.\n///\n/// # Panics\n///\n/// Panics if `foreground` is `false`.\n///\n/// # Examples\n///\n/// ```no_run\n/// # extern crate wl_clipboard_rs;\n/// # use wl_clipboard_rs::copy::Error;\n/// # fn foo() -> Result<(), Error> {\n/// use wl_clipboard_rs::copy::{MimeSource, MimeType, Options, Source};\n///\n/// let mut opts = Options::new();\n/// opts.foreground(true);\n/// let prepared_copy =\n///     opts.prepare_copy_multi(vec![MimeSource { source: Source::Bytes([1, 2, 3][..].into()),\n///                                               mime_type: MimeType::Autodetect },\n///                                  MimeSource { source: Source::Bytes([7, 8, 9][..].into()),\n///                                               mime_type: MimeType::Text }])?;\n/// prepared_copy.serve()?;\n///\n/// # Ok(())\n/// # }\n/// ```\n#[inline]\npub fn prepare_copy_multi(\n    options: Options,\n    sources: Vec<MimeSource>,\n) -> Result<PreparedCopy, Error> {\n    assert!(options.foreground);\n\n    prepare_copy_internal(options, sources, None)\n}\n\nfn prepare_copy_internal(\n    options: Options,\n    sources: Vec<MimeSource>,\n    socket_name: Option<OsString>,\n) -> Result<PreparedCopy, Error> {\n    let Options {\n        clipboard,\n        seat,\n        trim_newline,\n        serve_requests,\n        ..\n    } = options;\n\n    let primary = clipboard != ClipboardType::Regular;\n    let (queue, mut state, devices) = get_devices(primary, seat, socket_name)?;\n\n    state.serve_requests = serve_requests;\n\n    // Collect the source data to copy.\n    state.data_paths = {\n        let mut data_paths = HashMap::new();\n        let mut text_data_path = None;\n        for MimeSource { source, mime_type } in sources.into_iter() {\n            let (mime_type, mut data_path) =\n                make_source(source, mime_type, trim_newline).map_err(Error::TempCopy)?;\n\n            let mime_type_is_text = is_text(&mime_type);\n\n            match data_paths.entry(mime_type) {\n                Entry::Occupied(_) => {\n                    // This MIME type has already been specified, so ignore it.\n                    remove_file(&*data_path).map_err(Error::TempFileRemove)?;\n                    data_path.pop();\n                    remove_dir(&*data_path).map_err(Error::TempDirRemove)?;\n                }\n                Entry::Vacant(entry) => {\n                    if !options.omit_additional_text_mime_types\n                        && text_data_path.is_none()\n                        && mime_type_is_text\n                    {\n                        text_data_path = Some(data_path.clone());\n                    }\n\n                    entry.insert(data_path);\n                }\n            }\n        }\n\n        // If the MIME type is text, offer it in some other common formats.\n        if let Some(text_data_path) = text_data_path {\n            let text_mimes = [\n                \"text/plain;charset=utf-8\",\n                \"text/plain\",\n                \"STRING\",\n                \"UTF8_STRING\",\n                \"TEXT\",\n            ];\n            for &mime_type in &text_mimes {\n                // We don't want to overwrite an explicit mime type, because it might be bound to a\n                // different data_path\n                if !data_paths.contains_key(mime_type) {\n                    data_paths.insert(mime_type.to_string(), text_data_path.clone());\n                }\n            }\n        }\n        data_paths\n    };\n\n    // Create an iterator over (device, primary) for source creation later.\n    //\n    // This is needed because for ClipboardType::Both each device needs to appear twice because\n    // separate data sources need to be made for the regular and the primary clipboards (data\n    // sources cannot be reused).\n    let devices_iter = devices.iter().flat_map(|device| {\n        let first = match clipboard {\n            ClipboardType::Regular => iter::once((device, false)),\n            ClipboardType::Primary => iter::once((device, true)),\n            ClipboardType::Both => iter::once((device, false)),\n        };\n\n        let second = if clipboard == ClipboardType::Both {\n            iter::once(Some((device, true)))\n        } else {\n            iter::once(None)\n        };\n\n        first.chain(second.flatten())\n    });\n\n    // Create the data sources and set them as selections.\n    let sources = devices_iter\n        .map(|(device, primary)| {\n            let data_source = state\n                .common\n                .clipboard_manager\n                .create_data_source(&queue.handle());\n\n            for mime_type in state.data_paths.keys() {\n                data_source.offer(mime_type.clone());\n            }\n\n            if primary {\n                device.set_primary_selection(Some(&data_source));\n            } else {\n                device.set_selection(Some(&data_source));\n            }\n\n            // If we need to serve 0 requests, kill the data source right away.\n            if let ServeRequests::Only(0) = state.serve_requests {\n                data_source.destroy();\n            }\n            data_source\n        })\n        .collect::<Vec<_>>();\n\n    Ok(PreparedCopy {\n        queue,\n        state,\n        sources,\n    })\n}\n\n/// Copies data to the clipboard.\n///\n/// The data is copied from `source` and offered in the `mime_type` MIME type. See `Options` for\n/// customizing the behavior of this operation.\n///\n/// # Examples\n///\n/// ```no_run\n/// # extern crate wl_clipboard_rs;\n/// # use wl_clipboard_rs::copy::Error;\n/// # fn foo() -> Result<(), Error> {\n/// use wl_clipboard_rs::copy::{copy, MimeType, Options, Source};\n///\n/// let opts = Options::new();\n/// copy(opts, Source::Bytes([1, 2, 3][..].into()), MimeType::Autodetect)?;\n/// # Ok(())\n/// # }\n/// ```\n#[inline]\npub fn copy(options: Options, source: Source, mime_type: MimeType) -> Result<(), Error> {\n    let sources = vec![MimeSource { source, mime_type }];\n    copy_internal(options, sources, None)\n}\n\n/// Copies data to the clipboard, offering multiple data sources.\n///\n/// The data from each source in `sources` is copied and offered in the corresponding MIME type.\n/// See `Options` for customizing the behavior of this operation.\n///\n/// If multiple sources specify the same MIME type, the first one is offered. If one of the MIME\n/// types is text, all automatically added plain text offers will fall back to the first source\n/// with a text MIME type.\n///\n/// # Examples\n///\n/// ```no_run\n/// # extern crate wl_clipboard_rs;\n/// # use wl_clipboard_rs::copy::Error;\n/// # fn foo() -> Result<(), Error> {\n/// use wl_clipboard_rs::copy::{MimeSource, MimeType, Options, Source};\n///\n/// let opts = Options::new();\n/// opts.copy_multi(vec![MimeSource { source: Source::Bytes([1, 2, 3][..].into()),\n///                                   mime_type: MimeType::Autodetect },\n///                      MimeSource { source: Source::Bytes([7, 8, 9][..].into()),\n///                                   mime_type: MimeType::Text }])?;\n/// # Ok(())\n/// # }\n/// ```\n#[inline]\npub fn copy_multi(options: Options, sources: Vec<MimeSource>) -> Result<(), Error> {\n    copy_internal(options, sources, None)\n}\n\npub(crate) fn copy_internal(\n    options: Options,\n    sources: Vec<MimeSource>,\n    socket_name: Option<OsString>,\n) -> Result<(), Error> {\n    if options.foreground {\n        prepare_copy_internal(options, sources, socket_name)?.serve()\n    } else {\n        // The copy must be prepared on the thread because PreparedCopy isn't Send.\n        // To receive errors from prepare_copy, use a channel.\n        let (tx, rx) = sync_channel(1);\n\n        thread::spawn(\n            move || match prepare_copy_internal(options, sources, socket_name) {\n                Ok(prepared_copy) => {\n                    // prepare_copy completed successfully, report that.\n                    drop(tx.send(None));\n\n                    // There's nobody listening for errors at this point, just drop it.\n                    drop(prepared_copy.serve());\n                }\n                Err(err) => drop(tx.send(Some(err))),\n            },\n        );\n\n        if let Some(err) = rx.recv().unwrap() {\n            return Err(err);\n        }\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "dep-crates/wl-clipboard-rs/src/data_control.rs",
    "content": "//! Abstraction over ext/wlr-data-control.\n\nuse std::os::fd::BorrowedFd;\n\nuse ext::ext_data_control_device_v1::ExtDataControlDeviceV1;\nuse ext::ext_data_control_manager_v1::ExtDataControlManagerV1;\nuse ext::ext_data_control_offer_v1::ExtDataControlOfferV1;\nuse ext::ext_data_control_source_v1::ExtDataControlSourceV1;\nuse wayland_client::protocol::wl_seat::WlSeat;\nuse wayland_client::{Dispatch, Proxy as _, QueueHandle};\nuse wayland_protocols::ext::data_control::v1::client as ext;\nuse wayland_protocols_wlr::data_control::v1::client as zwlr;\nuse zwlr::zwlr_data_control_device_v1::ZwlrDataControlDeviceV1;\nuse zwlr::zwlr_data_control_manager_v1::ZwlrDataControlManagerV1;\nuse zwlr::zwlr_data_control_offer_v1::ZwlrDataControlOfferV1;\nuse zwlr::zwlr_data_control_source_v1::ZwlrDataControlSourceV1;\n\n#[derive(Clone)]\npub enum Manager {\n    Zwlr(ZwlrDataControlManagerV1),\n    Ext(ExtDataControlManagerV1),\n}\n\n#[derive(Clone)]\npub enum Device {\n    Zwlr(ZwlrDataControlDeviceV1),\n    Ext(ExtDataControlDeviceV1),\n}\n\n#[derive(Clone)]\npub enum Source {\n    Zwlr(ZwlrDataControlSourceV1),\n    Ext(ExtDataControlSourceV1),\n}\n\n#[derive(Clone, PartialEq, Eq, Hash)]\npub enum Offer {\n    Zwlr(ZwlrDataControlOfferV1),\n    Ext(ExtDataControlOfferV1),\n}\n\nimpl Manager {\n    pub fn get_data_device<D, U>(&self, seat: &WlSeat, qh: &QueueHandle<D>, udata: U) -> Device\n    where\n        D: Dispatch<ZwlrDataControlDeviceV1, U> + 'static,\n        D: Dispatch<ExtDataControlDeviceV1, U> + 'static,\n        U: Send + Sync + 'static,\n    {\n        match self {\n            Manager::Zwlr(manager) => Device::Zwlr(manager.get_data_device(seat, qh, udata)),\n            Manager::Ext(manager) => Device::Ext(manager.get_data_device(seat, qh, udata)),\n        }\n    }\n\n    pub fn create_data_source<D>(&self, qh: &QueueHandle<D>) -> Source\n    where\n        D: Dispatch<ZwlrDataControlSourceV1, ()> + 'static,\n        D: Dispatch<ExtDataControlSourceV1, ()> + 'static,\n    {\n        match self {\n            Manager::Zwlr(manager) => Source::Zwlr(manager.create_data_source(qh, ())),\n            Manager::Ext(manager) => Source::Ext(manager.create_data_source(qh, ())),\n        }\n    }\n}\n\nimpl Device {\n    pub fn destroy(&self) {\n        match self {\n            Device::Zwlr(device) => device.destroy(),\n            Device::Ext(device) => device.destroy(),\n        }\n    }\n\n    #[track_caller]\n    pub fn set_selection(&self, source: Option<&Source>) {\n        match self {\n            Device::Zwlr(device) => device.set_selection(source.map(Source::zwlr)),\n            Device::Ext(device) => device.set_selection(source.map(Source::ext)),\n        }\n    }\n\n    #[track_caller]\n    pub fn set_primary_selection(&self, source: Option<&Source>) {\n        match self {\n            Device::Zwlr(device) => device.set_primary_selection(source.map(Source::zwlr)),\n            Device::Ext(device) => device.set_primary_selection(source.map(Source::ext)),\n        }\n    }\n}\n\nimpl Source {\n    pub fn destroy(&self) {\n        match self {\n            Source::Zwlr(source) => source.destroy(),\n            Source::Ext(source) => source.destroy(),\n        }\n    }\n\n    pub fn offer(&self, mime_type: String) {\n        match self {\n            Source::Zwlr(source) => source.offer(mime_type),\n            Source::Ext(source) => source.offer(mime_type),\n        }\n    }\n\n    pub fn is_alive(&self) -> bool {\n        match self {\n            Source::Zwlr(source) => source.is_alive(),\n            Source::Ext(source) => source.is_alive(),\n        }\n    }\n\n    #[track_caller]\n    pub fn zwlr(&self) -> &ZwlrDataControlSourceV1 {\n        if let Self::Zwlr(v) = self {\n            v\n        } else {\n            panic!(\"tried to convert non-Zwlr Source to Zwlr\")\n        }\n    }\n\n    #[track_caller]\n    pub fn ext(&self) -> &ExtDataControlSourceV1 {\n        if let Self::Ext(v) = self {\n            v\n        } else {\n            panic!(\"tried to convert non-Ext Source to Ext\")\n        }\n    }\n}\n\nimpl Offer {\n    pub fn destroy(&self) {\n        match self {\n            Offer::Zwlr(offer) => offer.destroy(),\n            Offer::Ext(offer) => offer.destroy(),\n        }\n    }\n\n    pub fn receive(&self, mime_type: String, fd: BorrowedFd) {\n        match self {\n            Offer::Zwlr(offer) => offer.receive(mime_type, fd),\n            Offer::Ext(offer) => offer.receive(mime_type, fd),\n        }\n    }\n}\n\nimpl From<ZwlrDataControlSourceV1> for Source {\n    fn from(v: ZwlrDataControlSourceV1) -> Self {\n        Self::Zwlr(v)\n    }\n}\n\nimpl From<ExtDataControlSourceV1> for Source {\n    fn from(v: ExtDataControlSourceV1) -> Self {\n        Self::Ext(v)\n    }\n}\n\nimpl From<ZwlrDataControlOfferV1> for Offer {\n    fn from(v: ZwlrDataControlOfferV1) -> Self {\n        Self::Zwlr(v)\n    }\n}\n\nimpl From<ExtDataControlOfferV1> for Offer {\n    fn from(v: ExtDataControlOfferV1) -> Self {\n        Self::Ext(v)\n    }\n}\n\n// Some mildly cursed macros to avoid code duplication.\nmacro_rules! impl_dispatch_manager {\n    ($handler:ty => [$($iface:ty),*]) => {\n        $(\n            impl Dispatch<$iface, ()> for $handler {\n                fn event(\n                    _state: &mut Self,\n                    _proxy: &$iface,\n                    _event: <$iface as wayland_client::Proxy>::Event,\n                    _data: &(),\n                    _conn: &wayland_client::Connection,\n                    _qhandle: &wayland_client::QueueHandle<Self>,\n                ) {\n                }\n            }\n        )*\n    };\n\n    ($handler:ty) => {\n        impl_dispatch_manager!($handler => [\n            wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_manager_v1::ZwlrDataControlManagerV1,\n            wayland_protocols::ext::data_control::v1::client::ext_data_control_manager_v1::ExtDataControlManagerV1\n        ]);\n    };\n}\npub(crate) use impl_dispatch_manager;\n\nmacro_rules! impl_dispatch_device {\n    ($handler:ty, $udata:ty, $code:expr => [$(($iface:ty, $opcode:path, $offer:ty)),*]) => {\n        $(\n            impl Dispatch<$iface, $udata> for $handler {\n                fn event(\n                    state: &mut Self,\n                    _proxy: &$iface,\n                    event: <$iface as wayland_client::Proxy>::Event,\n                    data: &$udata,\n                    _conn: &wayland_client::Connection,\n                    _qhandle: &wayland_client::QueueHandle<Self>,\n                ) {\n                    type Event = <$iface as wayland_client::Proxy>::Event;\n\n                    ($code)(state, event, data)\n                }\n\n                event_created_child!($handler, $iface, [\n                    $opcode => ($offer, ()),\n                ]);\n            }\n        )*\n    };\n\n    ($handler:ty, $udata:ty, $code:expr) => {\n        impl_dispatch_device!($handler, $udata, $code => [\n            (\n                wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_device_v1::ZwlrDataControlDeviceV1,\n                wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_device_v1::EVT_DATA_OFFER_OPCODE,\n                wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_offer_v1::ZwlrDataControlOfferV1\n            ),\n            (\n                wayland_protocols::ext::data_control::v1::client::ext_data_control_device_v1::ExtDataControlDeviceV1,\n                wayland_protocols::ext::data_control::v1::client::ext_data_control_device_v1::EVT_DATA_OFFER_OPCODE,\n                wayland_protocols::ext::data_control::v1::client::ext_data_control_offer_v1::ExtDataControlOfferV1\n            )\n        ]);\n    };\n}\npub(crate) use impl_dispatch_device;\n\nmacro_rules! impl_dispatch_source {\n    ($handler:ty, $code:expr => [$($iface:ty),*]) => {\n        $(\n            impl Dispatch<$iface, ()> for $handler {\n                fn event(\n                    state: &mut Self,\n                    proxy: &$iface,\n                    event: <$iface as wayland_client::Proxy>::Event,\n                    _data: &(),\n                    _conn: &wayland_client::Connection,\n                    _qhandle: &wayland_client::QueueHandle<Self>,\n                ) {\n                    type Event = <$iface as wayland_client::Proxy>::Event;\n\n                    let source = $crate::data_control::Source::from(proxy.clone());\n                    ($code)(state, source, event)\n                }\n            }\n        )*\n    };\n\n    ($handler:ty, $code:expr) => {\n        impl_dispatch_source!($handler, $code => [\n            wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_source_v1::ZwlrDataControlSourceV1,\n            wayland_protocols::ext::data_control::v1::client::ext_data_control_source_v1::ExtDataControlSourceV1\n        ]);\n    };\n}\npub(crate) use impl_dispatch_source;\n\nmacro_rules! impl_dispatch_offer {\n    ($handler:ty, $code:expr => [$($iface:ty),*]) => {\n        $(\n            impl Dispatch<$iface, ()> for $handler {\n                fn event(\n                    state: &mut Self,\n                    proxy: &$iface,\n                    event: <$iface as wayland_client::Proxy>::Event,\n                    _data: &(),\n                    _conn: &wayland_client::Connection,\n                    _qhandle: &wayland_client::QueueHandle<Self>,\n                ) {\n                    type Event = <$iface as wayland_client::Proxy>::Event;\n\n                    let offer = $crate::data_control::Offer::from(proxy.clone());\n                    ($code)(state, offer, event)\n                }\n            }\n        )*\n    };\n\n    ($handler:ty, $code:expr) => {\n        impl_dispatch_offer!($handler, $code => [\n            wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_offer_v1::ZwlrDataControlOfferV1,\n            wayland_protocols::ext::data_control::v1::client::ext_data_control_offer_v1::ExtDataControlOfferV1\n        ]);\n    };\n\n    ($handler:ty) => {\n        impl_dispatch_offer!($handler, |_, _, _: Event| ());\n    };\n}\npub(crate) use impl_dispatch_offer;\n"
  },
  {
    "path": "dep-crates/wl-clipboard-rs/src/lib.rs",
    "content": "//! A safe Rust crate for working with the Wayland clipboard.\n//!\n//! This crate is intended to be used by terminal applications, clipboard managers and other\n//! utilities which don't spawn Wayland surfaces (windows). If your application has a window,\n//! please use the appropriate Wayland protocols for interacting with the Wayland clipboard\n//! (`wl_data_device` from the core Wayland protocol, the `primary_selection` protocol for the\n//! primary selection), for example via the\n//! [smithay-clipboard](https://crates.io/crates/smithay-clipboard) crate.\n//!\n//! The protocol used for clipboard interaction is `ext-data-control` or `wlr-data-control`. When\n//! using the regular clipboard, the compositor must support any version of either protocol. When\n//! using the \"primary\" clipboard, the compositor must support any version of `ext-data-control`,\n//! or the second version of the `wlr-data-control` protocol.\n//!\n//! For example applications using these features, see `wl-clipboard-rs-tools/src/bin/wl_copy.rs`\n//! and `wl-clipboard-rs-tools/src/bin/wl_paste.rs` which implement terminal apps similar to\n//! [wl-clipboard](https://github.com/bugaevc/wl-clipboard) or\n//! `wl-clipboard-rs-tools/src/bin/wl_clip.rs` which implements a Wayland version of `xclip`.\n//!\n//! The Rust implementation of the Wayland client is used by default; use the `native_lib` feature\n//! to link to `libwayland-client.so` for communication instead. A `dlopen` feature is also\n//! available for loading `libwayland-client.so` dynamically at runtime rather than linking to it.\n//!\n//! The code of the crate itself (and the code of the example utilities) is 100% safe Rust. This\n//! doesn't include the dependencies.\n//!\n//! # Examples\n//!\n//! Copying to the regular clipboard:\n//! ```no_run\n//! # extern crate wl_clipboard_rs;\n//! # fn foo() -> Result<(), Box<dyn std::error::Error>> {\n//! use wl_clipboard_rs::copy::{MimeType, Options, Source};\n//!\n//! let opts = Options::new();\n//! opts.copy(Source::Bytes(\"Hello world!\".to_string().into_bytes().into()), MimeType::Autodetect)?;\n//! # Ok(())\n//! # }\n//! ```\n//!\n//! Pasting plain text from the regular clipboard:\n//! ```no_run\n//! # extern crate wl_clipboard_rs;\n//! # fn foo() -> Result<(), Box<dyn std::error::Error>> {\n//! use std::io::Read;\n//! use wl_clipboard_rs::{paste::{get_contents, ClipboardType, Error, MimeType, Seat}};\n//!\n//! let result = get_contents(ClipboardType::Regular, Seat::Unspecified, MimeType::Text);\n//! match result {\n//!     Ok((mut pipe, _)) => {\n//!         let mut contents = vec![];\n//!         pipe.read_to_end(&mut contents)?;\n//!         println!(\"Pasted: {}\", String::from_utf8_lossy(&contents));\n//!     }\n//!\n//!     Err(Error::NoSeats) | Err(Error::ClipboardEmpty) | Err(Error::NoMimeType) => {\n//!         // The clipboard is empty or doesn't contain text, nothing to worry about.\n//!     }\n//!\n//!     Err(err) => Err(err)?\n//! }\n//! # Ok(())\n//! # }\n//! ```\n//!\n//! Checking if the \"primary\" clipboard is supported (note that this might be unnecessary depending\n//! on your crate usage, the regular copying and pasting functions do report if the primary\n//! selection is unsupported when it is requested):\n//!\n//! ```no_run\n//! # extern crate wl_clipboard_rs;\n//! # fn foo() -> Result<(), Box<dyn std::error::Error>> {\n//! use wl_clipboard_rs::utils::{is_primary_selection_supported, PrimarySelectionCheckError};\n//!\n//! match is_primary_selection_supported() {\n//!     Ok(supported) => {\n//!         // We have our definitive result. False means that ext/wlr-data-control is present\n//!         // and did not signal the primary selection support, or that only wlr-data-control\n//!         // version 1 is present (which does not support primary selection).\n//!     },\n//!     Err(PrimarySelectionCheckError::NoSeats) => {\n//!         // Impossible to give a definitive result. Primary selection may or may not be\n//!         // supported.\n//!\n//!         // The required protocol (ext-data-control, or wlr-data-control version 2) is there,\n//!         // but there are no seats. Unfortunately, at least one seat is needed to check for the\n//!         // primary clipboard support.\n//!     },\n//!     Err(PrimarySelectionCheckError::MissingProtocol) => {\n//!         // The data-control protocol (required for wl-clipboard-rs operation) is not\n//!         // supported by the compositor.\n//!     },\n//!     Err(_) => {\n//!         // Some communication error occurred.\n//!     }\n//! }\n//! # Ok(())\n//! # }\n//! ```\n//!\n//! # Included terminal utilities\n//!\n//! - `wl-paste`: implements `wl-paste` from\n//!   [wl-clipboard](https://github.com/bugaevc/wl-clipboard).\n//! - `wl-copy`: implements `wl-copy` from [wl-clipboard](https://github.com/bugaevc/wl-clipboard).\n//! - `wl-clip`: a Wayland version of `xclip`.\n#![allow(clippy::all)]\n#![doc(html_root_url = \"https://docs.rs/wl-clipboard-rs/0.9.2\")]\nmod common;\nmod data_control;\nmod seat_data;\n\n#[cfg(test)]\n#[allow(unsafe_code)] // It's more convenient for testing some stuff.\nmod tests;\n\npub mod copy;\npub mod paste;\npub mod utils;\n"
  },
  {
    "path": "dep-crates/wl-clipboard-rs/src/paste.rs",
    "content": "//! Getting the offered MIME types and the clipboard contents.\n\nuse std::collections::{HashMap, HashSet};\nuse std::ffi::OsString;\nuse std::io::Read;\nuse std::os::fd::AsFd;\nuse std::sync::mpsc;\nuse std::thread::JoinHandle;\nuse std::{io, thread};\n\nuse os_pipe::{pipe, PipeReader};\nuse wayland_client::globals::GlobalListContents;\nuse wayland_client::protocol::wl_registry::WlRegistry;\nuse wayland_client::protocol::wl_seat::WlSeat;\nuse wayland_client::{\n    delegate_dispatch, event_created_child, ConnectError, Dispatch, DispatchError, EventQueue,\n};\n\nuse crate::common::{self, initialize};\nuse crate::data_control::{\n    self, impl_dispatch_device, impl_dispatch_manager, impl_dispatch_offer, Offer,\n};\nuse crate::seat_data::SeatData;\nuse crate::utils::is_text;\n\n/// The clipboard to operate on.\n#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash, PartialOrd, Ord, Default)]\n#[cfg_attr(test, derive(proptest_derive::Arbitrary))]\npub enum ClipboardType {\n    /// The regular clipboard.\n    #[default]\n    Regular,\n    /// The \"primary\" clipboard.\n    ///\n    /// Working with the \"primary\" clipboard requires the compositor to support ext-data-control,\n    /// or wlr-data-control version 2 or above.\n    Primary,\n}\n\n/// MIME types that can be requested from the clipboard.\n#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash, PartialOrd, Ord)]\npub enum MimeType<'a> {\n    /// Request any available MIME type.\n    ///\n    /// If multiple MIME types are offered, the requested MIME type is unspecified and depends on\n    /// the order they are received from the Wayland compositor. However, plain text formats are\n    /// prioritized, so if a plain text format is available among others then it will be requested.\n    Any,\n    /// Request a plain text MIME type.\n    ///\n    /// This will request one of the multiple common plain text MIME types. It will prioritize MIME\n    /// types known to return UTF-8 text.\n    Text,\n    /// Request the given MIME type, and if it's not available fall back to `MimeType::Text`.\n    ///\n    /// Example use-case: pasting `text/html` should try `text/html` first, but if it's not\n    /// available, any other plain text format will do fine too.\n    TextWithPriority(&'a str),\n    /// Request a specific MIME type.\n    Specific(&'a str),\n}\n\n/// Seat to operate on.\n#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash, PartialOrd, Ord, Default)]\npub enum Seat<'a> {\n    /// Operate on one of the existing seats depending on the order returned by the compositor.\n    ///\n    /// This is perfectly fine when only a single seat is present, so for most configurations.\n    #[default]\n    Unspecified,\n    /// Operate on a seat with the given name.\n    Specific(&'a str),\n}\n\nstruct State {\n    common: common::State,\n    // The value is the set of MIME types in the offer.\n    // TODO: We never remove offers from here, even if we don't use them or after destroying them.\n    offers: HashMap<Offer, HashSet<String>>,\n    got_primary_selection: bool,\n}\n\ndelegate_dispatch!(State: [WlSeat: ()] => common::State);\n\nimpl AsMut<common::State> for State {\n    fn as_mut(&mut self) -> &mut common::State {\n        &mut self.common\n    }\n}\n\n/// Errors that can occur for pasting and listing MIME types.\n///\n/// You may want to ignore some of these errors (rather than show an error message), like\n/// `NoSeats`, `ClipboardEmpty` or `NoMimeType` as they are essentially equivalent to an empty\n/// clipboard.\n#[derive(thiserror::Error, Debug)]\npub enum Error {\n    #[error(\"There are no seats\")]\n    NoSeats,\n\n    #[error(\"The clipboard of the requested seat is empty\")]\n    ClipboardEmpty,\n\n    #[error(\"No suitable type of content copied\")]\n    NoMimeType,\n\n    #[error(\"Couldn't open the provided Wayland socket\")]\n    SocketOpenError(#[source] io::Error),\n\n    #[error(\"Couldn't connect to the Wayland compositor\")]\n    WaylandConnection(#[source] ConnectError),\n\n    #[error(\"Wayland compositor communication error\")]\n    WaylandCommunication(#[source] DispatchError),\n\n    #[error(\n        \"A required Wayland protocol ({} version {}) is not supported by the compositor\",\n        name,\n        version\n    )]\n    MissingProtocol { name: &'static str, version: u32 },\n\n    #[error(\"The compositor does not support primary selection\")]\n    PrimarySelectionUnsupported,\n\n    #[error(\"The requested seat was not found\")]\n    SeatNotFound,\n\n    #[error(\"Couldn't create a pipe for content transfer\")]\n    PipeCreation(#[source] io::Error),\n}\n\nimpl From<common::Error> for Error {\n    fn from(x: common::Error) -> Self {\n        use common::Error::*;\n\n        match x {\n            SocketOpenError(err) => Error::SocketOpenError(err),\n            WaylandConnection(err) => Error::WaylandConnection(err),\n            WaylandCommunication(err) => Error::WaylandCommunication(err.into()),\n            MissingProtocol { name, version } => Error::MissingProtocol { name, version },\n        }\n    }\n}\n\nimpl Dispatch<WlRegistry, GlobalListContents> for State {\n    fn event(\n        _state: &mut Self,\n        _proxy: &WlRegistry,\n        _event: <WlRegistry as wayland_client::Proxy>::Event,\n        _data: &GlobalListContents,\n        _conn: &wayland_client::Connection,\n        _qhandle: &wayland_client::QueueHandle<Self>,\n    ) {\n    }\n}\n\nimpl_dispatch_manager!(State);\n\nimpl_dispatch_device!(State, WlSeat, |state: &mut Self, event, seat| {\n    match event {\n        Event::DataOffer { id } => {\n            let offer = data_control::Offer::from(id);\n            state.offers.insert(offer, HashSet::new());\n        }\n        Event::Selection { id } => {\n            let offer = id.map(data_control::Offer::from);\n            let seat = state.common.seats.get_mut(seat).unwrap();\n            seat.set_offer(offer);\n        }\n        Event::Finished => {\n            // Destroy the device stored in the seat as it's no longer valid.\n            let seat = state.common.seats.get_mut(seat).unwrap();\n            seat.set_device(None);\n        }\n        Event::PrimarySelection { id } => {\n            let offer = id.map(data_control::Offer::from);\n            state.got_primary_selection = true;\n            let seat = state.common.seats.get_mut(seat).unwrap();\n            seat.set_primary_offer(offer);\n        }\n        _ => (),\n    }\n});\n\nimpl_dispatch_offer!(State, |state: &mut Self, offer: Offer, event| {\n    if let Event::Offer { mime_type } = event {\n        state.offers.get_mut(&offer).unwrap().insert(mime_type);\n    }\n});\n\nfn get_offer(\n    primary: bool,\n    seat: Seat<'_>,\n    socket_name: Option<OsString>,\n) -> Result<(EventQueue<State>, State, Offer), Error> {\n    let (mut queue, mut common) = initialize(primary, socket_name)?;\n\n    // Check if there are no seats.\n    if common.seats.is_empty() {\n        return Err(Error::NoSeats);\n    }\n\n    // Go through the seats and get their data devices.\n    for (seat, data) in &mut common.seats {\n        let device = common\n            .clipboard_manager\n            .get_data_device(seat, &queue.handle(), seat.clone());\n        data.set_device(Some(device));\n    }\n\n    let mut state = State {\n        common,\n        offers: HashMap::new(),\n        got_primary_selection: false,\n    };\n\n    // Retrieve all seat names and offers.\n    queue\n        .roundtrip(&mut state)\n        .map_err(Error::WaylandCommunication)?;\n\n    // Check if the compositor supports primary selection.\n    if primary && !state.got_primary_selection {\n        return Err(Error::PrimarySelectionUnsupported);\n    }\n\n    // Figure out which offer we're interested in.\n    let data = get_seat(&mut state, seat);\n\n    let Some(data) = data else {\n        return Err(Error::SeatNotFound);\n    };\n\n    let offer = if primary {\n        &data.primary_offer\n    } else {\n        &data.offer\n    };\n\n    // Check if we found anything.\n    match offer.clone() {\n        Some(offer) => Ok((queue, state, offer)),\n        None => Err(Error::ClipboardEmpty),\n    }\n}\n\n/// Retrieves the offered MIME types.\n///\n/// If `seat` is `None`, uses an unspecified seat (it depends on the order returned by the\n/// compositor). This is perfectly fine when only a single seat is present, so for most\n/// configurations.\n///\n/// # Examples\n///\n/// ```no_run\n/// # extern crate wl_clipboard_rs;\n/// # use wl_clipboard_rs::paste::Error;\n/// # fn foo() -> Result<(), Error> {\n/// use wl_clipboard_rs::{paste::{get_mime_types, ClipboardType, Seat}};\n///\n/// let mime_types = get_mime_types(ClipboardType::Regular, Seat::Unspecified)?;\n/// for mime_type in mime_types {\n///     println!(\"{}\", mime_type);\n/// }\n/// # Ok(())\n/// # }\n/// ```\n#[inline]\npub fn get_mime_types(clipboard: ClipboardType, seat: Seat<'_>) -> Result<HashSet<String>, Error> {\n    get_mime_types_internal(clipboard, seat, None)\n}\n\n// The internal function accepts the socket name, used for tests.\npub(crate) fn get_mime_types_internal(\n    clipboard: ClipboardType,\n    seat: Seat<'_>,\n    socket_name: Option<OsString>,\n) -> Result<HashSet<String>, Error> {\n    let primary = clipboard == ClipboardType::Primary;\n    let (_, mut state, offer) = get_offer(primary, seat, socket_name)?;\n    Ok(state.offers.remove(&offer).unwrap())\n}\n\n/// Retrieves the clipboard contents.\n///\n/// This function returns a tuple of the reading end of a pipe containing the clipboard contents\n/// and the actual MIME type of the contents.\n///\n/// If `seat` is `None`, uses an unspecified seat (it depends on the order returned by the\n/// compositor). This is perfectly fine when only a single seat is present, so for most\n/// configurations.\n///\n/// # Examples\n///\n/// ```no_run\n/// # extern crate wl_clipboard_rs;\n/// # fn foo() -> Result<(), Box<dyn std::error::Error>> {\n/// use std::io::Read;\n/// use wl_clipboard_rs::{paste::{get_contents, ClipboardType, Error, MimeType, Seat}};\n///\n/// let result = get_contents(ClipboardType::Regular, Seat::Unspecified, MimeType::Any);\n/// match result {\n///     Ok((mut pipe, mime_type)) => {\n///         println!(\"Got data of the {} MIME type\", &mime_type);\n///\n///         let mut contents = vec![];\n///         pipe.read_to_end(&mut contents)?;\n///         println!(\"Read {} bytes of data\", contents.len());\n///     }\n///\n///     Err(Error::NoSeats) | Err(Error::ClipboardEmpty) | Err(Error::NoMimeType) => {\n///         // The clipboard is empty, nothing to worry about.\n///     }\n///\n///     Err(err) => Err(err)?\n/// }\n/// # Ok(())\n/// # }\n/// ```\n#[inline]\npub fn get_contents(\n    clipboard: ClipboardType,\n    seat: Seat<'_>,\n    mime_type: MimeType<'_>,\n) -> Result<(PipeReader, String), Error> {\n    get_contents_internal(clipboard, seat, mime_type, None)\n}\n\n// The internal function accepts the socket name, used for tests.\npub(crate) fn get_contents_internal(\n    clipboard: ClipboardType,\n    seat: Seat<'_>,\n    mime_type: MimeType<'_>,\n    socket_name: Option<OsString>,\n) -> Result<(PipeReader, String), Error> {\n    let primary = clipboard == ClipboardType::Primary;\n    let (mut queue, mut state, offer) = get_offer(primary, seat, socket_name)?;\n\n    let mime_types = state.offers.remove(&offer).unwrap();\n\n    // Find the desired MIME type.\n    let mime_type = check_mime_type(mime_types, mime_type);\n\n    // Check if a suitable MIME type is copied.\n    if mime_type.is_none() {\n        return Err(Error::NoMimeType);\n    }\n\n    let mime_type = mime_type.unwrap();\n\n    // Create a pipe for content transfer.\n    let (read, write) = pipe().map_err(Error::PipeCreation)?;\n\n    // Start the transfer.\n    offer.receive(mime_type.clone(), write.as_fd());\n    drop(write);\n\n    // A flush() is not enough here, it will result in sometimes pasting empty contents. I suspect this is due to a\n    // race between the compositor reacting to the receive request, and the compositor reacting to wl-paste\n    // disconnecting after queue is dropped. The roundtrip solves that race.\n    queue\n        .roundtrip(&mut state)\n        .map_err(Error::WaylandCommunication)?;\n\n    Ok((read, mime_type))\n}\n\n/// Asynchronously sends clipboard contents through mpsc::channel.\n///\n/// This function returns a mpsc::Receiver containing the reading end of pipes containing the clipboard contents\n/// and the actual MIME type of the content.\n///\n/// If `seat` is `None`, uses an unspecified seat (it depends on the order returned by the\n/// compositor). This is perfectly fine when only a single seat is present, so for most\n/// configurations.\n///\n/// # Examples\n///\n/// ```no_run\n/// # extern crate wl_clipboard_rs;\n/// # fn foo() -> Result<(), Box<dyn std::error::Error>> {\n/// use std::io::Read;\n/// use wl_clipboard_rs::paste::{get_contents_channel, Error, MimeType, Seat};\n///\n/// let result = get_contents_channel(Seat::Unspecified, MimeType::Any);\n/// match result {\n///     Ok(rx) => {\n///         loop {\n///             match rx.recv() {\n///                 Ok(Ok((mut pipe, mime_type))) => {\n///                     println!(\"Got data of the {} MIME type\", &mime_type);\n///                     let mut contents = vec![];\n///                     pipe.read_to_end(&mut contents)?;\n///                     println!(\"Read {} bytes of data\", contents.len());\n///                 }\n///                 Ok(Err(Error::NoSeats)) | Ok(Err(Error::ClipboardEmpty)) | Ok(Err(Error::NoMimeType)) => {\n///                     // The clipboard is empty, nothing to worry about.\n///                 }\n///                 Ok(Err(err)) => Err(err)?, // other error getting clipboard data\n///                 Err(err) => Err(err)? // error receiving data\n///             }\n///         }\n///     }\n///\n///     Err(Error::WaylandConnection) | Err(Error::WaylandCommunication) | Err(Error::MissingProtocol) => {\n///         // Error setting up channel\n///     }\n///\n///     Err(err) => Err(err)?\n/// }\n/// # Ok(())\n/// # }\n/// ```\n#[inline]\npub fn get_contents_channel(\n    seat: Seat<'static>,\n    mime_type: MimeType<'static>,\n) -> Result<mpsc::Receiver<Result<(PipeReader, String), Error>>, Error> {\n    get_contents_channel_internal(seat, mime_type, None)\n}\n\npub(crate) fn get_contents_channel_internal(\n    seat: Seat<'static>,\n    mime_type: MimeType<'static>,\n    socket_name: Option<OsString>,\n) -> Result<mpsc::Receiver<Result<(PipeReader, String), Error>>, Error> {\n    let (sender, receiver) = mpsc::channel();\n    let (queue, mut common) = initialize(false, socket_name)?;\n    for (seat, data) in &mut common.seats {\n        let device = common\n            .clipboard_manager\n            .get_data_device(seat, &queue.handle(), seat.clone());\n        data.set_device(Some(device));\n    }\n    let state = State {\n        common,\n        offers: HashMap::new(),\n        got_primary_selection: false,\n    };\n    thread::spawn(move || run_dispatch_loop(queue, state, seat, mime_type, sender));\n    Ok(receiver)\n}\n\nfn run_dispatch_loop(\n    mut queue: EventQueue<State>,\n    mut state: State,\n    seat: Seat<'static>,\n    mime_type: MimeType<'static>,\n    sender: mpsc::Sender<Result<(PipeReader, String), Error>>,\n) {\n    loop {\n        if let Err(err) = queue.blocking_dispatch(&mut state) {\n            if sender.send(Err(Error::WaylandCommunication(err))).is_err() {\n                return; // receiver closed\n            }\n            continue;\n        }\n\n        let Some(seat_data) = get_seat(&mut state, seat) else {\n            if sender.send(Err(Error::NoSeats)).is_err() {\n                return; // receiver closed\n            }\n            continue;\n        };\n\n        // should also not happen as new data should be put into offers\n        let Some(offer) = seat_data.offer.take() else {\n            continue;\n        };\n\n        // shouldn't happen\n        let Some(mime_types) = state.offers.remove(&offer) else {\n            continue;\n        };\n\n        let Some(mime_type) = check_mime_type(mime_types, mime_type) else {\n            if sender.send(Err(Error::NoMimeType)).is_err() {\n                return; // receiver closed\n            }\n            continue;\n        };\n\n        let Ok((read, write)) = pipe() else {\n            if sender\n                .send(Err(Error::PipeCreation(io::Error::last_os_error())))\n                .is_err()\n            {\n                return; // receiver closed\n            }\n            continue;\n        };\n\n        offer.receive(mime_type.clone(), write.as_fd());\n        drop(write);\n\n        if let Err(err) = queue.roundtrip(&mut state) {\n            if sender.send(Err(Error::WaylandCommunication(err))).is_err() {\n                return; // receiver closed\n            }\n            continue;\n        }\n\n        if sender.send(Ok((read, mime_type))).is_err() {\n            return; // receiver closed\n        }\n    }\n}\n\n/// Asynchronously handle all clipboard contents with a callback.\n///\n/// This function returns a JoinHandle to the background thread and accepts a callback that either receives an Ok variant containing a HashMap of all offered MIME types\n/// and a function to load the contents of a specific MIME type or Error if something went wrong.\n/// If the function returns true, the thread will exit.\n///\n/// If `seat` is `None`, uses an unspecified seat (it depends on the order returned by the\n/// compositor). This is perfectly fine when only a single seat is present, so for most\n/// configurations.\n///\n/// # Examples\n///\n/// ```no_run\n/// # extern crate wl_clipboard_rs;\n/// use wl_clipboard_rs::paste::get_all_contents_channel;\n///\n/// fn handle_values(\n///     data: wl_clipboard_rs::paste::Data\n/// ) -> bool {\n///   let Ok((mut mimes, mut load)) = data else {\n///     return false;\n///   };\n///   if mimes.contains(\"text/html\") {\n///     let data = load(\"text/html\".to_string()).unwrap();\n///     println!(\"Got HTML data: {}\", String::from_utf8_lossy(&data));\n///   }\n///   false\n/// }\n///\n/// fn foo() -> Result<(), Box<dyn std::error::Error>> {\n///   let result = get_all_contents_channel(Seat::Unspecified, Box::new(filter_mime));\n///   match result {\n///     Ok(handle) => {\n///         handle.join()\n///     }\n///     Err(Error::WaylandConnection) | Err(Error::WaylandCommunication) | Err(Error::MissingProtocol) => {\n///         // Error setting up listener\n///     }\n///     Err(err) => Err(err)?\n///   }\n///   Ok(())\n/// }\n/// ```\n#[inline]\npub fn get_all_contents_callback(\n    seat: Seat<'static>,\n    callback: Box<dyn Fn(CallbackData) -> bool + Send + Sync + 'static>,\n) -> Result<JoinHandle<()>, Error> {\n    get_all_contents_callback_internal(seat, callback, None)\n}\n\npub type CallbackData<'a> = Result<\n    (\n        HashSet<String>,\n        &'a mut (dyn FnMut(String) -> Result<Vec<u8>, Error> + Send),\n    ),\n    Error,\n>;\n\npub(crate) fn get_all_contents_callback_internal(\n    seat: Seat<'static>,\n    callback: Box<dyn Fn(CallbackData) -> bool + Send + Sync + 'static>,\n    socket_name: Option<OsString>,\n) -> Result<JoinHandle<()>, Error> {\n    let (queue, mut common) = initialize(false, socket_name)?;\n    for (seat, data) in &mut common.seats {\n        let device = common\n            .clipboard_manager\n            .get_data_device(seat, &queue.handle(), seat.clone());\n        data.set_device(Some(device));\n    }\n    let state = State {\n        common,\n        offers: HashMap::new(),\n        got_primary_selection: false,\n    };\n    let handle = thread::spawn(move || run_callback_dispatch_loop(queue, state, seat, callback));\n    Ok(handle)\n}\n\nfn run_callback_dispatch_loop(\n    mut queue: EventQueue<State>,\n    mut state: State,\n    seat: Seat<'static>,\n    callback: Box<dyn Fn(CallbackData) -> bool + Send + Sync + 'static>,\n) {\n    loop {\n        if let Err(err) = queue.blocking_dispatch(&mut state) {\n            if callback(Err(Error::WaylandCommunication(err))) {\n                return;\n            }\n            continue;\n        }\n\n        let Some(seat_data) = get_seat(&mut state, seat) else {\n            if callback(Err(Error::NoSeats)) {\n                return;\n            }\n            continue;\n        };\n\n        // should also not happen as new data should be put into offers\n        let Some(offer) = seat_data.offer.take() else {\n            continue;\n        };\n\n        // shouldn't happen\n        let Some(mime_types) = state.offers.remove(&offer) else {\n            continue;\n        };\n\n        {\n            let mut load = create_load_mime_fn(offer.clone(), &mut queue, &mut state);\n            if callback(Ok((mime_types, &mut load))) {\n                return;\n            }\n        }\n    }\n}\n\nfn create_load_mime_fn<'a>(\n    offer: Offer,\n    queue: &'a mut EventQueue<State>,\n    state: &'a mut State,\n) -> impl FnMut(String) -> Result<Vec<u8>, Error> + use<'a> {\n    move |mime: String| {\n        let Ok((mut read, write)) = pipe() else {\n            return Err(Error::PipeCreation(io::Error::last_os_error()));\n        };\n        offer.receive(mime, write.as_fd());\n        drop(write);\n        let mut contents = Vec::new();\n        let _ = queue.roundtrip(state);\n        let _ = read.read_to_end(&mut contents);\n        Ok(contents)\n    }\n}\n\nfn get_seat<'a>(state: &'a mut State, seat: Seat) -> Option<&'a mut SeatData> {\n    match seat {\n        Seat::Unspecified => state.common.seats.values_mut().next(),\n        Seat::Specific(name) => state\n            .common\n            .seats\n            .values_mut()\n            .find(|data| data.name.as_deref() == Some(name)),\n    }\n}\n\nfn check_mime_type(mut mime_types: HashSet<String>, mime_type: MimeType) -> Option<String> {\n    match mime_type {\n        MimeType::Any => mime_types\n            .take(\"text/plain;charset=utf-8\")\n            .or_else(|| mime_types.take(\"UTF8_STRING\"))\n            .or_else(|| mime_types.iter().find(|x| is_text(x)).cloned())\n            .or_else(|| mime_types.drain().next()),\n        MimeType::Text => mime_types\n            .take(\"text/plain;charset=utf-8\")\n            .or_else(|| mime_types.take(\"UTF8_STRING\"))\n            .or_else(|| mime_types.drain().find(|x| is_text(x))),\n        MimeType::TextWithPriority(priority) => mime_types\n            .take(priority)\n            .or_else(|| mime_types.take(\"text/plain;charset=utf-8\"))\n            .or_else(|| mime_types.take(\"UTF8_STRING\"))\n            .or_else(|| mime_types.drain().find(|x| is_text(x))),\n        MimeType::Specific(mime_type) => mime_types.take(mime_type),\n    }\n}\n"
  },
  {
    "path": "dep-crates/wl-clipboard-rs/src/seat_data.rs",
    "content": "use crate::data_control::{Device, Offer};\n\n#[derive(Default)]\npub struct SeatData {\n    /// The name of this seat, if any.\n    pub name: Option<String>,\n\n    /// The data device of this seat, if any.\n    pub device: Option<Device>,\n\n    /// The data offer of this seat, if any.\n    pub offer: Option<Offer>,\n\n    /// The primary-selection data offer of this seat, if any.\n    pub primary_offer: Option<Offer>,\n}\n\nimpl SeatData {\n    /// Sets this seat's name.\n    pub fn set_name(&mut self, name: String) {\n        self.name = Some(name)\n    }\n\n    /// Sets this seat's device.\n    ///\n    /// Destroys the old one, if any.\n    pub fn set_device(&mut self, device: Option<Device>) {\n        let old_device = self.device.take();\n        self.device = device;\n\n        if let Some(device) = old_device {\n            device.destroy();\n        }\n    }\n\n    /// Sets this seat's data offer.\n    ///\n    /// Destroys the old one, if any.\n    pub fn set_offer(&mut self, new_offer: Option<Offer>) {\n        let old_offer = self.offer.take();\n        self.offer = new_offer;\n\n        if let Some(offer) = old_offer {\n            offer.destroy();\n        }\n    }\n\n    /// Sets this seat's primary-selection data offer.\n    ///\n    /// Destroys the old one, if any.\n    pub fn set_primary_offer(&mut self, new_offer: Option<Offer>) {\n        let old_offer = self.primary_offer.take();\n        self.primary_offer = new_offer;\n\n        if let Some(offer) = old_offer {\n            offer.destroy();\n        }\n    }\n}\n"
  },
  {
    "path": "dep-crates/wl-clipboard-rs/src/tests/copy.rs",
    "content": "use std::collections::HashMap;\nuse std::io::Read;\nuse std::sync::mpsc::channel;\nuse std::sync::{Arc, Mutex};\n\nuse proptest::prelude::*;\nuse wayland_protocols_wlr::data_control::v1::server::zwlr_data_control_manager_v1::ZwlrDataControlManagerV1;\n\nuse crate::copy::*;\nuse crate::paste;\nuse crate::paste::get_contents_internal;\nuse crate::tests::state::*;\nuse crate::tests::TestServer;\n\n#[test]\nfn clear_test() {\n    let server = TestServer::new();\n    server\n        .display\n        .handle()\n        .create_global::<State, ZwlrDataControlManagerV1, ()>(2, ());\n\n    let state = State {\n        seats: HashMap::from([(\n            \"seat0\".into(),\n            SeatInfo {\n                offer: Some(OfferInfo::Buffered {\n                    data: HashMap::from([(\"regular\".into(), vec![1, 2, 3])]),\n                }),\n                primary_offer: Some(OfferInfo::Buffered {\n                    data: HashMap::from([(\"primary\".into(), vec![1, 2, 3])]),\n                }),\n            },\n        )]),\n        ..Default::default()\n    };\n    state.create_seats(&server);\n    let state = Arc::new(Mutex::new(state));\n\n    let socket_name = server.socket_name().to_owned();\n    server.run_mutex(state.clone());\n\n    clear_internal(ClipboardType::Regular, Seat::All, Some(socket_name)).unwrap();\n\n    let state = state.lock().unwrap();\n    assert!(state.seats[\"seat0\"].offer.is_none());\n    assert!(state.seats[\"seat0\"].primary_offer.is_some());\n}\n\n#[test]\nfn copy_test() {\n    let server = TestServer::new();\n    server\n        .display\n        .handle()\n        .create_global::<State, ZwlrDataControlManagerV1, ()>(2, ());\n\n    let (tx, rx) = channel();\n\n    let state = State {\n        seats: HashMap::from([(\n            \"seat0\".into(),\n            SeatInfo {\n                ..Default::default()\n            },\n        )]),\n        selection_updated_sender: Some(tx),\n        ..Default::default()\n    };\n    state.create_seats(&server);\n\n    let socket_name = server.socket_name().to_owned();\n    server.run(state);\n\n    let sources = vec![MimeSource {\n        source: Source::Bytes([1, 3, 3, 7][..].into()),\n        mime_type: MimeType::Specific(\"test\".into()),\n    }];\n    copy_internal(Options::new(), sources, Some(socket_name.clone())).unwrap();\n\n    // Wait for the copy.\n    let mime_types = rx.recv().unwrap().unwrap();\n    assert_eq!(mime_types, [\"test\"]);\n\n    let (mut read, mime_type) = get_contents_internal(\n        paste::ClipboardType::Regular,\n        paste::Seat::Unspecified,\n        paste::MimeType::Any,\n        Some(socket_name.clone()),\n    )\n    .unwrap();\n\n    let mut contents = vec![];\n    read.read_to_end(&mut contents).unwrap();\n\n    assert_eq!(mime_type, \"test\");\n    assert_eq!(contents, [1, 3, 3, 7]);\n\n    clear_internal(ClipboardType::Both, Seat::All, Some(socket_name)).unwrap();\n}\n\n#[test]\nfn copy_multi_test() {\n    let server = TestServer::new();\n    server\n        .display\n        .handle()\n        .create_global::<State, ZwlrDataControlManagerV1, ()>(2, ());\n\n    let (tx, rx) = channel();\n\n    let state = State {\n        seats: HashMap::from([(\n            \"seat0\".into(),\n            SeatInfo {\n                ..Default::default()\n            },\n        )]),\n        selection_updated_sender: Some(tx),\n        ..Default::default()\n    };\n    state.create_seats(&server);\n\n    let socket_name = server.socket_name().to_owned();\n    server.run(state);\n\n    let sources = vec![\n        MimeSource {\n            source: Source::Bytes([1, 3, 3, 7][..].into()),\n            mime_type: MimeType::Specific(\"test\".into()),\n        },\n        MimeSource {\n            source: Source::Bytes([2, 4, 4][..].into()),\n            mime_type: MimeType::Specific(\"test2\".into()),\n        },\n        // Ignored because it's the second \"test\" MIME type.\n        MimeSource {\n            source: Source::Bytes([4, 3, 2, 1][..].into()),\n            mime_type: MimeType::Specific(\"test\".into()),\n        },\n        // The first text source, additional text types should fall back here.\n        MimeSource {\n            source: Source::Bytes(b\"hello fallback\"[..].into()),\n            mime_type: MimeType::Text,\n        },\n        // A specific override of an additional text type.\n        MimeSource {\n            source: Source::Bytes(b\"hello TEXT\"[..].into()),\n            mime_type: MimeType::Specific(\"TEXT\".into()),\n        },\n    ];\n    copy_internal(Options::new(), sources, Some(socket_name.clone())).unwrap();\n\n    // Wait for the copy.\n    let mut mime_types = rx.recv().unwrap().unwrap();\n    mime_types.sort_unstable();\n    assert_eq!(\n        mime_types,\n        [\n            \"STRING\",\n            \"TEXT\",\n            \"UTF8_STRING\",\n            \"test\",\n            \"test2\",\n            \"text/plain\",\n            \"text/plain;charset=utf-8\",\n        ]\n    );\n\n    let expected = [\n        (\"test\", &[1, 3, 3, 7][..]),\n        (\"test2\", &[2, 4, 4][..]),\n        (\"STRING\", &b\"hello fallback\"[..]),\n        (\"TEXT\", &b\"hello TEXT\"[..]),\n    ];\n\n    for (mime_type, expected_contents) in expected {\n        let mut read = get_contents_internal(\n            paste::ClipboardType::Regular,\n            paste::Seat::Unspecified,\n            paste::MimeType::Specific(mime_type),\n            Some(socket_name.clone()),\n        )\n        .unwrap()\n        .0;\n\n        let mut contents = vec![];\n        read.read_to_end(&mut contents).unwrap();\n\n        assert_eq!(contents, expected_contents);\n    }\n\n    clear_internal(ClipboardType::Both, Seat::All, Some(socket_name)).unwrap();\n}\n\n#[test]\nfn copy_multi_no_additional_text_mime_types_test() {\n    let server = TestServer::new();\n    server\n        .display\n        .handle()\n        .create_global::<State, ZwlrDataControlManagerV1, ()>(2, ());\n\n    let (tx, rx) = channel();\n\n    let state = State {\n        seats: HashMap::from([(\n            \"seat0\".into(),\n            SeatInfo {\n                ..Default::default()\n            },\n        )]),\n        selection_updated_sender: Some(tx),\n        ..Default::default()\n    };\n    state.create_seats(&server);\n\n    let socket_name = server.socket_name().to_owned();\n    server.run(state);\n\n    let mut opts = Options::new();\n    opts.omit_additional_text_mime_types(true);\n    let sources = vec![\n        MimeSource {\n            source: Source::Bytes([1, 3, 3, 7][..].into()),\n            mime_type: MimeType::Specific(\"test\".into()),\n        },\n        MimeSource {\n            source: Source::Bytes([2, 4, 4][..].into()),\n            mime_type: MimeType::Specific(\"test2\".into()),\n        },\n        // Ignored because it's the second \"test\" MIME type.\n        MimeSource {\n            source: Source::Bytes([4, 3, 2, 1][..].into()),\n            mime_type: MimeType::Specific(\"test\".into()),\n        },\n        // A specific override of an additional text type.\n        MimeSource {\n            source: Source::Bytes(b\"hello TEXT\"[..].into()),\n            mime_type: MimeType::Specific(\"TEXT\".into()),\n        },\n    ];\n    copy_internal(opts, sources, Some(socket_name.clone())).unwrap();\n\n    // Wait for the copy.\n    let mut mime_types = rx.recv().unwrap().unwrap();\n    mime_types.sort_unstable();\n    assert_eq!(mime_types, [\"TEXT\", \"test\", \"test2\"]);\n\n    let expected = [\n        (\"test\", &[1, 3, 3, 7][..]),\n        (\"test2\", &[2, 4, 4][..]),\n        (\"TEXT\", &b\"hello TEXT\"[..]),\n    ];\n\n    for (mime_type, expected_contents) in expected {\n        let mut read = get_contents_internal(\n            paste::ClipboardType::Regular,\n            paste::Seat::Unspecified,\n            paste::MimeType::Specific(mime_type),\n            Some(socket_name.clone()),\n        )\n        .unwrap()\n        .0;\n\n        let mut contents = vec![];\n        read.read_to_end(&mut contents).unwrap();\n\n        assert_eq!(contents, expected_contents);\n    }\n\n    clear_internal(ClipboardType::Both, Seat::All, Some(socket_name)).unwrap();\n}\n\n// The idea here is to exceed the pipe capacity. This fails unless O_NONBLOCK is cleared when\n// sending data over the pipe using cat.\n#[test]\nfn copy_large() {\n    // Assuming the default pipe capacity is 65536.\n    let mut bytes_to_copy = vec![];\n    for i in 0..65536 * 10 {\n        bytes_to_copy.push((i % 256) as u8);\n    }\n\n    let server = TestServer::new();\n    server\n        .display\n        .handle()\n        .create_global::<State, ZwlrDataControlManagerV1, ()>(2, ());\n\n    let (tx, rx) = channel();\n\n    let state = State {\n        seats: HashMap::from([(\n            \"seat0\".into(),\n            SeatInfo {\n                ..Default::default()\n            },\n        )]),\n        selection_updated_sender: Some(tx),\n        // Emulate what XWayland does and set O_NONBLOCK.\n        set_nonblock_on_write_fd: true,\n        ..Default::default()\n    };\n    state.create_seats(&server);\n\n    let socket_name = server.socket_name().to_owned();\n    server.run(state);\n\n    let sources = vec![MimeSource {\n        source: Source::Bytes(bytes_to_copy.clone().into_boxed_slice()),\n        mime_type: MimeType::Specific(\"test\".into()),\n    }];\n    copy_internal(Options::new(), sources, Some(socket_name.clone())).unwrap();\n\n    // Wait for the copy.\n    let mime_types = rx.recv().unwrap().unwrap();\n    assert_eq!(mime_types, [\"test\"]);\n\n    let (mut read, mime_type) = get_contents_internal(\n        paste::ClipboardType::Regular,\n        paste::Seat::Unspecified,\n        paste::MimeType::Any,\n        Some(socket_name.clone()),\n    )\n    .unwrap();\n\n    let mut contents = vec![];\n    read.read_to_end(&mut contents).unwrap();\n\n    assert_eq!(mime_type, \"test\");\n    assert_eq!(contents.len(), bytes_to_copy.len());\n    assert_eq!(contents, bytes_to_copy);\n\n    clear_internal(ClipboardType::Both, Seat::All, Some(socket_name)).unwrap();\n}\n\nproptest! {\n    #[test]\n    fn copy_randomized(\n        mut state: State,\n        clipboard_type: ClipboardType,\n        source: Source,\n        mime_type: MimeType,\n        seat_index: prop::sample::Index,\n        clipboard_type_index: prop::sample::Index,\n    ) {\n        prop_assume!(!state.seats.is_empty());\n\n        let server = TestServer::new();\n        server\n            .display\n            .handle()\n            .create_global::<State, ZwlrDataControlManagerV1, ()>(2, ());\n\n        let (tx, rx) = channel();\n        state.selection_updated_sender = Some(tx);\n\n        state.create_seats(&server);\n\n        let seat_index = seat_index.index(state.seats.len());\n        let seat_name = state.seats.keys().nth(seat_index).unwrap();\n        let seat_name = seat_name.to_owned();\n\n        let paste_clipboard_type = match clipboard_type {\n            ClipboardType::Regular => paste::ClipboardType::Regular,\n            ClipboardType::Primary => paste::ClipboardType::Primary,\n            ClipboardType::Both => *clipboard_type_index\n                .get(&[paste::ClipboardType::Regular, paste::ClipboardType::Primary]),\n        };\n\n        let socket_name = server.socket_name().to_owned();\n        server.run(state);\n\n        let expected_contents = match &source {\n            Source::Bytes(bytes) => bytes.clone(),\n            Source::StdIn => unreachable!(),\n        };\n\n        let sources = vec![MimeSource {\n            source,\n            mime_type: mime_type.clone(),\n        }];\n        let mut opts = Options::new();\n        opts.clipboard(clipboard_type);\n        opts.seat(Seat::Specific(seat_name.clone()));\n        opts.omit_additional_text_mime_types(true);\n        copy_internal(opts, sources, Some(socket_name.clone())).unwrap();\n\n        // Wait for the copy.\n        let mut mime_types = rx.recv().unwrap().unwrap();\n        mime_types.sort_unstable();\n        match &mime_type {\n            MimeType::Autodetect => unreachable!(),\n            MimeType::Text => assert_eq!(mime_types, [\"text/plain\"]),\n            MimeType::Specific(mime) => assert_eq!(mime_types, [mime.clone()]),\n        }\n\n        let paste_mime_type = match mime_type {\n            MimeType::Autodetect => unreachable!(),\n            MimeType::Text => \"text/plain\".into(),\n            MimeType::Specific(mime) => mime,\n        };\n        let (mut read, mime_type) = get_contents_internal(\n            paste_clipboard_type,\n            paste::Seat::Specific(&seat_name),\n            paste::MimeType::Specific(&paste_mime_type),\n            Some(socket_name.clone()),\n        )\n        .unwrap();\n\n        let mut contents = vec![];\n        read.read_to_end(&mut contents).unwrap();\n\n        assert_eq!(mime_type, paste_mime_type);\n        assert_eq!(contents.into_boxed_slice(), expected_contents);\n\n        clear_internal(clipboard_type, Seat::Specific(seat_name), Some(socket_name)).unwrap();\n    }\n}\n"
  },
  {
    "path": "dep-crates/wl-clipboard-rs/src/tests/mod.rs",
    "content": "use std::ffi::OsStr;\nuse std::os::fd::OwnedFd;\nuse std::sync::atomic::AtomicU8;\nuse std::sync::atomic::Ordering::SeqCst;\nuse std::sync::{Arc, Mutex};\nuse std::thread;\n\nuse rustix::event::epoll;\nuse wayland_backend::server::ClientData;\nuse wayland_server::{Display, ListeningSocket};\n\nmod copy;\nmod paste;\nmod state;\nmod utils;\n\npub struct TestServer<S: 'static> {\n    pub display: Display<S>,\n    pub socket: ListeningSocket,\n    pub epoll: OwnedFd,\n}\n\nstruct ClientCounter(AtomicU8);\n\nimpl ClientData for ClientCounter {\n    fn disconnected(\n        &self,\n        _client_id: wayland_backend::server::ClientId,\n        _reason: wayland_backend::server::DisconnectReason,\n    ) {\n        self.0.fetch_sub(1, SeqCst);\n    }\n}\n\nimpl<S: Send + 'static> TestServer<S> {\n    pub fn new() -> Self {\n        let mut display = Display::new().unwrap();\n        let socket = ListeningSocket::bind_auto(\"wl-clipboard-rs-test\", 0..).unwrap();\n\n        let epoll = epoll::create(epoll::CreateFlags::CLOEXEC).unwrap();\n\n        epoll::add(\n            &epoll,\n            &socket,\n            epoll::EventData::new_u64(0),\n            epoll::EventFlags::IN,\n        )\n        .unwrap();\n        epoll::add(\n            &epoll,\n            display.backend().poll_fd(),\n            epoll::EventData::new_u64(1),\n            epoll::EventFlags::IN,\n        )\n        .unwrap();\n\n        TestServer {\n            display,\n            socket,\n            epoll,\n        }\n    }\n\n    pub fn socket_name(&self) -> &OsStr {\n        self.socket.socket_name().unwrap()\n    }\n\n    pub fn run(self, mut state: S) {\n        thread::spawn(move || self.run_internal(&mut state));\n    }\n\n    pub fn run_mutex(self, state: Arc<Mutex<S>>) {\n        thread::spawn(move || {\n            let mut state = state.lock().unwrap();\n            self.run_internal(&mut *state);\n        });\n    }\n\n    fn run_internal(mut self, state: &mut S) {\n        let mut waiting_for_first_client = true;\n        let client_counter = Arc::new(ClientCounter(AtomicU8::new(0)));\n\n        while client_counter.0.load(SeqCst) > 0 || waiting_for_first_client {\n            // Wait for requests from the client.\n            let mut events = epoll::EventVec::with_capacity(2);\n            epoll::wait(&self.epoll, &mut events, -1).unwrap();\n\n            for event in &events {\n                match event.data.u64() {\n                    0 => {\n                        // Try to accept a new client.\n                        if let Some(stream) = self.socket.accept().unwrap() {\n                            waiting_for_first_client = false;\n                            client_counter.0.fetch_add(1, SeqCst);\n                            self.display\n                                .handle()\n                                .insert_client(stream, client_counter.clone())\n                                .unwrap();\n                        }\n                    }\n                    1 => {\n                        // Try to dispatch client messages.\n                        self.display.dispatch_clients(state).unwrap();\n                        self.display.flush_clients().unwrap();\n                    }\n                    x => panic!(\"unexpected epoll event: {x}\"),\n                }\n            }\n        }\n    }\n}\n\n// https://github.com/Smithay/wayland-rs/blob/90a9ad1f8f1fdef72e96d3c48bdb76b53a7722ff/wayland-tests/tests/helpers/mod.rs\n#[macro_export]\nmacro_rules! server_ignore_impl {\n    ($handler:ty => [$($iface:ty),*]) => {\n        $(\n            impl wayland_server::Dispatch<$iface, ()> for $handler {\n                fn request(\n                    _: &mut Self,\n                    _: &wayland_server::Client,\n                    _: &$iface,\n                    _: <$iface as wayland_server::Resource>::Request,\n                    _: &(),\n                    _: &wayland_server::DisplayHandle,\n                    _: &mut wayland_server::DataInit<'_, Self>,\n                ) {\n                }\n            }\n        )*\n    }\n}\n\n#[macro_export]\nmacro_rules! server_ignore_global_impl {\n    ($handler:ty => [$($iface:ty),*]) => {\n        $(\n            impl wayland_server::GlobalDispatch<$iface, ()> for $handler {\n                fn bind(\n                    _: &mut Self,\n                    _: &wayland_server::DisplayHandle,\n                    _: &wayland_server::Client,\n                    new_id: wayland_server::New<$iface>,\n                    _: &(),\n                    data_init: &mut wayland_server::DataInit<'_, Self>,\n                ) {\n                    data_init.init(new_id, ());\n                }\n            }\n        )*\n    }\n}\n"
  },
  {
    "path": "dep-crates/wl-clipboard-rs/src/tests/paste.rs",
    "content": "use std::collections::{HashMap, HashSet};\nuse std::io::Read;\n\nuse proptest::prelude::*;\nuse wayland_protocols_wlr::data_control::v1::server::zwlr_data_control_manager_v1::ZwlrDataControlManagerV1;\n\nuse crate::paste::*;\nuse crate::tests::state::*;\nuse crate::tests::TestServer;\n\n#[test]\nfn get_mime_types_test() {\n    let server = TestServer::new();\n    server\n        .display\n        .handle()\n        .create_global::<State, ZwlrDataControlManagerV1, ()>(2, ());\n\n    let state = State {\n        seats: HashMap::from([(\n            \"seat0\".into(),\n            SeatInfo {\n                offer: Some(OfferInfo::Buffered {\n                    data: HashMap::from([\n                        (\"first\".into(), vec![]),\n                        (\"second\".into(), vec![]),\n                        (\"third\".into(), vec![]),\n                    ]),\n                }),\n                ..Default::default()\n            },\n        )]),\n        ..Default::default()\n    };\n    state.create_seats(&server);\n\n    let socket_name = server.socket_name().to_owned();\n    server.run(state);\n\n    let mime_types =\n        get_mime_types_internal(ClipboardType::Regular, Seat::Unspecified, Some(socket_name))\n            .unwrap();\n\n    let expected = HashSet::from([\"first\", \"second\", \"third\"].map(String::from));\n    assert_eq!(mime_types, expected);\n}\n\n#[test]\nfn get_mime_types_no_data_control() {\n    let server = TestServer::new();\n\n    let state = State {\n        seats: HashMap::from([(\n            \"seat0\".into(),\n            SeatInfo {\n                ..Default::default()\n            },\n        )]),\n        ..Default::default()\n    };\n    state.create_seats(&server);\n\n    let socket_name = server.socket_name().to_owned();\n    server.run(state);\n\n    let result =\n        get_mime_types_internal(ClipboardType::Regular, Seat::Unspecified, Some(socket_name));\n    assert!(matches!(\n        result,\n        Err(Error::MissingProtocol {\n            name: \"ext-data-control, or wlr-data-control\",\n            version: 1\n        })\n    ));\n}\n\n#[test]\nfn get_mime_types_no_data_control_2() {\n    let server = TestServer::new();\n\n    let state = State {\n        seats: HashMap::from([(\n            \"seat0\".into(),\n            SeatInfo {\n                ..Default::default()\n            },\n        )]),\n        ..Default::default()\n    };\n    state.create_seats(&server);\n\n    let socket_name = server.socket_name().to_owned();\n    server.run(state);\n\n    let result =\n        get_mime_types_internal(ClipboardType::Primary, Seat::Unspecified, Some(socket_name));\n    assert!(matches!(\n        result,\n        Err(Error::MissingProtocol {\n            name: \"ext-data-control, or wlr-data-control\",\n            version: 2\n        })\n    ));\n}\n\n#[test]\nfn get_mime_types_no_seats() {\n    let server = TestServer::new();\n    server\n        .display\n        .handle()\n        .create_global::<State, ZwlrDataControlManagerV1, ()>(2, ());\n\n    let state = State {\n        ..Default::default()\n    };\n    state.create_seats(&server);\n\n    let socket_name = server.socket_name().to_owned();\n    server.run(state);\n\n    let result =\n        get_mime_types_internal(ClipboardType::Primary, Seat::Unspecified, Some(socket_name));\n    assert!(matches!(result, Err(Error::NoSeats)));\n}\n\n#[test]\nfn get_mime_types_empty_clipboard() {\n    let server = TestServer::new();\n    server\n        .display\n        .handle()\n        .create_global::<State, ZwlrDataControlManagerV1, ()>(2, ());\n\n    let state = State {\n        seats: HashMap::from([(\n            \"seat0\".into(),\n            SeatInfo {\n                ..Default::default()\n            },\n        )]),\n        ..Default::default()\n    };\n    state.create_seats(&server);\n\n    let socket_name = server.socket_name().to_owned();\n    server.run(state);\n\n    let result =\n        get_mime_types_internal(ClipboardType::Primary, Seat::Unspecified, Some(socket_name));\n    assert!(matches!(result, Err(Error::ClipboardEmpty)));\n}\n\n#[test]\nfn get_mime_types_specific_seat() {\n    let server = TestServer::new();\n    server\n        .display\n        .handle()\n        .create_global::<State, ZwlrDataControlManagerV1, ()>(2, ());\n\n    let state = State {\n        seats: HashMap::from([\n            (\n                \"seat0\".into(),\n                SeatInfo {\n                    ..Default::default()\n                },\n            ),\n            (\n                \"yay\".into(),\n                SeatInfo {\n                    offer: Some(OfferInfo::Buffered {\n                        data: HashMap::from([\n                            (\"first\".into(), vec![]),\n                            (\"second\".into(), vec![]),\n                            (\"third\".into(), vec![]),\n                        ]),\n                    }),\n                    ..Default::default()\n                },\n            ),\n        ]),\n        ..Default::default()\n    };\n    state.create_seats(&server);\n\n    let socket_name = server.socket_name().to_owned();\n    server.run(state);\n\n    let mime_types = get_mime_types_internal(\n        ClipboardType::Regular,\n        Seat::Specific(\"yay\"),\n        Some(socket_name),\n    )\n    .unwrap();\n\n    let expected = HashSet::from([\"first\", \"second\", \"third\"].map(String::from));\n    assert_eq!(mime_types, expected);\n}\n\n#[test]\nfn get_mime_types_primary() {\n    let server = TestServer::new();\n    server\n        .display\n        .handle()\n        .create_global::<State, ZwlrDataControlManagerV1, ()>(2, ());\n\n    let state = State {\n        seats: HashMap::from([(\n            \"seat0\".into(),\n            SeatInfo {\n                primary_offer: Some(OfferInfo::Buffered {\n                    data: HashMap::from([\n                        (\"first\".into(), vec![]),\n                        (\"second\".into(), vec![]),\n                        (\"third\".into(), vec![]),\n                    ]),\n                }),\n                ..Default::default()\n            },\n        )]),\n        ..Default::default()\n    };\n    state.create_seats(&server);\n\n    let socket_name = server.socket_name().to_owned();\n    server.run(state);\n\n    let mime_types =\n        get_mime_types_internal(ClipboardType::Primary, Seat::Unspecified, Some(socket_name))\n            .unwrap();\n\n    let expected = HashSet::from([\"first\", \"second\", \"third\"].map(String::from));\n    assert_eq!(mime_types, expected);\n}\n\n#[test]\nfn get_contents_test() {\n    let server = TestServer::new();\n    server\n        .display\n        .handle()\n        .create_global::<State, ZwlrDataControlManagerV1, ()>(2, ());\n\n    let state = State {\n        seats: HashMap::from([(\n            \"seat0\".into(),\n            SeatInfo {\n                offer: Some(OfferInfo::Buffered {\n                    data: HashMap::from([(\"application/octet-stream\".into(), vec![1, 3, 3, 7])]),\n                }),\n                ..Default::default()\n            },\n        )]),\n        ..Default::default()\n    };\n    state.create_seats(&server);\n\n    let socket_name = server.socket_name().to_owned();\n    server.run(state);\n\n    let (mut read, mime_type) = get_contents_internal(\n        ClipboardType::Regular,\n        Seat::Unspecified,\n        MimeType::Any,\n        Some(socket_name),\n    )\n    .unwrap();\n\n    assert_eq!(mime_type, \"application/octet-stream\");\n\n    let mut contents = vec![];\n    read.read_to_end(&mut contents).unwrap();\n    assert_eq!(contents, [1, 3, 3, 7]);\n}\n\n#[test]\nfn get_contents_wrong_mime_type() {\n    let server = TestServer::new();\n    server\n        .display\n        .handle()\n        .create_global::<State, ZwlrDataControlManagerV1, ()>(2, ());\n\n    let state = State {\n        seats: HashMap::from([(\n            \"seat0\".into(),\n            SeatInfo {\n                offer: Some(OfferInfo::Buffered {\n                    data: HashMap::from([(\"application/octet-stream\".into(), vec![1, 3, 3, 7])]),\n                }),\n                ..Default::default()\n            },\n        )]),\n        ..Default::default()\n    };\n    state.create_seats(&server);\n\n    let socket_name = server.socket_name().to_owned();\n    server.run(state);\n\n    let result = get_contents_internal(\n        ClipboardType::Regular,\n        Seat::Unspecified,\n        MimeType::Specific(\"wrong\"),\n        Some(socket_name),\n    );\n    assert!(matches!(result, Err(Error::NoMimeType)));\n}\n\n#[test]\nfn get_contents_channel_test() {\n    let server = TestServer::new();\n    server\n        .display\n        .handle()\n        .create_global::<State, ZwlrDataControlManagerV1, ()>(2, ());\n\n    let state = State {\n        seats: HashMap::from([(\n            \"seat0\".into(),\n            SeatInfo {\n                ..Default::default()\n            },\n        )]),\n        ..Default::default()\n    };\n    state.create_seats(&server);\n\n    let socket_name = server.socket_name().to_owned();\n    server.run(state);\n\n    let sources = vec![crate::copy::MimeSource {\n        source: crate::copy::Source::Bytes([1, 3, 3, 7, 8][..].into()),\n        mime_type: crate::copy::MimeType::Specific(\"application/octet-stream\".into()),\n    }];\n    crate::copy::copy_internal(\n        crate::copy::Options::new(),\n        sources,\n        Some(socket_name.clone()),\n    )\n    .expect(\"unable to copy\");\n\n    let tx =\n        get_contents_channel_internal(Seat::Unspecified, MimeType::Any, Some(socket_name.clone()))\n            .expect(\"unable to create channel\");\n\n    let mut result = tx.recv().expect(\"failed to receive\").expect(\"no data\");\n    assert_eq!(result.1, \"application/octet-stream\");\n\n    let mut contents = vec![];\n    result.0.read_to_end(&mut contents).unwrap();\n    assert_eq!(contents, [1, 3, 3, 7, 8]);\n}\n\n#[test]\nfn get_contents_channel_no_protocol() {\n    let server = TestServer::new();\n\n    let state = State {\n        seats: HashMap::from([(\n            \"seat0\".into(),\n            SeatInfo {\n                ..Default::default()\n            },\n        )]),\n        ..Default::default()\n    };\n    state.create_seats(&server);\n\n    let socket_name = server.socket_name().to_owned();\n    server.run(state);\n\n    let result = get_contents_channel_internal(\n        Seat::Unspecified,\n        MimeType::Specific(\"wrong\"),\n        Some(socket_name),\n    );\n    assert!(matches!(\n        result,\n        Err(Error::MissingProtocol {\n            name: \"ext-data-control, or wlr-data-control\",\n            version: 1\n        })\n    ));\n}\n\n#[test]\nfn get_contents_channel_wrong_mime_type() {\n    let server = TestServer::new();\n    server\n        .display\n        .handle()\n        .create_global::<State, ZwlrDataControlManagerV1, ()>(2, ());\n\n    let state = State {\n        seats: HashMap::from([(\n            \"seat0\".into(),\n            SeatInfo {\n                ..Default::default()\n            },\n        )]),\n        ..Default::default()\n    };\n    state.create_seats(&server);\n\n    let socket_name = server.socket_name().to_owned();\n    server.run(state);\n\n    let sources = vec![crate::copy::MimeSource {\n        source: crate::copy::Source::Bytes([1, 3, 3, 7, 8][..].into()),\n        mime_type: crate::copy::MimeType::Specific(\"application/octet-stream\".into()),\n    }];\n    crate::copy::copy_internal(\n        crate::copy::Options::new(),\n        sources,\n        Some(socket_name.clone()),\n    )\n    .expect(\"unable to copy\");\n\n    let tx = get_contents_channel_internal(\n        Seat::Unspecified,\n        MimeType::Specific(\"wrong\"),\n        Some(socket_name),\n    )\n    .expect(\"unable to create channel\");\n    let result = tx.recv().expect(\"failed to receive\");\n    assert!(matches!(result, Err(Error::NoMimeType)));\n}\n\n#[test]\nfn get_contents_channel_test_multiple_mime() {\n    let server = TestServer::new();\n    server\n        .display\n        .handle()\n        .create_global::<State, ZwlrDataControlManagerV1, ()>(2, ());\n\n    let state = State {\n        seats: HashMap::from([(\n            \"seat0\".into(),\n            SeatInfo {\n                ..Default::default()\n            },\n        )]),\n        ..Default::default()\n    };\n    state.create_seats(&server);\n\n    let socket_name = server.socket_name().to_owned();\n    server.run(state);\n\n    let sources = vec![\n        crate::copy::MimeSource {\n            source: crate::copy::Source::Bytes([1, 3, 3, 7, 8][..].into()),\n            mime_type: crate::copy::MimeType::Specific(\"application/octet-stream\".into()),\n        },\n        crate::copy::MimeSource {\n            source: crate::copy::Source::Bytes([1, 3, 3, 7, 9][..].into()),\n            mime_type: crate::copy::MimeType::Specific(\"STRING\".into()),\n        },\n    ];\n    crate::copy::copy_internal(\n        crate::copy::Options::new(),\n        sources,\n        Some(socket_name.clone()),\n    )\n    .expect(\"unable to copy\");\n\n    let tx =\n        get_contents_channel_internal(Seat::Unspecified, MimeType::Text, Some(socket_name.clone()))\n            .expect(\"unable to create channel\");\n\n    let mut result = tx.recv().expect(\"failed to receive\").expect(\"no data\");\n    assert_eq!(result.1, \"text/plain;charset=utf-8\");\n\n    let mut contents = vec![];\n    result.0.read_to_end(&mut contents).unwrap();\n    assert_eq!(contents, [1, 3, 3, 7, 9]);\n}\n\nproptest! {\n    #[test]\n    fn get_mime_types_randomized(\n        mut state: State,\n        clipboard_type: ClipboardType,\n        seat_index: prop::sample::Index,\n    ) {\n        let server = TestServer::new();\n        let socket_name = server.socket_name().to_owned();\n        server\n            .display\n            .handle()\n            .create_global::<State, ZwlrDataControlManagerV1, ()>(2, ());\n\n        state.create_seats(&server);\n\n        if state.seats.is_empty() {\n            server.run(state);\n\n            let result = get_mime_types_internal(clipboard_type, Seat::Unspecified, Some(socket_name));\n            prop_assert!(matches!(result, Err(Error::NoSeats)));\n        } else {\n            let seat_index = seat_index.index(state.seats.len());\n            let (seat_name, seat_info) = state.seats.iter().nth(seat_index).unwrap();\n            let seat_name = seat_name.to_owned();\n            let seat_info = (*seat_info).clone();\n\n            server.run(state);\n\n            let result = get_mime_types_internal(\n                clipboard_type,\n                Seat::Specific(&seat_name),\n                Some(socket_name),\n            );\n\n            let expected_offer = match clipboard_type {\n                ClipboardType::Regular => &seat_info.offer,\n                ClipboardType::Primary => &seat_info.primary_offer,\n            };\n            match expected_offer {\n                None => prop_assert!(matches!(result, Err(Error::ClipboardEmpty))),\n                Some(offer) => prop_assert_eq!(result.unwrap(), offer.data().keys().cloned().collect()),\n            }\n        }\n    }\n\n    #[test]\n    fn get_contents_randomized(\n        mut state: State,\n        clipboard_type: ClipboardType,\n        seat_index: prop::sample::Index,\n        mime_index: prop::sample::Index,\n    ) {\n        let server = TestServer::new();\n        let socket_name = server.socket_name().to_owned();\n        server\n            .display\n            .handle()\n            .create_global::<State, ZwlrDataControlManagerV1, ()>(2, ());\n\n        state.create_seats(&server);\n\n        if state.seats.is_empty() {\n            server.run(state);\n\n            let result = get_mime_types_internal(clipboard_type, Seat::Unspecified, Some(socket_name));\n            prop_assert!(matches!(result, Err(Error::NoSeats)));\n        } else {\n            let seat_index = seat_index.index(state.seats.len());\n            let (seat_name, seat_info) = state.seats.iter().nth(seat_index).unwrap();\n            let seat_name = seat_name.to_owned();\n            let seat_info = (*seat_info).clone();\n\n            let expected_offer = match clipboard_type {\n                ClipboardType::Regular => &seat_info.offer,\n                ClipboardType::Primary => &seat_info.primary_offer,\n            };\n\n            let mime_type = match expected_offer {\n                Some(offer) if !offer.data().is_empty() => {\n                    let mime_index = mime_index.index(offer.data().len());\n                    Some(offer.data().keys().nth(mime_index).unwrap())\n                }\n                _ => None,\n            };\n\n            server.run(state);\n\n            let result = get_contents_internal(\n                clipboard_type,\n                Seat::Specific(&seat_name),\n                mime_type.map_or(MimeType::Any, |name| MimeType::Specific(name)),\n                Some(socket_name),\n            );\n\n            match expected_offer {\n                None => prop_assert!(matches!(result, Err(Error::ClipboardEmpty))),\n                Some(offer) => {\n                    if offer.data().is_empty() {\n                        prop_assert!(matches!(result, Err(Error::NoMimeType)));\n                    } else {\n                        let mime_type = mime_type.unwrap();\n\n                        let (mut read, recv_mime_type) = result.unwrap();\n                        prop_assert_eq!(&recv_mime_type, mime_type);\n\n                        let mut contents = vec![];\n                        read.read_to_end(&mut contents).unwrap();\n                        prop_assert_eq!(&contents, &offer.data()[mime_type]);\n                    }\n                },\n            }\n\n        }\n    }\n}\n"
  },
  {
    "path": "dep-crates/wl-clipboard-rs/src/tests/state.rs",
    "content": "//! Test compositor implementation.\n//!\n//! This module contains the test compositor ([`State`]), which boils down to a minimal wlr-data-control protocol\n//! implementation. The compositor can be initialized with an arbitrary set of seats, each offering arbitrary clipboard\n//! contents in their regular and primary selections. Then the compositor handles all wlr-data-control interactions, such\n//! as copying and pasting.\n\nuse std::collections::HashMap;\nuse std::io::Write;\nuse std::os::fd::AsFd;\nuse std::sync::atomic::AtomicU8;\nuse std::sync::atomic::Ordering::SeqCst;\nuse std::sync::mpsc::Sender;\n\nuse os_pipe::PipeWriter;\nuse proptest::prelude::*;\nuse proptest_derive::Arbitrary;\nuse rustix::fs::{fcntl_setfl, OFlags};\nuse wayland_protocols_wlr::data_control::v1::server::zwlr_data_control_device_v1::{\n    self, ZwlrDataControlDeviceV1,\n};\nuse wayland_protocols_wlr::data_control::v1::server::zwlr_data_control_manager_v1::{\n    self, ZwlrDataControlManagerV1,\n};\nuse wayland_protocols_wlr::data_control::v1::server::zwlr_data_control_offer_v1::{\n    self, ZwlrDataControlOfferV1,\n};\nuse wayland_protocols_wlr::data_control::v1::server::zwlr_data_control_source_v1::{\n    self, ZwlrDataControlSourceV1,\n};\nuse wayland_server::protocol::wl_seat::WlSeat;\nuse wayland_server::{Dispatch, GlobalDispatch, Resource};\n\nuse super::TestServer;\nuse crate::server_ignore_global_impl;\n\n#[derive(Debug, Clone, Arbitrary)]\npub enum OfferInfo {\n    Buffered {\n        #[proptest(\n            strategy = \"prop::collection::hash_map(any::<String>(), prop::collection::vec(any::<u8>(), 0..5), 0..5)\"\n        )]\n        data: HashMap<String, Vec<u8>>,\n    },\n    #[proptest(skip)]\n    Runtime { source: ZwlrDataControlSourceV1 },\n}\n\nimpl Default for OfferInfo {\n    fn default() -> Self {\n        Self::Buffered {\n            data: HashMap::new(),\n        }\n    }\n}\n\nimpl OfferInfo {\n    fn mime_types(&self, state: &State) -> Vec<String> {\n        match self {\n            OfferInfo::Buffered { data } => data.keys().cloned().collect(),\n            OfferInfo::Runtime { source } => state.sources[source].clone(),\n        }\n    }\n\n    pub fn data(&self) -> &HashMap<String, Vec<u8>> {\n        match self {\n            OfferInfo::Buffered { data } => data,\n            OfferInfo::Runtime { .. } => panic!(),\n        }\n    }\n}\n\n#[derive(Debug, Clone, Default, Arbitrary)]\npub struct SeatInfo {\n    pub offer: Option<OfferInfo>,\n    pub primary_offer: Option<OfferInfo>,\n}\n\n#[derive(Debug, Clone, Default, Arbitrary)]\npub struct State {\n    #[proptest(strategy = \"prop::collection::hash_map(any::<String>(), any::<SeatInfo>(), 0..5)\")]\n    pub seats: HashMap<String, SeatInfo>,\n    #[proptest(value = \"HashMap::new()\")]\n    pub sources: HashMap<ZwlrDataControlSourceV1, Vec<String>>,\n    #[proptest(value = \"None\")]\n    pub selection_updated_sender: Option<Sender<Option<Vec<String>>>>,\n    pub set_nonblock_on_write_fd: bool,\n}\n\nserver_ignore_global_impl!(State => [ZwlrDataControlManagerV1]);\n\nimpl State {\n    pub fn create_seats(&self, server: &TestServer<Self>) {\n        for name in self.seats.keys() {\n            server\n                .display\n                .handle()\n                .create_global::<Self, WlSeat, _>(6, name.clone());\n        }\n    }\n}\n\nimpl GlobalDispatch<WlSeat, String> for State {\n    fn bind(\n        _state: &mut Self,\n        _handle: &wayland_server::DisplayHandle,\n        _client: &wayland_server::Client,\n        resource: wayland_server::New<WlSeat>,\n        name: &String,\n        data_init: &mut wayland_server::DataInit<'_, Self>,\n    ) {\n        let seat = data_init.init(resource, name.clone());\n        seat.name((*name).to_owned());\n    }\n}\n\nimpl Dispatch<WlSeat, String> for State {\n    fn request(\n        _state: &mut Self,\n        _client: &wayland_server::Client,\n        _seat: &WlSeat,\n        _request: <WlSeat as wayland_server::Resource>::Request,\n        _name: &String,\n        _dhandle: &wayland_server::DisplayHandle,\n        _data_init: &mut wayland_server::DataInit<'_, Self>,\n    ) {\n    }\n}\n\nimpl Dispatch<ZwlrDataControlManagerV1, ()> for State {\n    fn request(\n        state: &mut Self,\n        client: &wayland_server::Client,\n        manager: &ZwlrDataControlManagerV1,\n        request: <ZwlrDataControlManagerV1 as wayland_server::Resource>::Request,\n        _data: &(),\n        dhandle: &wayland_server::DisplayHandle,\n        data_init: &mut wayland_server::DataInit<'_, Self>,\n    ) {\n        match request {\n            zwlr_data_control_manager_v1::Request::GetDataDevice { id, seat } => {\n                let name: &String = seat.data().unwrap();\n                let info = &state.seats[name];\n\n                let data_device = data_init.init(id, (*name).clone());\n\n                let create_offer = |offer_info: &OfferInfo, is_primary: bool| {\n                    let offer = client\n                        .create_resource::<_, _, Self>(\n                            dhandle,\n                            manager.version(),\n                            (name.clone(), is_primary),\n                        )\n                        .unwrap();\n                    data_device.data_offer(&offer);\n\n                    for mime_type in offer_info.mime_types(state) {\n                        offer.offer(mime_type);\n                    }\n\n                    offer\n                };\n\n                let selection = info\n                    .offer\n                    .as_ref()\n                    .map(|offer_info| create_offer(offer_info, false));\n                data_device.selection(selection.as_ref());\n\n                let primary_selection = info\n                    .primary_offer\n                    .as_ref()\n                    .map(|offer_info| create_offer(offer_info, true));\n                data_device.primary_selection(primary_selection.as_ref());\n            }\n            zwlr_data_control_manager_v1::Request::CreateDataSource { id } => {\n                let source = data_init.init(id, AtomicU8::new(0));\n                state.sources.insert(source, vec![]);\n            }\n            _ => (),\n        }\n    }\n}\n\nimpl Dispatch<ZwlrDataControlDeviceV1, String> for State {\n    fn request(\n        state: &mut Self,\n        _client: &wayland_server::Client,\n        _resource: &ZwlrDataControlDeviceV1,\n        request: <ZwlrDataControlDeviceV1 as Resource>::Request,\n        name: &String,\n        _dhandle: &wayland_server::DisplayHandle,\n        _data_init: &mut wayland_server::DataInit<'_, Self>,\n    ) {\n        match request {\n            zwlr_data_control_device_v1::Request::SetSelection { source } => {\n                let mime_types = source.as_ref().map(|source| state.sources[source].clone());\n\n                let info = state.seats.get_mut(name).unwrap();\n\n                if let Some(source) = &source {\n                    source.data::<AtomicU8>().unwrap().fetch_add(1, SeqCst);\n                }\n                if let Some(OfferInfo::Runtime { source }) = &info.offer {\n                    if source.data::<AtomicU8>().unwrap().fetch_sub(1, SeqCst) == 1 {\n                        source.cancelled();\n                    }\n                }\n                info.offer = source.map(|source| OfferInfo::Runtime { source });\n\n                if let Some(sender) = &state.selection_updated_sender {\n                    let _ = sender.send(mime_types);\n                }\n            }\n            zwlr_data_control_device_v1::Request::SetPrimarySelection { source } => {\n                let mime_types = source.as_ref().map(|source| state.sources[source].clone());\n\n                let info = state.seats.get_mut(name).unwrap();\n\n                if let Some(source) = &source {\n                    source.data::<AtomicU8>().unwrap().fetch_add(1, SeqCst);\n                }\n                if let Some(OfferInfo::Runtime { source }) = &info.primary_offer {\n                    if source.data::<AtomicU8>().unwrap().fetch_sub(1, SeqCst) == 1 {\n                        source.cancelled();\n                    }\n                }\n                info.primary_offer = source.map(|source| OfferInfo::Runtime { source });\n\n                if let Some(sender) = &state.selection_updated_sender {\n                    let _ = sender.send(mime_types);\n                }\n            }\n            _ => (),\n        }\n    }\n}\n\nimpl Dispatch<ZwlrDataControlOfferV1, (String, bool)> for State {\n    fn request(\n        state: &mut Self,\n        _client: &wayland_server::Client,\n        _resource: &ZwlrDataControlOfferV1,\n        request: <ZwlrDataControlOfferV1 as Resource>::Request,\n        (name, is_primary): &(String, bool),\n        _dhandle: &wayland_server::DisplayHandle,\n        _data_init: &mut wayland_server::DataInit<'_, Self>,\n    ) {\n        if let zwlr_data_control_offer_v1::Request::Receive { mime_type, fd } = request {\n            let info = &state.seats[name];\n            let offer_info = if *is_primary {\n                info.primary_offer.as_ref().unwrap()\n            } else {\n                info.offer.as_ref().unwrap()\n            };\n\n            match offer_info {\n                OfferInfo::Buffered { data } => {\n                    let mut write = PipeWriter::from(fd);\n                    let _ = write.write_all(&data[mime_type.as_str()]);\n                }\n                OfferInfo::Runtime { source } => {\n                    if state.set_nonblock_on_write_fd {\n                        fcntl_setfl(&fd, OFlags::NONBLOCK).unwrap();\n                    }\n\n                    source.send(mime_type, fd.as_fd())\n                }\n            }\n        }\n    }\n}\n\nimpl Dispatch<ZwlrDataControlSourceV1, AtomicU8> for State {\n    fn request(\n        state: &mut Self,\n        _client: &wayland_server::Client,\n        source: &ZwlrDataControlSourceV1,\n        request: <ZwlrDataControlSourceV1 as Resource>::Request,\n        _data: &AtomicU8,\n        _dhandle: &wayland_server::DisplayHandle,\n        _data_init: &mut wayland_server::DataInit<'_, Self>,\n    ) {\n        if let zwlr_data_control_source_v1::Request::Offer { mime_type } = request {\n            state.sources.get_mut(source).unwrap().push(mime_type);\n        }\n    }\n}\n"
  },
  {
    "path": "dep-crates/wl-clipboard-rs/src/tests/utils.rs",
    "content": "use wayland_protocols::ext::data_control::v1::server::ext_data_control_device_v1::ExtDataControlDeviceV1;\nuse wayland_protocols::ext::data_control::v1::server::ext_data_control_manager_v1::{\n    self, ExtDataControlManagerV1,\n};\nuse wayland_protocols_wlr::data_control::v1::server::zwlr_data_control_device_v1::ZwlrDataControlDeviceV1;\nuse wayland_protocols_wlr::data_control::v1::server::zwlr_data_control_manager_v1::{\n    self, ZwlrDataControlManagerV1,\n};\nuse wayland_server::protocol::wl_seat::WlSeat;\nuse wayland_server::Dispatch;\n\nuse crate::tests::TestServer;\nuse crate::utils::*;\nuse crate::{server_ignore_global_impl, server_ignore_impl};\n\nstruct State {\n    advertise_primary_selection: bool,\n}\n\nserver_ignore_global_impl!(State => [WlSeat, ZwlrDataControlManagerV1, ExtDataControlManagerV1]);\nserver_ignore_impl!(State => [WlSeat, ZwlrDataControlDeviceV1, ExtDataControlDeviceV1]);\n\nimpl Dispatch<ZwlrDataControlManagerV1, ()> for State {\n    fn request(\n        state: &mut Self,\n        _client: &wayland_server::Client,\n        _resource: &ZwlrDataControlManagerV1,\n        request: <ZwlrDataControlManagerV1 as wayland_server::Resource>::Request,\n        _data: &(),\n        _dhandle: &wayland_server::DisplayHandle,\n        data_init: &mut wayland_server::DataInit<'_, Self>,\n    ) {\n        if let zwlr_data_control_manager_v1::Request::GetDataDevice { id, .. } = request {\n            let data_device = data_init.init(id, ());\n\n            if state.advertise_primary_selection {\n                data_device.primary_selection(None);\n            }\n        }\n    }\n}\n\nimpl Dispatch<ExtDataControlManagerV1, ()> for State {\n    fn request(\n        state: &mut Self,\n        _client: &wayland_server::Client,\n        _resource: &ExtDataControlManagerV1,\n        request: <ExtDataControlManagerV1 as wayland_server::Resource>::Request,\n        _data: &(),\n        _dhandle: &wayland_server::DisplayHandle,\n        data_init: &mut wayland_server::DataInit<'_, Self>,\n    ) {\n        if let ext_data_control_manager_v1::Request::GetDataDevice { id, .. } = request {\n            let data_device = data_init.init(id, ());\n\n            if state.advertise_primary_selection {\n                data_device.primary_selection(None);\n            }\n        }\n    }\n}\n\n#[test]\nfn is_primary_selection_supported_test() {\n    let server = TestServer::new();\n    server\n        .display\n        .handle()\n        .create_global::<State, WlSeat, ()>(6, ());\n    server\n        .display\n        .handle()\n        .create_global::<State, ZwlrDataControlManagerV1, ()>(2, ());\n\n    let state = State {\n        advertise_primary_selection: true,\n    };\n\n    let socket_name = server.socket_name().to_owned();\n    server.run(state);\n\n    let result = is_primary_selection_supported_internal(Some(socket_name)).unwrap();\n    assert!(result);\n}\n\n#[test]\nfn is_primary_selection_supported_primary_selection_unsupported() {\n    let server = TestServer::new();\n    server\n        .display\n        .handle()\n        .create_global::<State, WlSeat, ()>(6, ());\n    server\n        .display\n        .handle()\n        .create_global::<State, ZwlrDataControlManagerV1, ()>(2, ());\n\n    let state = State {\n        advertise_primary_selection: false,\n    };\n\n    let socket_name = server.socket_name().to_owned();\n    server.run(state);\n\n    let result = is_primary_selection_supported_internal(Some(socket_name)).unwrap();\n    assert!(!result);\n}\n\n#[test]\nfn is_primary_selection_supported_data_control_v1() {\n    let server = TestServer::new();\n    server\n        .display\n        .handle()\n        .create_global::<State, WlSeat, ()>(6, ());\n    server\n        .display\n        .handle()\n        .create_global::<State, ZwlrDataControlManagerV1, ()>(1, ());\n\n    let state = State {\n        advertise_primary_selection: false,\n    };\n\n    let socket_name = server.socket_name().to_owned();\n    server.run(state);\n\n    let result = is_primary_selection_supported_internal(Some(socket_name)).unwrap();\n    assert!(!result);\n}\n\n#[test]\nfn is_primary_selection_supported_no_seats() {\n    let server = TestServer::new();\n    server\n        .display\n        .handle()\n        .create_global::<State, ZwlrDataControlManagerV1, ()>(2, ());\n\n    let state = State {\n        advertise_primary_selection: true,\n    };\n\n    let socket_name = server.socket_name().to_owned();\n    server.run(state);\n\n    let result = is_primary_selection_supported_internal(Some(socket_name));\n    assert!(matches!(result, Err(PrimarySelectionCheckError::NoSeats)));\n}\n\n#[test]\nfn supports_v2_seats() {\n    let server = TestServer::new();\n    server\n        .display\n        .handle()\n        .create_global::<State, WlSeat, ()>(2, ());\n    server\n        .display\n        .handle()\n        .create_global::<State, ZwlrDataControlManagerV1, ()>(2, ());\n\n    let state = State {\n        advertise_primary_selection: true,\n    };\n\n    let socket_name = server.socket_name().to_owned();\n    server.run(state);\n\n    let result = is_primary_selection_supported_internal(Some(socket_name)).unwrap();\n    assert!(result);\n}\n\n#[test]\nfn is_primary_selection_supported_no_data_control() {\n    let server = TestServer::new();\n    server\n        .display\n        .handle()\n        .create_global::<State, WlSeat, ()>(6, ());\n\n    let state = State {\n        advertise_primary_selection: false,\n    };\n\n    let socket_name = server.socket_name().to_owned();\n    server.run(state);\n\n    let result = is_primary_selection_supported_internal(Some(socket_name));\n    assert!(matches!(\n        result,\n        Err(PrimarySelectionCheckError::MissingProtocol)\n    ));\n}\n\n#[test]\nfn is_primary_selection_supported_ext_data_control() {\n    let server = TestServer::new();\n    server\n        .display\n        .handle()\n        .create_global::<State, WlSeat, ()>(6, ());\n    server\n        .display\n        .handle()\n        .create_global::<State, ExtDataControlManagerV1, ()>(1, ());\n\n    let state = State {\n        advertise_primary_selection: true,\n    };\n\n    let socket_name = server.socket_name().to_owned();\n    server.run(state);\n\n    let result = is_primary_selection_supported_internal(Some(socket_name)).unwrap();\n    assert!(result);\n}\n\n#[test]\nfn is_primary_selection_supported_primary_selection_unsupported_ext_data_control() {\n    let server = TestServer::new();\n    server\n        .display\n        .handle()\n        .create_global::<State, WlSeat, ()>(6, ());\n    server\n        .display\n        .handle()\n        .create_global::<State, ExtDataControlManagerV1, ()>(1, ());\n\n    let state = State {\n        advertise_primary_selection: false,\n    };\n\n    let socket_name = server.socket_name().to_owned();\n    server.run(state);\n\n    let result = is_primary_selection_supported_internal(Some(socket_name)).unwrap();\n    assert!(!result);\n}\n\n#[test]\nfn is_primary_selection_supported_data_control_v1_and_ext_data_control() {\n    let server = TestServer::new();\n    server\n        .display\n        .handle()\n        .create_global::<State, WlSeat, ()>(6, ());\n    server\n        .display\n        .handle()\n        .create_global::<State, ZwlrDataControlManagerV1, ()>(1, ());\n    server\n        .display\n        .handle()\n        .create_global::<State, ExtDataControlManagerV1, ()>(1, ());\n\n    let state = State {\n        advertise_primary_selection: true,\n    };\n\n    let socket_name = server.socket_name().to_owned();\n    server.run(state);\n\n    let result = is_primary_selection_supported_internal(Some(socket_name)).unwrap();\n    assert!(result);\n}\n"
  },
  {
    "path": "dep-crates/wl-clipboard-rs/src/utils.rs",
    "content": "//! Helper functions.\n\nuse std::ffi::OsString;\nuse std::os::unix::net::UnixStream;\nuse std::path::PathBuf;\nuse std::{env, io};\n\nuse wayland_client::protocol::wl_registry::{self, WlRegistry};\nuse wayland_client::protocol::wl_seat::WlSeat;\nuse wayland_client::{\n    event_created_child, ConnectError, Connection, Dispatch, DispatchError, Proxy,\n};\nuse wayland_protocols::ext::data_control::v1::client::ext_data_control_manager_v1::ExtDataControlManagerV1;\nuse wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_manager_v1::ZwlrDataControlManagerV1;\n\nuse crate::data_control::{\n    impl_dispatch_device, impl_dispatch_manager, impl_dispatch_offer, Manager,\n};\n\n/// Checks if the given MIME type represents plain text.\n///\n/// # Examples\n///\n/// ```\n/// use wl_clipboard_rs::utils::is_text;\n///\n/// assert!(is_text(\"text/plain\"));\n/// assert!(!is_text(\"application/octet-stream\"));\n/// ```\npub fn is_text(mime_type: &str) -> bool {\n    match mime_type {\n        \"TEXT\" | \"STRING\" | \"UTF8_STRING\" => true,\n        x if x.starts_with(\"text/\") => true,\n        _ => false,\n    }\n}\n\nstruct PrimarySelectionState {\n    // Any seat that we get from the compositor.\n    seat: Option<WlSeat>,\n    clipboard_manager: Option<Manager>,\n    saw_zwlr_v1: bool,\n    got_primary_selection: bool,\n}\n\nimpl Dispatch<WlRegistry, ()> for PrimarySelectionState {\n    fn event(\n        state: &mut Self,\n        registry: &WlRegistry,\n        event: <WlRegistry as wayland_client::Proxy>::Event,\n        _data: &(),\n        _conn: &Connection,\n        qh: &wayland_client::QueueHandle<Self>,\n    ) {\n        if let wl_registry::Event::Global {\n            name,\n            interface,\n            version,\n        } = event\n        {\n            if interface == WlSeat::interface().name && version >= 2 && state.seat.is_none() {\n                let seat = registry.bind(name, 2, qh, ());\n                state.seat = Some(seat);\n            }\n\n            if state.clipboard_manager.is_none() {\n                if interface == ZwlrDataControlManagerV1::interface().name {\n                    if version == 1 {\n                        state.saw_zwlr_v1 = true;\n                    } else {\n                        let manager = registry.bind(name, 2, qh, ());\n                        state.clipboard_manager = Some(Manager::Zwlr(manager));\n                    }\n                }\n\n                if interface == ExtDataControlManagerV1::interface().name {\n                    let manager = registry.bind(name, 1, qh, ());\n                    state.clipboard_manager = Some(Manager::Ext(manager));\n                }\n            }\n        }\n    }\n}\n\nimpl Dispatch<WlSeat, ()> for PrimarySelectionState {\n    fn event(\n        _state: &mut Self,\n        _proxy: &WlSeat,\n        _event: <WlSeat as Proxy>::Event,\n        _data: &(),\n        _conn: &Connection,\n        _qhandle: &wayland_client::QueueHandle<Self>,\n    ) {\n    }\n}\n\nimpl_dispatch_manager!(PrimarySelectionState);\n\nimpl_dispatch_device!(PrimarySelectionState, (), |state: &mut Self, event, _| {\n    if let Event::PrimarySelection { id: _ } = event {\n        state.got_primary_selection = true;\n    }\n});\n\nimpl_dispatch_offer!(PrimarySelectionState);\n\n/// Errors that can occur when checking whether the primary selection is supported.\n#[derive(thiserror::Error, Debug)]\npub enum PrimarySelectionCheckError {\n    #[error(\"There are no seats\")]\n    NoSeats,\n\n    #[error(\"Couldn't open the provided Wayland socket\")]\n    SocketOpenError(#[source] io::Error),\n\n    #[error(\"Couldn't connect to the Wayland compositor\")]\n    WaylandConnection(#[source] ConnectError),\n\n    #[error(\"Wayland compositor communication error\")]\n    WaylandCommunication(#[source] DispatchError),\n\n    #[error(\n        \"A required Wayland protocol (ext-data-control, or wlr-data-control version 1) \\\n         is not supported by the compositor\"\n    )]\n    MissingProtocol,\n}\n\n/// Checks if the compositor supports the primary selection.\n///\n/// # Examples\n///\n/// ```no_run\n/// # extern crate wl_clipboard_rs;\n/// # fn foo() -> Result<(), Box<dyn std::error::Error>> {\n/// use wl_clipboard_rs::utils::{is_primary_selection_supported, PrimarySelectionCheckError};\n///\n/// match is_primary_selection_supported() {\n///     Ok(supported) => {\n///         // We have our definitive result. False means that ext/wlr-data-control is present\n///         // and did not signal the primary selection support, or that only wlr-data-control\n///         // version 1 is present (which does not support primary selection).\n///     },\n///     Err(PrimarySelectionCheckError::NoSeats) => {\n///         // Impossible to give a definitive result. Primary selection may or may not be\n///         // supported.\n///\n///         // The required protocol (ext-data-control, or wlr-data-control version 2) is there,\n///         // but there are no seats. Unfortunately, at least one seat is needed to check for the\n///         // primary clipboard support.\n///     },\n///     Err(PrimarySelectionCheckError::MissingProtocol) => {\n///         // The data-control protocol (required for wl-clipboard-rs operation) is not\n///         // supported by the compositor.\n///     },\n///     Err(_) => {\n///         // Some communication error occurred.\n///     }\n/// }\n/// # Ok(())\n/// # }\n/// ```\n#[inline]\npub fn is_primary_selection_supported() -> Result<bool, PrimarySelectionCheckError> {\n    is_primary_selection_supported_internal(None)\n}\n\npub(crate) fn is_primary_selection_supported_internal(\n    socket_name: Option<OsString>,\n) -> Result<bool, PrimarySelectionCheckError> {\n    // Connect to the Wayland compositor.\n    let conn = match socket_name {\n        Some(name) => {\n            let mut socket_path = env::var_os(\"XDG_RUNTIME_DIR\")\n                .map(Into::<PathBuf>::into)\n                .ok_or(ConnectError::NoCompositor)\n                .map_err(PrimarySelectionCheckError::WaylandConnection)?;\n            if !socket_path.is_absolute() {\n                return Err(PrimarySelectionCheckError::WaylandConnection(\n                    ConnectError::NoCompositor,\n                ));\n            }\n            socket_path.push(name);\n\n            let stream = UnixStream::connect(socket_path)\n                .map_err(PrimarySelectionCheckError::SocketOpenError)?;\n            Connection::from_socket(stream)\n        }\n        None => Connection::connect_to_env(),\n    }\n    .map_err(PrimarySelectionCheckError::WaylandConnection)?;\n    let display = conn.display();\n\n    let mut queue = conn.new_event_queue();\n    let qh = queue.handle();\n\n    let mut state = PrimarySelectionState {\n        seat: None,\n        clipboard_manager: None,\n        saw_zwlr_v1: false,\n        got_primary_selection: false,\n    };\n\n    // Retrieve the global interfaces.\n    let _registry = display.get_registry(&qh, ());\n    queue\n        .roundtrip(&mut state)\n        .map_err(PrimarySelectionCheckError::WaylandCommunication)?;\n\n    // If data control is present but is version 1, then return false as version 1 does not support\n    // primary clipboard.\n    if state.clipboard_manager.is_none() && state.saw_zwlr_v1 {\n        return Ok(false);\n    }\n\n    // Verify that we got the clipboard manager.\n    let Some(ref clipboard_manager) = state.clipboard_manager else {\n        return Err(PrimarySelectionCheckError::MissingProtocol);\n    };\n\n    // Check if there are no seats.\n    let Some(ref seat) = state.seat else {\n        return Err(PrimarySelectionCheckError::NoSeats);\n    };\n\n    clipboard_manager.get_data_device(seat, &qh, ());\n\n    queue\n        .roundtrip(&mut state)\n        .map_err(PrimarySelectionCheckError::WaylandCommunication)?;\n\n    Ok(state.got_primary_selection)\n}\n"
  },
  {
    "path": "docs/CONFIGURE.md",
    "content": "# Config\n\n**Use `hyprshell config edit` to open the hyprshell config editor. It also contains documentation of all the options.**\n\n---\n\nThe main config file is located at `~/.config/hyprshell/config.ron` but can be configured using the `-c` argument. You can also use `.toml` and `.json(5)` as config file formats.\nThe config is loaded at startup and is reloaded when the file changes.\n\nTo interactively generate a default config file with all possible options set, run the following command:\n\n```bash\nhyprshell config generate\n```\n\nIn case this documentation is outdated, or you understand rust, look at the [struct definition](../crates/config-lib/src/structs.rs) for the most up-to-date information.\n\nThe default values for these configs, which are also the values that get used when generating the config, are located in the code directly above the value definition (`#[default ... ]`).\n\n## Config Options\n\n- **version:**_[number]_ The version of the config file. When loading the config this is value checked to migrate the file if needed.\n- **windows:**_[Windows?]_ Configuration for the different windows like overview, switch and launcher (optional).\n\n## Windows\n\n- **scale:**_[number]_ The scale used to scale down the real dimension the windows displayed in the overview. Can be set from `0.0 < X > to 15.0`\n- **items_per_row:**_[number]_ The number of workspaces or windows to show. If you have 6 workspaces open and set this to 3, you will see 2 rows of 3 workspaces.\n  Pressing arrow up or down switches between the rows.\n- **overview:**_[Overview?]_ Configuration for the overview mode (optional).\n- **switch:**_[Switch?]_ Configuration for the switch mode (optional).\n\n### Overview\n\nThis mode displays the windows in a downscaled view of the screen. It also shows the launcher. This option itself is optional, if not set, this mode is disabled.\n\n- **launcher:**_[Launcher]_ Configuration for the launcher.\n- **key:**_[string]_ The key to use to open the Overview mode (like \"tab\" or \"alt_r\"). This is used to register the keybinding to open the Overview mode. If you want to only open using a modifier, set this to the modifier name like `super_l`.\n- **modifier:**_[string]_ The modifier that must be pressed together with the key to open the Overview mode (like ctrl). This MUST be one of these modifiers: `alt, ctrl, super`.\n- **filter_by:**_[List<FilterBy>]_ Filter the windows by the provided filters. This is a list of the following objects. (example: `filter_by: [current_workspace]`)\n    - **same_class**: Only includes windows of the same class / type. If you currently have alacritty open, only alacritty windows will be shown.\n    - **current_workspace**: Only includes windows of the current workspace.\n    - **current_monitor**: Only includes windows of the current monitor.\n- **hide_filtered:**_[boolean]_ !deprecated! whether to hide the filtered windows or not. This is used to show the windows that are filtered out by the `filter_by` option. If this is set to false, the filtered windows are shown with a grayscale effect.\n\n## Launcher\n\n- **default_terminal:**_[string]_ Defined the name of the default terminal to use. This value is optional, if unset a list of [default terminals](../crates/core-lib/src/const.rs) is used to find a default terminal.\n  This is used to launch programs like micro from the launcher that need to be run in a terminal.\n  This terminal is also used by the `terminal` plugin to run the typed command in a terminal.\n- **launch_modifier:**_[string]_ Sets the modifier used to launch apps in the launcher by pressing `<Mod> + 1` to open second, `<Mod> + t` to run in terminal, etc. This MUST be one of these modifiers: `alt, ctrl, super`.\n- **width:**_[number]_ The width of the launcher in pixels.\n- **max_items:**_[number]_ Sets the maximum number of items to show in the launcher.\n  This does not include the plugin row and only limits the number of items retuned by for examples the application search.\n  This value will get reduced to 10 if it is set to a value higher than 10.\n- **show_when_empty:**_[boolean]_ Show entries in the launcher when no text is entered.\n- **plugins:**_[Plugins]_ Configuration for each Plugin. Ignore the individual plugins to disable them.\n\n### Plugins\n\n- **applications:** Show installed applications in the launcher, filed by the input, sorted by how often they are used. The following options can be provided:\n    - **run_cache_weeks:**_[u8]_ Number of weeks to retain run history; used to rank applications by usage.\n    - **show_execs:**_[boolean]_ Show the exec line from the Desktop file. In the case of Flatpaks and PWAs these get shortened to the name of the app.\n      The full exec can still be seen in the tooltip.\n    - **show_actions_submenu:**_[boolean]_ Show a dropdown menu with all the desktop actions specified in the `.desktop` files of the applications, like `new private window`, etc.\n- **terminal:** Open a terminal and run the typed command in it. The terminal is defined in the `default_terminal` config option. This plugin doesn't accept any options.\n- **shell:** Run the typed command in a shell (in the background). This plugin doesn't accept any options.\n- **webSearch:** Allows searching for the typed query in a web browser.\n    - **engines:**_[List<WebSearchEngine>]_ A list of search engines to use. Each search engine is defined by the following properties.\n        - **url:**_[string]_ URL to open in the browser. This must include a `{}` to replace with the searched text (like `https://www.google.com/search?q={}`).\n        - **name:**_[string]_ Name of the search engine. This is used to show the name in the launcher.\n        - **key:**_[string]_ Key to use to select this search engine. This is used to register the keybinding to select the search engine without clicking on it.\n- **calc:** Calculates any mathematical expression typed into the launcher. This plugin doesn't accept any options.\n- **path:** Opens the typed path in the default file manager (see [Debugging](DEBUG.md) to check default). This plugin doesn't accept any options.\n- **actions:** Runs the specified action like reboot, hibernate, etc. Custom actions can also be specified.\n    - **actions:**_[List<Action>]_ A list of actions to display in the launcher. Actions can be one of the following predefined actions or a custom action.\n        - **lock_screen** Locks the screen.\n        - **hibernate** Hibernates the system (copys the RAM to disk and powers off).\n        - **logout** Logs out the user.\n        - **reboot** Reboots the system.\n        - **shutdown** Shuts down the system.\n        - **suspend** Suspends the system.\n        - **custom** A list of custom actions to run. Each action is defined by the following properties.\n            - **names:**_[List<string>]_ List of names to use for the action, like `[\"poweroff\", \"shutdown\"]`.\n            - **details:**_[string]_ Details about the action. This is used to show the details in the launcher.\n            - **command:**_[string]_ Command to run when the action is selected. (example: `command: \"sudo shutdown -h now\"`).\n              can include `{}` which is replaced with the content of the text in the launcher (without the name of the action).\n              Typing `kill 100` and running the action kill with a name `kill` would replace `{}` with `100`.\n            - **icon:**_[string]_ Icon to show in the launcher. (you can find icons using the `hyprshell debug list-icons` command)\n\n### Switch\n\nThis mode displays the windows sorted by their most recent access. This option itself is optional, if not set, this mode is disabled.\n\n- **modifier:**_[string]_ The modifier that must be helled down together with `tab` key to open the Switch mode (for example `alt`). Letting go of this key will close the Switch mode. This MUST be one of these modifiers: `alt, ctrl, super`.\n- **filter_by**_[List<FilterBy>]_ Filter the windows by the provided filter. This is a list of `FilterBy` objects. (example: `filter_by: [current_workspace]`)\n    - **same_class:** Only includes windows of the same class / type. If you currently have alacritty open, only alacritty windows will be shown.\n    - **current_workspace:** Only includes windows of the current workspace.\n    - **current_monitor:** Only includes windows of the current monitor.\n- **switch_workspaces:**_[boolean]_ Switch between workspaces in the Switch mode instead of windows.\n\n# CSS\n\nThe CSS file is located at `~/.config/hyprshell/styles.css` but can be configured using the `-s` argument.\nThe config is loaded at startup and is reloaded when the file changes. (removing styles will not work, adding or overriding styles works)\n\n**Some examples can be found in the [CSS Examples folder](css-examples).**\n\nTo generate a default file with all possible classes and CSS variables, run the following command:\n\n```bash\nhyprshell config generate\n```\n\nGTK only supports a subset of CSS, so not all CSS properties will work. The supported properties are listed in the [GTK documentation](https://docs.gtk.org/gtk4/css-overview.html).\n\nThe override file contains many empty classes that can be used to configure padding, fonts, etc.\nThese settings will take priority over the default values set by the application itself. The application defaults can be found in the CSS files inside the codebase (for example, [this one](../src/default_styles.css) or [that one](../crates/windows-lib/src/styles.css)).\n\nIf you want to change colors borders, etc. you can edit the CSS variables in the `:root {}` section.\nThese styles are automatically used everywhere in the application, so you don't have to set them for every class.\nThe values in the `:root {}` are set as fallbacks everywhere in the application, so you can just not set the ones you don't want to change.\n\n![image.png](css/swappy-20250510_222852.png)\n![image.png](css/swappy-20250510_224344.png)\n"
  },
  {
    "path": "docs/DEBUG.md",
    "content": "# Debug Commands for Hyprshell\n\nThis document lists all available debug commands in Hyprshell CLI, along with sample usage for each command.\n\n## Debug Commands\n\nDebug commands are available when Hyprshell is built with the `debug_command` feature. These commands help with debugging various aspects of Hyprshell.\n\n### List Icons\n\nLists all icons available in the current icon theme.\n\n```bash\nhyprshell debug list-icons\n```\n\n### List Desktop Files\n\nLists all desktop files found in the system.\n\n```bash\nhyprshell debug list-desktop-files\n```\n\n### Check Class\n\nSearches for an icon associated with a specific window class. If no class is provided, all open windows will be searched.\n\n```bash\n# Check a specific class\nhyprshell debug check-class \"firefox\"\n```\n\n```bash\n# Check all open windows\nhyprshell debug check-class\n```\n\n### Search\n\nSimulates a search in the launcher and displays search insights. This helps debug the search functionality.\n\n```bash\n# Basic search\nhyprshell debug search \"terminal\"\n```\n\n```bash\n# Show all matches (not limited by config)\nhyprshell debug search \"terminal\" --all\n```\n\n### Default Applications\n\nCommands to manage default applications for different mime types.\n\n#### Get Default App\n\nGet the default application for a specific mime type.\n\n```bash\nhyprshell debug default-applications get \"text/plain\"\n```\n\n#### Add Default App\n\nAdd a default application for a specific mime type. If one already exists, the new one is placed before.\n\n```bash\nhyprshell debug default-applications add \"text/plain\" \"org.gnome.gedit.desktop\"\n```\n\n#### List Default Apps\n\nList default applications for all mime types.\n\n```bash\n# List default apps for mime types used by Hyprshell (browser: x-scheme-handler/https, file manager: inode/director)\nhyprshell debug default-applications list\n```\n\n```bash\n# List default apps for all mime types\nhyprshell debug default-applications list --all\n```\n\n#### Check Default Apps\n\nCheck if all entries in all mimetype files point to valid desktop files.\n\n```bash\nhyprshell debug default-applications check\n```\n\n## Data Commands\n\nData commands allow you to view and manage data stored by Hyprshell.\n\n### Launch History\n\nShows the history of launched applications.\n\n```bash\n# Show launch history with default weeks setting from config\nhyprshell data launch-history\n\n# Show launch history for a specific number of weeks\nhyprshell data launch-history 4\n```\n\n## Info Command\n\nShow info about the current Hyprshell installation.\n\n```bash\nhyprshell debug info\n```\n\n## Global Options\n\nThese options can be used with any command:\n\n- `-v, -vv`: Increase verbosity level (-v: debug, -vv: trace)\n- `-q, --quiet`: Turn off all output\n- `-c, --config-file <PATH>`: Specify a custom config file path\n- `-s, --css-file <PATH>`: Specify a custom CSS file path\n- `-d, --data-dir <PATH>`: Specify a custom data directory path\n\nExample with global options:\n\n```bash\nhyprshell -v -c ~/custom-config.ron debug list-icons\n```"
  },
  {
    "path": "docs/NIX.md",
    "content": "# NixOS\n\n## No flakes\n\n### Without Flakes (nixpkgs-unstable)\n\n`configuration.nix`:\n\n```nix\n{pkgs, ...}: {\n  environment.systemPackages = [pkgs.hyprshell];\n}\n```\n\n### Without Flakes with Home-manager\n\n`./user.nix`:\n\nAll the settings are optional and can be found in the [config](CONFIGURE.md)\n\nThis config enables overview and switch, but is not type-save like the flake home-manager config.\n\n```nix\n{ inputs, ... } : {\n  services.hyprshell = {\n    enable = true;\n    settings = {\n      windows = {\n        scale = 8.0;\n        overview = {\n          launcher = {\n            max_items = 6;\n          };\n        };\n        switch = {\n          modifier = \"alt\";\n        };\n      };\n    };\n  };\n}\n```\n\n## Flakes\n\nWarning: hyprshell builds a hyprland plugin at runtime which **requires the C headers** from the running hyprland instance.\n\nThis is trivial for other platforms, but not for NixOS and can cause problems (please report them on github if you encounter any).\nThe default hyprshell program from nixpkgs has access to the hyprland C headers from the latest hyprland flake (updated every ~2 weeks).\nTo synchronize the hyprland version with the hyprshell version, you must override the hyprland input in the flake.\n\n### No Home-manager with default hyprland\n\n**[Cachix Cache](https://app.cachix.org/cache/hyprshell#pull) can be added with `cachix use hyprshell`**\n\n`flake.nix`:\n\n```nix\n{\n  inputs = {\n    nixpkgs.url = \"github:nixos/nixpkgs?ref=nixos-unstable\";\n    hyprshell = {\n      url = \"github:H3rmt/hyprshell?ref=hyprshell-release\";\n    };\n  };\n\n  outputs = { nixpkgs, hyprshell }: {\n    nixosConfigurations.hostname = nixpkgs.lib.nixosSystem {\n      system = \"x86_64-linux\";\n      modules = [{ \n        environment.systemPackages = [ \n            nixpkgs.hyprland\n            hyprshell.packages.${nixpkgs.stdenv.hostPlatform.system}.hyprshell-nixpkgs \n        ]; \n      }];\n    };\n  };\n}\n```\n\n### No Home-manager with custom hyprland\n\n**[Cachix Cache](https://app.cachix.org/cache/hyprshell#pull) can be added with `cachix use hyprshell`, be aware that this will break if you override any inputs**\n\n`flake.nix`:\n\n```nix\n{\n  inputs = {\n    nixpkgs.url = \"github:nixos/nixpkgs?ref=nixos-unstable\";\n    hyprland.url = \"github:hyprwm/Hyprland\";\n    hyprshell = {\n      url = \"github:H3rmt/hyprshell?ref=hyprshell-release\";\n      inputs.hyprland.follows = \"hyprland\";\n    };\n  };\n\n  outputs = { nixpkgs, hyprland, hyprshell }@inputs: {\n    nixosConfigurations.hostname = nixpkgs.lib.nixosSystem {\n      system = \"x86_64-linux\";\n      modules = [{ \n        environment.systemPackages = [ \n          hyprland.packages.${nixpkgs.stdenv.hostPlatform.system}.hyprland\n          hyprshell.packages.${nixpkgs.stdenv.hostPlatform.system}.hyprshell \n          # Use this if you want the more minimal hyprshell (see Readme.md > Features)\n          # hyprshell.packages.${nixpkgs.stdenv.hostPlatform.system}.hyprshell-slim\n        ]; \n      }];\n    };\n  };\n}\n```\n\n### With Home-manager [recommend]\n\n**[Cachix Cache](https://app.cachix.org/cache/hyprshell#pull) can be added with `cachix use hyprshell`**\n\n`flake.nix`:\n\n```nix\n{\n  inputs = {\n    nixpkgs.url = \"github:nixos/nixpkgs?ref=nixos-unstable\";\n    hyprland.url = \"github:hyprwm/Hyprland\";\n    hyprshell = {\n      url = \"github:H3rmt/hyprshell?ref=hyprshell-release\";\n      inputs.hyprland.follows = \"hyprland\";\n    };\n  };\n\n  outputs = { nixpkgs, hyprshell }@inputs: {\n    nixosConfigurations.hostname = nixpkgs.lib.nixosSystem {\n      specialArgs = { inherit inputs; };\n      system = \"x86_64-linux\";\n      modules = [\n        home-manager.nixosModules.home-manager\n        ./home.nix\n      ];\n    };\n  };\n}\n```\n\n`./home.nix`:\n\n```nix\n{ inputs, ... } : {\n  home-manager = {\n    extraSpecialArgs = { inherit inputs; };\n    user.test = import ./user.nix; \n  };\n}\n```\n\n`./user.nix`:\n\nAll the settings are optional and can be found in the [module.nix](../nix/module.nix)\n\nEverything is disabled by default, so you need to enable it (even settings.windows if settings.windows.overview is enabled).\n\n```nix\n{ inputs, ... } : {\n  imports = [\n    inputs.hyprshell.homeModules.hyprshell\n  ];\n  programs.hyprshell = {\n    enable = true;\n    # use this if you want the more minimal hyprshell (see Readme.md > Features)\n    package = inputs.hyprshell.packages.${inputs.nixpkgs.stdenv.hostPlatform.system}.hyprshell-slim;\n    # use this if you dont use hyprland via a flake and override hyprshells hyprland input\n    package = inputs.hyprshell.packages.${inputs.nixpkgs.stdenv.hostPlatform.system}.hyprshell-nixpkgs;\n    systemd.args = \"-v\";\n    settings = {\n      windows = {\n        enable = true; # please dont forget to enable windows if you want to use overview or switch\n        overview = {\n          enable = true;\n          key = \"super_l\";\n          modifier = \"super\";\n          launcher = {\n            max_items = 6;\n          };\n        };\n        switch.enable = true;\n      };\n    };\n  };\n}\n```\n"
  },
  {
    "path": "docs/css-examples/default.css",
    "content": ":root {\n    --border-color: rgba(45, 45, 45, 1);\n    --border-color-active: rgba(0, 180, 255, 1);\n\n    --bg-color: rgba(12, 12, 12, 0.8);\n    --bg-color-hover: rgba(28, 28, 28, 1);\n\n    --border-radius: 12px;\n    --border-size: 3px;\n    --border-style: solid;\n\n    --text-color: rgba(245, 245, 245, 1);\n\n    --window-padding: 3px;\n    --bg-window-color: rgba(18, 18, 18, 0.95);\n}"
  },
  {
    "path": "docs/css-examples/dracula.css",
    "content": ":root {\n    --border-color: rgba(75, 75, 90, 0.8);\n    --border-color-active: rgba(189, 147, 249, 0.9);\n\n    --bg-color: rgba(40, 42, 54, 0.8);\n    --bg-color-hover: rgba(53, 55, 70, 0.9);\n\n    --border-radius: 12px;\n    --border-size: 3px;\n    --border-style: solid;\n\n    --text-color: rgba(248, 248, 242, 1);\n\n    --window-padding: 2px;\n    --bg-window-color: rgba(42, 47, 66, 0.95);\n}"
  },
  {
    "path": "docs/css-examples/gruvbox_dark.css",
    "content": ":root {\n    --border-color: rgba(80, 73, 69, 0.65);\n    --border-color-active: rgba(250, 189, 47, 0.95);\n\n    --bg-color: rgba(40, 40, 40, 0.9);\n    --bg-color-hover: rgba(60, 56, 54, 1);\n\n    --border-radius: 12px;\n    --border-size: 3px;\n    --border-style: solid;\n\n    --text-color: rgba(235, 219, 178, 1);\n\n    --window-padding: 2px;\n    --bg-window-color: rgba(29, 32, 33, 0.92);\n}"
  },
  {
    "path": "docs/css-examples/liquid-glass.css",
    "content": "/* please add layerrules to hyprland config:\n\nlayerrule = blur, hyprshell_overview\nlayerrule = ignorezero, hyprshell_overview\nlayerrule = blur, hyprshell_switch\nlayerrule = ignorezero, hyprshell_switch\nlayerrule = blur, hyprshell_launcher\nlayerrule = ignorezero, hyprshell_launcher\n*/\n\n\n:root {\n    --border-color: rgba(255, 255, 255, 0);\n    --border-color-active: rgba(120, 245, 255, 0.85);\n\n    --bg-color: rgba(255, 255, 255, 0.01);\n    --bg-color-hover: rgba(255, 255, 255, 0.05);\n\n    --border-radius: 20px;\n    --border-size: 3px;\n    --border-style: solid;\n\n    --text-color: rgba(245, 245, 245, 1);\n\n    --window-padding: 0;\n    --bg-window-color: rgba(255, 255, 255, 0.01);\n}\n\n:root {\n    --box-shadow-color: rgba(255, 255, 255, 0.7);\n    --box-shadow-color-hover: rgba(255, 255, 255, 0.85);\n}\n\n.monitor {\n    border: unset;\n    box-shadow: inset 0 0 30px var(--box-shadow-color);\n}\n\n.workspace {\n    box-shadow: inset 0 0 25px var(--box-shadow-color);\n}\n\n.workspace.active {\n    box-shadow: inset 0 0 30px var(--box-shadow-color-hover);\n}\n\n.client {\n    box-shadow: inset 0 0 25px var(--box-shadow-color);\n}\n\n.client.active {\n    box-shadow: inset 0 0 30px var(--box-shadow-color-hover);\n}\n\n.client:hover {\n    box-shadow: inset 0 0 35px var(--box-shadow-color-hover);\n}\n\n.launcher {\n    border: unset;\n    box-shadow: inset 0 0 25px var(--box-shadow-color);\n}\n\n.launcher-input {\n    border-radius: 12px;\n    box-shadow: inset 0 0 8px var(--box-shadow-color);\n}\n\n.launcher-input:focus-within {\n    outline: var(--border-size, 3px) var(--border-style, solid) var(--border-color-active);\n    box-shadow: inset 0 0 12px var(--box-shadow-color-hover);\n}\n\n.launcher-item {\n    padding: 0 10px;\n    border-radius: 16px;\n    box-shadow: inset 0 0 12px var(--box-shadow-color);\n}\n\n.launcher-plugin-inner {\n    background: unset;\n}\n\n.launcher-item:hover {\n    box-shadow: inset 0 0 14px var(--box-shadow-color-hover);\n}\n\n.launcher-plugin {\n    padding: 6px;\n    border-radius: 20px;\n    box-shadow: inset 0 0 20px var(--box-shadow-color);\n}\n\n.launcher-item-inner {\n    background: unset;\n}\n\n.launcher-plugin:hover {\n    box-shadow: inset 0 0 20px var(--box-shadow-color-hover);\n}\n\n@keyframes background-moving {\n    0% {\n        background: linear-gradient(90deg, rgba(120, 245, 255, 0.35) 0%, transparent 0%);\n    }\n    100% {\n        background: linear-gradient(90deg, rgba(120, 245, 255, 0.35) 100%, transparent 100%);\n    }\n}"
  },
  {
    "path": "docs/css-examples/modern.css",
    "content": ":root {\n    --border-color: rgba(67, 76, 94, 1);\n    --border-color-active: rgba(136, 192, 208, 1);\n\n    --bg-color: rgba(30, 34, 42, 0.95);\n    --bg-color-hover: rgba(46, 52, 64, 1);\n\n    --border-radius: 12px;\n    --border-size: 3px;\n    --border-style: solid;\n\n    --text-color: rgba(236, 239, 244, 1);\n\n    --window-padding: 3px;\n    --bg-window-color: rgba(36, 41, 53, 0.95);\n}"
  },
  {
    "path": "docs/css-examples/solarized_dark.css",
    "content": ":root {\n    --border-color: rgba(10, 27, 37, 0.5);\n    --border-color-active: rgba(9, 92, 114, 0.9);\n\n    --bg-color: rgba(0, 36, 45, 0.8);\n    --bg-color-hover: rgba(7, 54, 66, 1);\n\n    --border-radius: 12px;\n    --border-size: 3px;\n    --border-style: solid;\n\n    --text-color: rgb(175, 189, 190);\n\n    --window-padding: 2px;\n    --bg-window-color: rgba(0, 43, 54, 0.85);\n}"
  },
  {
    "path": "docs/css-examples/test.css",
    "content": ".window {\n    background: rgba(90, 45, 45, 0.9);\n}\n\n.monitor {\n    background: rgba(90, 70, 45, 0.9);\n}\n\n.workspace {\n    background: rgba(80, 90, 45, 0.9);\n}\n\n.client {\n    background: rgba(45, 80, 90, 0.9);\n}\n\n.client-image {\n    background: rgba(45, 60, 90, 0.9);\n}\n\n.launcher {\n    background: rgba(60, 45, 90, 0.9);\n}\n\n.launcher-input {\n    background: rgba(90, 45, 75, 0.9);\n}\n\n.launcher-results {\n    background: rgba(45, 90, 75, 0.9);\n}\n\n.launcher-item {\n    background: rgba(70, 70, 70, 0.9);\n}\n\n.launcher-item-inner {\n    background: rgba(90, 90, 45, 0.9);\n}\n\n.launcher-exec {\n    background: rgba(45, 90, 60, 0.9);\n}\n\n.launcher-key {\n    background: rgba(61, 148, 150, 0.9);\n}\n\n\n.launcher-plugins {\n    background: rgba(55, 45, 65, 0.9);\n}\n\n.launcher-plugin {\n    background: rgba(75, 45, 90, 0.9);\n}\n\n.launcher-plugin-inner {\n    background: rgba(90, 65, 45, 0.9);\n}\n\n.launcher-plugin-key {\n    background: rgba(90, 45, 60, 0.9);\n}"
  },
  {
    "path": "docs/css-examples/tokyo_night_storm.css",
    "content": ":root {\n    --border-color: rgba(65, 72, 104, 0.6);\n    --border-color-active: rgba(122, 162, 247, 1);\n\n    --bg-color: rgba(26, 27, 38, 0.92);\n    --bg-color-hover: rgba(41, 46, 66, 1);\n\n    --border-radius: 12px;\n    --border-size: 3px;\n    --border-style: solid;\n\n    --text-color: rgba(192, 202, 245, 1);\n\n    --window-padding: 2px;\n    --bg-window-color: rgba(30, 32, 48, 0.85);\n}"
  },
  {
    "path": "flake.nix",
    "content": "{\n  description = \"hyprshell - A Rust-based GUI designed to enhance window management in hyprland\";\n  inputs = {\n    nixpkgs.url = \"github:nixos/nixpkgs?ref=nixos-unstable\";\n    home-manager = {\n      url = \"github:nix-community/home-manager\";\n      inputs.nixpkgs.follows = \"nixpkgs\";\n    };\n    flake-parts = {\n      url = \"github:hercules-ci/flake-parts\";\n      inputs.nixpkgs-lib.follows = \"nixpkgs\";\n    };\n    hyprland.url = \"github:hyprwm/Hyprland\";\n    crane.url = \"github:ipetkov/crane\";\n  };\n  outputs =\n    inputs@{\n      self,\n      nixpkgs,\n      home-manager,\n      flake-parts,\n      hyprland,\n      crane,\n    }:\n    flake-parts.lib.mkFlake { inherit inputs; } {\n      systems = [\n        \"aarch64-linux\"\n        \"x86_64-linux\"\n      ];\n      perSystem =\n        { pkgs, self', ... }:\n        let\n          craneLib = crane.mkLib pkgs;\n          buildLib = import ./nix/build.nix { inherit craneLib pkgs; };\n        in\n        {\n          formatter = pkgs.nixfmt-tree;\n          packages = rec {\n            hyprshell = craneLib.buildPackage (\n              buildLib.commonArgs\n              // {\n                cargoArtifacts = buildLib.cargoReleaseArtifacts;\n                preFixup =\n                  buildLib.addWrapWithGccArgs\n                    inputs.hyprland.packages.${pkgs.stdenv.hostPlatform.system}.default;\n                postInstall = buildLib.postInstall;\n              }\n            );\n            hyprshell-nixpkgs = craneLib.buildPackage (\n              buildLib.commonArgs\n              // {\n                cargoArtifacts = buildLib.cargoReleaseArtifacts;\n                preFixup = buildLib.addWrapWithGccArgs pkgs.hyprland;\n                postInstall = buildLib.postInstall;\n              }\n            );\n            hyprshell-slim = craneLib.buildPackage (\n              buildLib.commonArgs\n              // {\n                cargoArtifacts = buildLib.cargoReleaseArtifacts;\n                cargoExtraArgs = \"--no-default-features --features slim\";\n                preFixup =\n                  buildLib.addWrapWithGccArgs\n                    inputs.hyprland.packages.${pkgs.stdenv.hostPlatform.system}.default;\n                postInstall = buildLib.postInstall;\n              }\n            );\n            hyprshell-slim-nixpkgs = craneLib.buildPackage (\n              buildLib.commonArgs\n              // {\n                cargoArtifacts = buildLib.cargoReleaseArtifacts;\n                cargoExtraArgs = \"--no-default-features --features slim\";\n                preFixup = buildLib.addWrapWithGccArgs pkgs.hyprland;\n                postInstall = buildLib.postInstall;\n              }\n            );\n            default = hyprshell;\n          };\n          checks = import ./nix/checks.nix {\n            inherit\n              self\n              pkgs\n              craneLib\n              buildLib\n              home-manager\n              ;\n          };\n          devShells.default = craneLib.devShell {\n            checks = self'.checks;\n            stdenv = buildLib.stdenv;\n            packages = [\n              pkgs.rust-analyzer\n            ];\n          };\n        };\n      flake = {\n        homeModules = rec {\n          hyprshell = import ./nix/module.nix self;\n          default = hyprshell;\n        };\n      };\n    };\n}\n"
  },
  {
    "path": "justfile",
    "content": "project_dir := justfile_directory()\n\ndefault:\n    @just --list --justfile {{ justfile() }}\n\n[group('security')]\naudit:\n    #!/usr/bin/env bash\n    if ! command -v cargo-audit >/dev/null 2>&1; then\n        echo \"cargo-audit not found, installing...\"\n        if ! command -v cargo binstall >/dev/null 2>&1; then\n          cargo install --locked cargo-audit\n        else\n          echo \"installing with cargo binstall\"\n          cargo binstall cargo-outdated\n        fi\n    fi\n    cargo audit\n\n[group('security')]\noutdated:\n    #!/usr/bin/env bash\n    if ! command -v cargo-outdated >/dev/null 2>&1; then\n        echo \"cargo-outdated not found, installing...\"\n        if ! command -v cargo binstall >/dev/null 2>&1; then\n          cargo install --locked cargo-outdated\n        else\n          echo \"installing with cargo binstall\"\n          cargo binstall cargo-outdated\n        fi\n    fi\n    cargo outdated\n\n[group('security')]\nshear:\n    #!/usr/bin/env bash\n    if ! command -v cargo-shear >/dev/null 2>&1; then\n        echo \"cargo-shear not found, installing...\"\n        if ! command -v cargo binstall >/dev/null 2>&1; then\n          cargo install --locked cargo-shear\n        else\n          echo \"installing with cargo binstall\"\n          cargo binstall cargo-shear\n        fi\n    fi\n    cargo shear\n\n[group('security')]\nbloat:\n    #!/usr/bin/env bash\n    if ! command -v cargo-bloat >/dev/null 2>&1; then\n        echo \"cargo-bloat not found, installing...\"\n        if ! command -v cargo binstall >/dev/null 2>&1; then\n          cargo install --locked cargo-bloat\n        else\n          echo \"installing with cargo binstall\"\n          cargo binstall cargo-bloat\n        fi\n    fi\n    cargo bloat --release\n\n[group('develop')]\nformat:\n    cargo +nightly fmt --all\n\n[group('develop')]\nfix:\n    cargo fix --allow-dirty --workspace --exclude hyprshell-hyprland --exclude hyprshell-wl-clipboard-rs\n\n[group('develop')]\nbuild profile=\"dev\":\n    cargo build --profile {{ profile }}\n\n[group('checks')]\nlint profile=\"dev\":\n    cargo +nightly fmt --all -- --check\n    cargo clippy --profile {{ profile }} --all-targets --workspace --exclude hyprshell-hyprland --exclude hyprshell-wl-clipboard-rs -- --deny warnings\n\n[group('checks')]\ntest profile=\"dev\":\n    cargo nextest run --cargo-profile {{ profile }} --all-targets --workspace --exclude hyprshell-hyprland --exclude hyprshell-wl-clipboard-rs\n\n[group('checks')]\ncheck-feature-combinations:\n    bash {{ project_dir }}/scripts/check-all-feature-combinations.sh\n\n[group('checks')]\ncheck-default-nix-features:\n    nix build '.#checks.x86_64-linux.hyprshell-check-nix-configs' -L\n\n[group('checks')]\ncheck profile=\"dev\": (build profile) (lint profile) (test profile)\n\npre-release: (check \"release\")\n\n[group('run')]\nrun profile=\"dev\" *args=\"\":\n    cargo run --profile {{ profile }} -- {{ args }}\n\n[group('run')]\nrun-run profile=\"dev\" *args=\"-vv\": (run profile \"run\" args)\n\n[group('run')]\nrun-edit-config profile=\"dev\" *args=\"-vv\": (run profile \"config edit\" args)\n\n[group('run')]\nrun-explain-config profile=\"dev\" *args=\"-vv\": (run profile \"config explain\" args)\n\n[group('run')]\nrun-debug profile=\"dev\" *args=\"\": (run profile \"debug\" args)\n\n[group('dist')]\npackage-usr-lib:\n    #!/usr/bin/env bash\n    sudo tar -cvf ar.tar -C /usr/share/hyprshell.debug setup_preview themes\n    ls -lah ar.tar\n    sudo mv ar.tar ./packaging/usr-share.tar\n    sudo chown user:user ./packaging/usr-share.tar\n"
  },
  {
    "path": "nix/build.nix",
    "content": "{\n  craneLib,\n  pkgs,\n}:\nrec {\n  meta = import ./meta.nix { inherit pkgs; };\n  pname = \"hyprshell\";\n  version = (pkgs.lib.trivial.importTOML ../Cargo.toml).workspace.package.version;\n  # no more filtering, excluded to many files\n  src = ../.;\n  stdenv = p: p.stdenv;\n  # use in preFixup\n  addWrapWithGccArgs = hyprland: ''\n    gappsWrapperArgs+=(\n       --prefix PATH : '${pkgs.lib.makeBinPath [ pkgs.gcc ]}'\n       --prefix CPATH : '${\n         pkgs.lib.makeIncludePath (\n           hyprland.buildInputs\n           ++ [\n             hyprland\n             pkgs.pixman\n           ]\n         )\n       }:${pkgs.libdrm.dev}/include/libdrm'\n     )\n  '';\n  commonArgs = {\n    inherit\n      src\n      stdenv\n      pname\n      version\n      meta\n      ;\n    strictDeps = true;\n    doCheck = false;\n    cargoBuildCommand = \"cargo build --release --locked\";\n    cargoTestCommand = \"\";\n    cargoCheckCommand = \"\";\n    cargoCheckExtraArgs = \"\";\n    cargoExtraArgs = \"\";\n\n    nativeBuildInputs = [\n      pkgs.pkg-config\n      pkgs.wrapGAppsHook4\n    ];\n\n    buildInputs = [\n      pkgs.libadwaita\n      pkgs.gtk4-layer-shell\n    ];\n  };\n\n  cargoReleaseArtifacts = craneLib.buildDepsOnly (\n    commonArgs\n    // {\n      mkDummySrc = craneLib.mkDummySrc {\n        inherit stdenv;\n        src = ../.;\n      };\n      pname = \"hyprshell\";\n    }\n  );\n  cargoFullArtifacts = craneLib.buildDepsOnly (\n    commonArgs\n    // {\n      src = craneLib.cleanCargoSource ../.;\n      pname = \"hyprshell-full\";\n      doCheck = true;\n      cargoBuildCommand = \"cargo build --profile dev --locked --all-targets --all-features\";\n      cargoCheckCommand = \"cargo check --profile dev --locked --all-targets --all-features\";\n      cargoTestCommand = \"cargo test --profile dev --locked --all-targets --all-features\";\n    }\n  );\n  commonArgsFullCached = (\n    commonArgs\n    // {\n      cargoArtifacts = cargoFullArtifacts;\n    }\n  );\n\n  postInstall = ''\n    # Desktop entry\n    install -Dm644 packaging/hyprshell-settings.desktop $out/share/applications/hyprshell-settings.desktop\n\n    # Icon\n    install -Dm644 packaging/hyprshell-settings.png $out/share/pixmaps/hyprshell-settings.png\n\n    # Extract runtime data\n    mkdir -p $out/share/hyprshell\n    tar -xf packaging/usr-share.tar -C $out/share/hyprshell\n  '';\n}\n"
  },
  {
    "path": "nix/checks.nix",
    "content": "{\n  self,\n  pkgs,\n  craneLib,\n  buildLib,\n  home-manager,\n  ...\n}:\nlet\n  customLib = import ./util.nix { lib = pkgs.lib; };\nin\nrec {\n  hyprshell-build-deps = buildLib.cargoFullArtifacts;\n  hyprshell-config-check = craneLib.buildPackage (\n    buildLib.commonArgsFullCached\n    // {\n      cargoBuildCommand = \"cargo build --profile dev --locked\";\n      cargoExtraArgs = \"--features ci_config_check\";\n    }\n  );\n  hyprshell-test = craneLib.cargoNextest (\n    buildLib.commonArgsFullCached\n    // {\n      doCheck = true;\n      CARGO_PROFILE = \"dev\";\n      CPATH = pkgs.lib.makeIncludePath (\n        pkgs.hyprland.buildInputs\n        ++ [\n          pkgs.hyprland\n          pkgs.pixman\n        ]\n      );\n      cargoNextestExtraArgs = \"--all-targets --all-features -p hyprshell-config-lib -p hyprshell-core-lib -p hyprshell-exec-lib -p hyprshell-launcher-lib -p hyprshell-windows-lib -p hyprshell-hyprland-plugin -p hyprshell-clipboard-lib -p hyprshell-config-edit-lib\";\n    }\n  );\n  hyprshell-clippy = craneLib.cargoClippy (\n    buildLib.commonArgsFullCached\n    // {\n      CARGO_PROFILE = \"dev\";\n      cargoClippyExtraArgs = \"--all-targets --all-features  -p hyprshell-config-lib -p hyprshell-core-lib -p hyprshell-exec-lib -p hyprshell-launcher-lib -p hyprshell-windows-lib -p hyprshell-hyprland-plugin  -p hyprshell-clipboard-lib -p hyprshell-config-edit-lib -- --deny warnings\";\n    }\n  );\n  hyprshell-fmt = craneLib.cargoFmt buildLib.commonArgs;\n  hyprshell-check-nix-configs =\n    let\n      base-modules = [\n        self.homeModules.hyprshell\n        {\n          home.stateVersion = \"24.05\";\n          home.username = \"test\";\n          home.homeDirectory = \"/home/test\";\n        }\n      ];\n      empty-config = home-manager.lib.homeManagerConfiguration {\n        inherit pkgs;\n        modules = base-modules;\n      };\n      test-config = home-manager.lib.homeManagerConfiguration {\n        inherit pkgs;\n        modules = base-modules ++ [\n          {\n            programs.hyprshell.settings = {\n              windows.enable = true;\n              windows.overview.enable = true;\n              windows.switch.enable = true;\n            };\n          }\n        ];\n      };\n    in\n    pkgs.runCommand \"hyprshell-check-nix-configs\" { } ''\n      set -euo pipefail\n\n      TMP=$(mktemp -d)\n      trap 'rm -r \"$TMP\"' EXIT\n\n      touch \"$TMP/test.json\"\n      echo \"test json created at $TMP\"\n      cat <<EOF> \"$TMP/test.json\"\n      ${builtins.toJSON (\n        (customLib.filterDisabledAndDropEnable empty-config.config.programs.hyprshell.settings) // { version = 3; }\n      )}\n      EOF\n      chmod 444 \"$TMP/test.json\"\n      ${pkgs.jq}/bin/jq < \"$TMP/test.json\"\n      echo \"test json written to $TMP/test.json\"\n\n      ${hyprshell-config-check}/bin/hyprshell -vv config check-if-default -c \"$TMP/test.json\"\n\n      touch \"$TMP/test-2.json\"\n      echo \"test-2 json created at $TMP\"\n      cat <<EOF> \"$TMP/test-2.json\"\n      ${builtins.toJSON (\n        (customLib.filterDisabledAndDropEnable test-config.config.programs.hyprshell.settings) // { version = 3; }\n      )}\n      EOF\n      chmod 444 \"$TMP/test-2.json\"\n      ${pkgs.jq}/bin/jq < \"$TMP/test-2.json\"\n      echo \"test-2 json written to $TMP/test-2.json\"\n\n      ${hyprshell-config-check}/bin/hyprshell -vv config check-if-full -c \"$TMP/test-2.json\"\n\n      mkdir \"$out\"\n    '';\n  hyprshell-check-all-feature-combinations = craneLib.mkCargoDerivation (\n    buildLib.commonArgsFullCached\n    // {\n      pnameSuffix = \"-check-all-feature-combinations\";\n      nativeBuildInputs = [\n        pkgs.bash\n        pkgs.clippy\n      ]\n      ++ buildLib.commonArgs.nativeBuildInputs;\n      cargoClippyExtraArgs = \"\";\n      buildPhaseCargoCommand = ''\n        bash ${../scripts/check-all-feature-combinations.sh}\n      '';\n    }\n  );\n}\n"
  },
  {
    "path": "nix/meta.nix",
    "content": "{ pkgs }:\n{\n  mainProgram = \"hyprshell\";\n  description = \"A modern GTK4-based window switcher and application launcher for Hyprland\";\n  homepage = \"https://github.com/h3rmt/hyprshell\";\n  license = pkgs.lib.licenses.mit;\n  platforms = pkgs.hyprland.meta.platforms;\n}\n"
  },
  {
    "path": "nix/module.nix",
    "content": "self:\n{\n  pkgs,\n  config,\n  lib,\n  ...\n}:\nlet\n  inherit (lib.types)\n    either\n    bool\n    float\n    int\n    enum\n    lines\n    listOf\n    nullOr\n    package\n    path\n    str\n    submodule\n    ;\n  customLib = import ./util.nix { inherit lib; };\n  cfg = config.programs.hyprshell;\n  mkOpt =\n    description: type: default:\n    lib.mkOption { inherit description type default; };\nin\n{\n  options.programs.hyprshell = {\n    enable = lib.mkEnableOption \"hyprshell\";\n\n    package = lib.mkOption {\n      description = \"The Hyprshell package\";\n      type = nullOr package;\n      default = self.packages.${pkgs.stdenv.hostPlatform.system}.hyprshell;\n    };\n\n    systemd = {\n      enable = lib.mkEnableOption \"Hyprshell systemd service\" // {\n        default = true;\n      };\n      target = lib.mkOption {\n        description = \"The systemd target that will automatically start the Hyprshell service\";\n        type = str;\n        default = config.wayland.systemd.target;\n      };\n      args = lib.mkOption {\n        description = \"Arguments to pass to the Hyprshell service\";\n        type = str;\n        default = \"\";\n        example = \"-vv\";\n      };\n    };\n\n    styleFile = lib.mkOption {\n      description = ''\n        File containing Hyprshell CSS overrides (either a file or text).\n      '';\n      type = nullOr (either path lines);\n      default = null;\n    };\n\n    configFile = lib.mkOption {\n      description = ''\n        File containing Hyprshell configuration !JSON! (either a file or text).\n        Can be used instead of generated config via settings.\n      '';\n      type = nullOr (either path lines);\n      default = null;\n    };\n\n    settings = {\n      windows = {\n        enable = lib.mkEnableOption \"Enable windows (overview, switch)\";\n        scale = mkOpt \"Scale\" float 8.5 // {\n          apply = num: if (num >= 0 && num <= 15) then num else throw \"Value must be between 0 and 15\";\n        };\n        items_per_row = mkOpt \"Workspaces per row\" int 5;\n        overview = {\n          enable = lib.mkEnableOption \"Enable overview\";\n          key = mkOpt \"Key to open overview\" str \"Super_L\";\n          modifier = mkOpt \"Modifier key\" (enum [\n            \"alt\"\n            \"ctrl\"\n            \"super\"\n          ]) \"super\";\n\n          exclude_special_workspaces = mkOpt \"Exclude special workspaces regex\" str \"\";\n          filter_by = mkOpt \"Filter by\" (listOf (enum [\n            \"same_class\"\n            \"current_monitor\"\n            \"current_workspace\"\n          ])) [ ];\n          hide_filtered = mkOpt \"Hide filtered windows\" bool false;\n          launcher = {\n            width = mkOpt \"Launcher width\" int 650;\n            launch_modifier = mkOpt \"Launch modifier\" (enum [\n              \"alt\"\n              \"ctrl\"\n              \"super\"\n            ]) \"ctrl\";\n            max_items = mkOpt \"Max shown items\" int 5;\n            default_terminal = mkOpt \"Default terminal\" (nullOr (str)) null;\n            show_when_empty = mkOpt \"Show entries when no text is entered\" bool true;\n\n            plugins = {\n              applications = {\n                enable = mkOpt \"Open applications\" bool true;\n                run_cache_weeks = mkOpt \"Run Cache weeks\" int 8;\n                show_execs = mkOpt \"Show execs\" bool true;\n                show_actions_submenu = mkOpt \"Show actions submenu\" bool true;\n              };\n              calc = {\n                enable = mkOpt \"Enable calculator\" bool true;\n              };\n              shell = {\n                enable = mkOpt \"Run in Shell\" bool false;\n              };\n              terminal = {\n                enable = mkOpt \"Run in Terminal\" bool true;\n              };\n              websearch = {\n                enable = mkOpt \"Web search\" bool true;\n                engines =\n                  mkOpt \"Search engines\"\n                    (listOf (submodule {\n                      options = {\n                        url = mkOpt \"Search engine URL\" str null;\n                        name = mkOpt \"Search engine name\" str null;\n                        key = mkOpt \"Key to use for search engine\" str null // {\n                          apply = key: if (builtins.stringLength key) != 1 then throw \"Key must be single character\" else key;\n                        };\n                      };\n                    }))\n                    [\n                      {\n                        url = \"https://www.google.com/search?q={}\";\n                        name = \"Google\";\n                        key = \"g\";\n                      }\n                      {\n                        url = \"https://en.wikipedia.org/wiki/Special:Search?search={}\";\n                        name = \"Wikipedia\";\n                        key = \"w\";\n                      }\n                    ];\n              };\n              path = {\n                enable = mkOpt \"Open in File manager\" bool true;\n              };\n              actions = {\n                enable = mkOpt \"Run action\" bool true;\n                actions =\n                  mkOpt \"Actions\"\n                    (listOf (\n                      either\n                        (enum [\n                          \"lock_screen\"\n                          \"hibernate\"\n                          \"logout\"\n                          \"reboot\"\n                          \"shutdown\"\n                          \"suspend\"\n                        ])\n                        (submodule {\n                          options = {\n                            custom = lib.mkOption {\n                              description = \"Custom action object\";\n                              type = submodule {\n                                options = {\n                                  names = lib.mkOption {\n                                    description = \"Names for the action\";\n                                    type = listOf str;\n                                    default = [ ];\n                                  };\n                                  details = mkOpt \"Details about the action\" str null;\n                                  command = mkOpt \"Command to run\" str null;\n                                  icon = mkOpt \"Icon name\" str null;\n                                };\n                              };\n                              default = { };\n                            };\n                          };\n                        })\n                    ))\n                    [\n                      \"lock_screen\"\n                      \"hibernate\"\n                      \"logout\"\n                      \"reboot\"\n                      \"shutdown\"\n                      \"suspend\"\n                      {\n                        custom = {\n                          names = [\n                            \"Kill\"\n                            \"Stop\"\n                          ];\n                          details = \"Kill or stop a process by name\";\n                          command = \"pkill \\\"{}\\\" && notify-send hyprshell \\\"stopped {}\\\"\";\n                          icon = \"remove\";\n                        };\n                      }\n                      {\n                        custom = {\n                          names = [\n                            \"Reload Hyprshell\"\n                          ];\n                          details = \"Reload Hyprshell\";\n                          command = \"sleep 1; hyprshell socat '\\\"Restart\\\"'\";\n                          icon = \"system-restart\";\n                        };\n                      }\n                    ];\n              };\n            };\n          };\n        };\n        switch = {\n          enable = mkOpt \"Enable recent window switcher\" bool true;\n          key = mkOpt \"Key to open switch\" str \"Tab\";\n          modifier = mkOpt \"Modifier key\" (enum [\n            \"alt\"\n            \"ctrl\"\n            \"super\"\n          ]) \"alt\";\n          filter_by = mkOpt \"Filter by\" (listOf (enum [\n            \"same_class\"\n            \"current_monitor\"\n            \"current_workspace\"\n          ])) [ \"current_monitor\" ];\n          switch_workspaces = mkOpt \"Switch workspaces\" bool false;\n          exclude_special_workspaces = mkOpt \"Exclude special workspaces regex\" str \"\";\n        };\n        switch_2 = {\n          enable = mkOpt \"Enable recent window switcher\" bool false;\n          key = mkOpt \"Key to open switch\" str \"Tab\";\n          modifier = mkOpt \"Modifier key\" (enum [\n            \"alt\"\n            \"ctrl\"\n            \"super\"\n          ]) \"alt\";\n          filter_by = mkOpt \"Filter by\" (listOf (enum [\n            \"same_class\"\n            \"current_monitor\"\n            \"current_workspace\"\n          ])) [ \"current_monitor\" ];\n          switch_workspaces = mkOpt \"Switch workspaces\" bool false;\n          exclude_special_workspaces = mkOpt \"Exclude special workspaces regex\" str \"\";\n        };\n      };\n    };\n  };\n\n  config = lib.mkIf cfg.enable ({\n    home.packages = [ cfg.package ];\n\n    systemd.user.services.hyprshell = lib.mkIf cfg.systemd.enable {\n      Unit = {\n        Description = \"Starts Hyprshell daemon\";\n        After = [ cfg.systemd.target ];\n      };\n      Service = {\n        Type = \"simple\";\n        ExecStart = \"${lib.getExe cfg.package} run ${cfg.systemd.args}\";\n        Restart = \"on-failure\";\n      };\n      Install.WantedBy = [ cfg.systemd.target ];\n    };\n\n    xdg.configFile.\"hyprshell/config.json\" =\n      if (lib.isPath cfg.configFile || lib.isStorePath cfg.configFile) then\n        {\n          source = cfg.configFile;\n        }\n      else if (builtins.isString cfg.configFile) then\n        {\n          text = cfg.configFile;\n        }\n      else\n        {\n          text = builtins.toJSON ((customLib.filterDisabledAndDropEnable cfg.settings) // { version = 3; });\n        };\n\n    xdg.configFile.\"hyprshell/styles.css\" =\n      if (lib.isPath cfg.styleFile || lib.isStorePath cfg.styleFile) then\n        {\n          source = cfg.styleFile;\n        }\n      else if (builtins.isString cfg.styleFile) then\n        {\n          text = cfg.styleFile;\n        }\n      else\n        {\n          text = \"\";\n        };\n  });\n}\n"
  },
  {
    "path": "nix/util.nix",
    "content": "{\n  lib,\n}:\nrec {\n  filterDisabledAndDropEnable =\n    value:\n    if lib.isAttrs value then\n      if value ? enable && value.enable == false then\n        null\n      else\n        lib.filterAttrs (k: v: v != null && k != \"enable\") (\n          lib.mapAttrs (_: filterDisabledAndDropEnable) value\n        )\n    else if lib.isList value then\n      lib.filter (v: v != null) (map filterDisabledAndDropEnable value)\n    else\n      value;\n}\n"
  },
  {
    "path": "packaging/hyprshell-settings.desktop",
    "content": "[Desktop Entry]\nName=Hyprshell Settings editor\nComment=Edit Hyprshell settings\nExec=/usr/bin/hyprshell config edit\nIcon=hyprshell-settings\nType=Application\nStartupWMClass=\"com.github.h3rmt.hyprshell-edit\"\nKeywords=hyprshell;settings;"
  },
  {
    "path": "packaging/hyprshell.service",
    "content": "[Unit]\nDescription=hyprshell daemon\nAfter=graphical-session.target\nWants=graphical-session.target\nStartLimitIntervalSec=120\nStartLimitBurst=10\n\n[Service]\nType=simple\nExecStartPre=/bin/sh -c '[ \"$XDG_CURRENT_DESKTOP\" = \"Hyprland\" ] || exit 0'\nExecStart=/usr/bin/hyprshell run\nRestart=always\nRestartSec=1\nTimeoutStopSec=5\n\n[Install]\nWantedBy=graphical-session.target"
  },
  {
    "path": "packaging/pkgbuild/PKGBUILD",
    "content": "pkgname=hyprshell\n# x-release-please-start-version\npkgver=4.9.5\n# x-release-please-end\npkgrel=1\npkgdesc=\"A modern GTK4-based window switcher and application launcher for Hyprland\"\narch=('x86_64' 'aarch64')\nconflicts=('hyprshell')\nprovides=('hyprshell')\nurl=\"https://github.com/h3rmt/hyprshell/\"\nlicense=(\"MIT\")\nmakedepends=('cargo')\ndepends=('hyprland' 'gtk4-layer-shell' 'gtk4' 'gcc' 'pixman' 'libadwaita' 'zstd')\nsource=(\"hyprshell-$pkgver.tar.gz::https://static.crates.io/crates/hyprshell/hyprshell-$pkgver.crate\")\n\nprepare() {\n    export RUSTUP_TOOLCHAIN=stable\n    cd \"hyprshell-$pkgver\"\n    cargo fetch --locked --target \"$(rustc -vV | sed -n 's/host: //p')\"\n}\n\nbuild() {\n    export RUSTUP_TOOLCHAIN=stable\n    export CARGO_TARGET_DIR=target\n    cd \"hyprshell-$pkgver\"\n    cargo build --frozen --release\n}\n\npackage() {\n    install -Dm755 \"hyprshell-$pkgver/target/release/hyprshell\"             \"$pkgdir/usr/bin/hyprshell\"\n    install -Dm644 \"hyprshell-$pkgver/LICENSE\"                              \"$pkgdir/usr/share/licenses/hyprshell/LICENSE\"\n    install -Dm644 \"hyprshell-$pkgver/README.md\"                            \"$pkgdir/usr/share/doc/hyprshell/README.md\"\n    install -Dm644 \"hyprshell-$pkgver/docs/CONFIGURE.md\"                    \"$pkgdir/usr/share/doc/hyprshell/CONFIGURE.md\"\n    install -Dm644 \"hyprshell-$pkgver/docs/DEBUG.md\"                        \"$pkgdir/usr/share/doc/hyprshell/DEBUG.md\"\n\tinstall -Dm644 \"hyprshell-$pkgver/packaging/hyprshell.service\"          \"$pkgdir/usr/lib/systemd/user/hyprshell.service\"\n\tinstall -Dm644 \"hyprshell-$pkgver/packaging/hyprshell-settings.png\"     \"$pkgdir/usr/share/pixmaps/hyprshell.png\"\n\tinstall -Dm644 \"hyprshell-$pkgver/packaging/hyprshell-settings.desktop\" \"$pkgdir/usr/share/applications/hyprshell-settings.desktop\"\n\n    mkdir \"$pkgdir/usr/share/hyprshell\"\n    tar -xvf \"hyprshell-$pkgver/packaging/usr-share.tar\" -C \"$pkgdir/usr/share/hyprshell\"\n\n    \"$pkgdir/usr/bin/hyprshell\" completions bash -p  \"$pkgdir/usr/share/bash-completion/completions\"\n    \"$pkgdir/usr/bin/hyprshell\" completions fish -p  \"$pkgdir/usr/share/fish/vendor_completions.d\"\n    \"$pkgdir/usr/bin/hyprshell\" completions zsh -p   \"$pkgdir/usr/share/zsh/site-functions\"\n}\n"
  },
  {
    "path": "packaging/pkgbuild/PKGBUILD-bin",
    "content": "pkgname=hyprshell-bin\n# x-release-please-start-version\npkgver=4.9.5\n# x-release-please-end\npkgrel=1\npkgdesc=\"A modern GTK4-based window switcher and application launcher for Hyprland (binary release)\"\narch=('x86_64' 'aarch64')\nconflicts=('hyprshell')\nprovides=('hyprshell')\nurl=\"https://github.com/h3rmt/hyprshell/\"\nlicense=(\"MIT\")\ndepends=('hyprland' 'gtk4-layer-shell' 'gtk4' 'gcc' 'pixman' 'libadwaita' 'zstd')\nsource_x86_64=(\"https://github.com/H3rmt/hyprshell/releases/download/v$pkgver/hyprshell-$pkgver-x86_64.tar.zst\")\nsource_aarch64=(\"https://github.com/H3rmt/hyprshell/releases/download/v$pkgver/hyprshell-$pkgver-aarch64.tar.zst\")\n\npackage() {\n    install -Dm755 \"hyprshell\"                  \"$pkgdir/usr/bin/hyprshell\"\n    install -Dm644 \"LICENSE\"                    \"$pkgdir/usr/share/licenses/hyprshell/LICENSE\"\n    install -Dm644 \"README.md\"                  \"$pkgdir/usr/share/doc/hyprshell/README.md\"\n    install -Dm644 \"CONFIGURE.md\"               \"$pkgdir/usr/share/doc/hyprshell/CONFIGURE.md\"\n    install -Dm644 \"DEBUG.md\"                   \"$pkgdir/usr/share/doc/hyprshell/DEBUG.md\"\n\tinstall -Dm644 \"hyprshell.service\"          \"$pkgdir/usr/lib/systemd/user/hyprshell.service\"\n\tinstall -Dm644 \"hyprshell-settings.png\"     \"$pkgdir/usr/share/pixmaps/hyprshell.png\"\n\tinstall -Dm644 \"hyprshell-settings.desktop\" \"$pkgdir/usr/share/applications/hyprshell-settings.desktop\"\n\n    mkdir \"$pkgdir/usr/share/hyprshell\"\n    tar -xvf \"usr-share.tar\" -C \"$pkgdir/usr/share/hyprshell\"\n\n    \"$pkgdir/usr/bin/hyprshell\" completions bash -p  \"$pkgdir/usr/share/bash-completion/completions\"\n    \"$pkgdir/usr/bin/hyprshell\" completions fish -p  \"$pkgdir/usr/share/fish/vendor_completions.d\"\n    \"$pkgdir/usr/bin/hyprshell\" completions zsh -p   \"$pkgdir/usr/share/zsh/site-functions\"\n}"
  },
  {
    "path": "packaging/pkgbuild/PKGBUILD-slim",
    "content": "pkgname=hyprshell-slim\n# x-release-please-start-version\npkgver=4.9.5\n# x-release-please-end\npkgrel=1\npkgdesc=\"A modern GTK4-based window switcher and application launcher for Hyprland (lightweight version)\"\narch=('x86_64' 'aarch64')\nconflicts=('hyprshell')\nprovides=('hyprshell')\nurl=\"https://github.com/h3rmt/hyprshell/\"\nlicense=(\"MIT\")\nmakedepends=('cargo')\ndepends=('hyprland' 'gtk4-layer-shell' 'gtk4' 'gcc' 'pixman' 'libadwaita' 'zstd')\nsource=(\"hyprshell-$pkgver.tar.gz::https://static.crates.io/crates/hyprshell/hyprshell-$pkgver.crate\")\n\nprepare() {\n    export RUSTUP_TOOLCHAIN=stable\n    cd \"hyprshell-$pkgver\"\n    cargo fetch --locked --target \"$(rustc -vV | sed -n 's/host: //p')\"\n}\n\nbuild() {\n    export RUSTUP_TOOLCHAIN=stable\n    export CARGO_TARGET_DIR=target\n    cd \"hyprshell-$pkgver\"\n    cargo build --frozen --release --no-default-features --features=slim\n}\n\npackage() {\n    install -Dm755 \"hyprshell-$pkgver/target/release/hyprshell\"             \"$pkgdir/usr/bin/hyprshell\"\n    install -Dm644 \"hyprshell-$pkgver/LICENSE\"                              \"$pkgdir/usr/share/licenses/hyprshell/LICENSE\"\n    install -Dm644 \"hyprshell-$pkgver/README.md\"                            \"$pkgdir/usr/share/doc/hyprshell/README.md\"\n    install -Dm644 \"hyprshell-$pkgver/docs/CONFIGURE.md\"                    \"$pkgdir/usr/share/doc/hyprshell/CONFIGURE.md\"\n    install -Dm644 \"hyprshell-$pkgver/docs/DEBUG.md\"                        \"$pkgdir/usr/share/doc/hyprshell/DEBUG.md\"\n\tinstall -Dm644 \"hyprshell-$pkgver/packaging/hyprshell.service\"          \"$pkgdir/usr/lib/systemd/user/hyprshell.service\"\n\tinstall -Dm644 \"hyprshell-$pkgver/packaging/hyprshell-settings.png\"     \"$pkgdir/usr/share/pixmaps/hyprshell.png\"\n\tinstall -Dm644 \"hyprshell-$pkgver/packaging/hyprshell-settings.desktop\" \"$pkgdir/usr/share/applications/hyprshell-settings.desktop\"\n\n    mkdir \"$pkgdir/usr/share/hyprshell\"\n    tar -xvf \"hyprshell-$pkgver/packaging/usr-share.tar\" -C \"$pkgdir/usr/share/hyprshell\"\n\n    \"$pkgdir/usr/bin/hyprshell\" completions bash -p  \"$pkgdir/usr/share/bash-completion/completions\"\n    \"$pkgdir/usr/bin/hyprshell\" completions fish -p  \"$pkgdir/usr/share/fish/vendor_completions.d\"\n    \"$pkgdir/usr/bin/hyprshell\" completions zsh -p   \"$pkgdir/usr/share/zsh/site-functions\"\n}\n"
  },
  {
    "path": "pkgbuild/PKGBUILD",
    "content": "pkgname=hyprshell\n# x-release-please-start-version\npkgver=4.8.3\n# x-release-please-end\npkgrel=1\npkgdesc=\"A modern GTK4-based window switcher and application launcher for Hyprland\"\narch=('x86_64' 'aarch64')\nconflicts=('hyprshell')\nprovides=('hyprshell')\nurl=\"https://github.com/h3rmt/hyprshell/\"\nlicense=(\"MIT\")\nmakedepends=('cargo')\ndepends=('hyprland' 'gtk4-layer-shell' 'gtk4' 'gcc' 'pixman' 'libadwaita')\nsource=(\"hyprshell-$pkgver.tar.gz::https://static.crates.io/crates/hyprshell/hyprshell-$pkgver.crate\")\n\nprepare() {\n    export RUSTUP_TOOLCHAIN=stable\n    cd \"hyprshell-$pkgver\"\n    cargo fetch --locked --target \"$(rustc -vV | sed -n 's/host: //p')\"\n}\n\nbuild() {\n    export RUSTUP_TOOLCHAIN=stable\n    export CARGO_TARGET_DIR=target\n    cd \"hyprshell-$pkgver\"\n    cargo build --frozen --release\n}\n\npackage() {\n    install -Dm755 \"hyprshell-$pkgver/target/release/hyprshell\" \"$pkgdir/usr/bin/hyprshell\"\n    install -Dm644 \"hyprshell-$pkgver/LICENSE\" \"$pkgdir/usr/share/licenses/hyprshell/LICENSE\"\n\n    \"$pkgdir/usr/bin/hyprshell\" completions bash -p \"$pkgdir/usr/share/bash-completion/completions\"\n    \"$pkgdir/usr/bin/hyprshell\" completions fish -p \"$pkgdir/usr/share/fish/vendor_completions.d\"\n    \"$pkgdir/usr/bin/hyprshell\" completions zsh -p \"$pkgdir/usr/share/zsh/site-functions\"\n}\n"
  },
  {
    "path": "pkgbuild/PKGBUILD-bin",
    "content": "pkgname=hyprshell-bin\n# x-release-please-start-version\npkgver=4.8.3\n# x-release-please-end\npkgrel=1\npkgdesc=\"A modern GTK4-based window switcher and application launcher for Hyprland\"\narch=('x86_64' 'aarch64')\nconflicts=('hyprshell')\nprovides=('hyprshell')\nurl=\"https://github.com/h3rmt/hyprshell/\"\nlicense=(\"MIT\")\ndepends=('hyprland' 'gtk4' 'gcc' 'pixman' 'libadwaita')\nsource_x86_64=(\"https://github.com/H3rmt/hyprshell/releases/download/v$pkgver/hyprshell-$pkgver-x86_64.tar.zst\")\nsource_aarch64=(\"https://github.com/H3rmt/hyprshell/releases/download/v$pkgver/hyprshell-$pkgver-aarch64.tar.zst\")\n\npackage() {\n    install -Dm755 hyprshell \"$pkgdir/usr/bin/hyprshell\"\n    install -Dm644 LICENSE \"$pkgdir/usr/share/licenses/hyprshell/LICENSE\"\n\n    \"$pkgdir/usr/bin/hyprshell\" completions bash -p \"$pkgdir/usr/share/bash-completion/completions\"\n    \"$pkgdir/usr/bin/hyprshell\" completions fish -p \"$pkgdir/usr/share/fish/vendor_completions.d\"\n    \"$pkgdir/usr/bin/hyprshell\" completions zsh -p \"$pkgdir/usr/share/zsh/site-functions\"\n}\n"
  },
  {
    "path": "pkgbuild/PKGBUILD-slim",
    "content": "pkgname=hyprshell-slim\n# x-release-please-start-version\npkgver=4.8.3\n# x-release-please-end\npkgrel=1\npkgdesc=\"A modern GTK4-based window switcher and application launcher for Hyprland (lightweight version)\"\narch=('x86_64' 'aarch64')\nconflicts=('hyprshell')\nprovides=('hyprshell')\nurl=\"https://github.com/h3rmt/hyprshell/\"\nlicense=(\"MIT\")\nmakedepends=('cargo')\ndepends=('hyprland' 'gtk4-layer-shell' 'gtk4' 'gcc' 'pixman' 'libadwaita')\nsource=(\"hyprshell-$pkgver.tar.gz::https://static.crates.io/crates/hyprshell/hyprshell-$pkgver.crate\")\n\nprepare() {\n    export RUSTUP_TOOLCHAIN=stable\n    cd \"hyprshell-$pkgver\"\n    cargo fetch --locked --target \"$(rustc -vV | sed -n 's/host: //p')\"\n}\n\nbuild() {\n    export RUSTUP_TOOLCHAIN=stable\n    export CARGO_TARGET_DIR=target\n    cd \"hyprshell-$pkgver\"\n    cargo build --frozen --release --no-default-features --features=slim\n}\n\npackage() {\n    install -Dm755 \"hyprshell-$pkgver/target/release/hyprshell\" \"$pkgdir/usr/bin/hyprshell\"\n    install -Dm644 \"hyprshell-$pkgver/LICENSE\" \"$pkgdir/usr/share/licenses/hyprshell/LICENSE\"\n\n    \"$pkgdir/usr/bin/hyprshell\" completions bash -p \"$pkgdir/usr/share/bash-completion/completions\"\n    \"$pkgdir/usr/bin/hyprshell\" completions fish -p \"$pkgdir/usr/share/fish/vendor_completions.d\"\n    \"$pkgdir/usr/bin/hyprshell\" completions zsh -p \"$pkgdir/usr/share/zsh/site-functions\"\n}\n"
  },
  {
    "path": "release-please-config.json",
    "content": "{\n  \"$schema\": \"https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json\",\n  \"last-release-sha\": \"0714d2de5c82d2cffd7cacfb21377aac0f224cf2\",\n  \"always-update\": true,\n  \"include-v-in-tag\": true,\n  \"draft\": false,\n  \"packages\": {\n    \".\": {\n      \"release-type\": \"simple\",\n      \"extra-files\": [\n        {\n          \"type\": \"generic\",\n          \"path\": \"packaging/pkgbuild/PKGBUILD\"\n        },\n        {\n          \"type\": \"generic\",\n          \"path\": \"packaging/pkgbuild/PKGBUILD-slim\"\n        },\n        {\n          \"type\": \"generic\",\n          \"path\": \"packaging/pkgbuild/PKGBUILD-bin\"\n        }\n      ]\n    }\n  },\n  \"changelog-sections\": [\n    {\n      \"type\": \"feat\",\n      \"section\": \"Features\"\n    },\n    {\n      \"type\": \"feature\",\n      \"section\": \"Features\"\n    },\n    {\n      \"type\": \"fix\",\n      \"section\": \"Bug Fixes\"\n    },\n    {\n      \"type\": \"perf\",\n      \"section\": \"Performance Improvements\"\n    },\n    {\n      \"type\": \"revert\",\n      \"section\": \"Reverts\"\n    },\n    {\n      \"type\": \"refactor\",\n      \"section\": \"Code Refactoring\"\n    },\n    {\n      \"type\": \"docs\",\n      \"section\": \"Documentation\"\n    },\n    {\n      \"type\": \"style\",\n      \"section\": \"Styles\",\n      \"hidden\": true\n    },\n    {\n      \"type\": \"chore\",\n      \"section\": \"Miscellaneous Chores\",\n      \"hidden\": true\n    },\n    {\n      \"type\": \"test\",\n      \"section\": \"Tests\",\n      \"hidden\": true\n    },\n    {\n      \"type\": \"build\",\n      \"section\": \"Build System\",\n      \"hidden\": true\n    },\n    {\n      \"type\": \"ci\",\n      \"section\": \"Continuous Integration\",\n      \"hidden\": true\n    }\n  ]\n}\n"
  },
  {
    "path": "renovate.json",
    "content": "{\n  \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n  \"extends\": [\n    \"schedule:earlyMondays\",\n    \"config:best-practices\",\n    \"helpers:pinGitHubActionDigestsToSemver\"\n  ],\n  \"baseBranchPatterns\": [\n    \"hyprshell\"\n  ],\n  \"ignorePaths\": [\n    \"dep-crates/**/*\"\n  ]\n}\n"
  },
  {
    "path": "scripts/check-all-feature-combinations.sh",
    "content": "#!/usr/bin/env bash\nset -euo pipefail\n\ncargoBuildLog=$(mktemp cargoBuildLogXXXX.json)\n\n# Function to build with a specific combination of features\nbuild_with_features() {\n  local feature_combination=\"$1\"\n  local iteration=\"$2\"\n  local start_time=$(date +%s.%N)\n\n  if [[ -z \"$feature_combination\" ]]; then\n    echo \"[$iteration] Running clippy without any features...\"\n    cargo clippy --profile dev --locked --no-default-features --message-format json-render-diagnostics > \"$cargoBuildLog\"\n  else\n    echo \"[$iteration] Building with features: $feature_combination\"\n    cargo clippy --profile dev --locked --no-default-features --features \"$feature_combination\" --message-format json-render-diagnostics > \"$cargoBuildLog\"\n  fi\n\n  local duration=$(awk \"BEGIN {print $(date +%s.%N) - $start_time}\")\n  printf \"  took %.2f seconds\\n\" \"$duration\"\n}\n\n\ntest_feature_combinations() {\n  local -n features_ref=$1\n  local num_features=${#features_ref[@]}\n  echo \"num_features: $num_features, iterations: $((1 << num_features))\"\n  for ((i = 0; i < (1 << num_features); i++)); do\n    combination=()\n    for ((j = num_features - 1; j >= 0; j--)); do\n      if ((i & (1 << j))); then\n        combination+=(\"${features_ref[j]}\")\n      fi\n    done\n    build_with_features \"$(IFS=,; printf '%s' \"${combination[*]}\")\" \"$i\"\n  done\n  echo \"all features tested\"\n}\n\nbuild_with_features \"default\" default\nbuild_with_features \"slim\" slim\n\ndeclare -a features=(\"gui_settings_editor\" \"ci_config_check\" \"launcher_calc\" \"debug_command\" \"json5_config\")\ntest_feature_combinations features\n\ndeclare -a crypt_features=(\"clipboard_encrypt_chacha20poly1305\" \"clipboard_encrypt_aes_gcm\" \"clipboard_compress_zstd\" \"clipboard_compress_brotli\" \"clipboard_compress_lz4\")\ntest_feature_combinations crypt_features"
  },
  {
    "path": "scripts/ci/build-aarch64.sh",
    "content": "#!/usr/bin/env bash\nset -euxo pipefail\n\nsudo apt update && sudo apt -y install --no-install-recommends \\\n  libgraphene-1.0-dev:arm64 zstd\n\nexport PKG_CONFIG_ALLOW_CROSS=1\nexport PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig:/usr/aarch64-linux-gnu/lib/pkgconfig\n\nrustup default stable && rustup target add aarch64-unknown-linux-gnu\nCARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc cargo build --release --target aarch64-unknown-linux-gnu\nls -lh target/aarch64-unknown-linux-gnu/release/hyprshell\n\ntar --zstd -cf /tmp/hyprshell-aarch64.tar.zst --transform 's,.*/,,' LICENSE README.md docs/CONFIGURE.md docs/DEBUG.md target/aarch64-unknown-linux-gnu/release/hyprshell packaging/hyprshell.service packaging/hyprshell-settings.png packaging/hyprshell-settings.desktop packaging/usr-share.tar\nls -lh /tmp/hyprshell-aarch64.tar.zst\n\nCARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc cargo build --release --target aarch64-unknown-linux-gnu --no-default-features --features slim\nls -lh target/aarch64-unknown-linux-gnu/release/hyprshell\n\ntar --zstd -cf /tmp/hyprshell-slim-aarch64.tar.zst --transform 's,.*/,,' LICENSE README.md docs/CONFIGURE.md docs/DEBUG.md target/aarch64-unknown-linux-gnu/release/hyprshell packaging/hyprshell.service packaging/hyprshell-settings.png packaging/hyprshell-settings.desktop packaging/usr-share.tar\nls -lh /tmp/hyprshell-slim-aarch64.tar.zst\n"
  },
  {
    "path": "scripts/ci/build-x86.sh",
    "content": "#!/usr/bin/env bash\nset -euxo pipefail\n\nrustup default stable && rustup target add x86_64-unknown-linux-gnu\ncargo build --release --target x86_64-unknown-linux-gnu\nls -lh target/x86_64-unknown-linux-gnu/release/hyprshell\n\ntar --zstd -cf /tmp/hyprshell-x86_64.tar.zst --transform 's,.*/,,' LICENSE README.md docs/CONFIGURE.md docs/DEBUG.md target/x86_64-unknown-linux-gnu/release/hyprshell packaging/hyprshell.service packaging/hyprshell-settings.png packaging/hyprshell-settings.desktop packaging/usr-share.tar\nls -lh /tmp/hyprshell-x86_64.tar.zst\n\ncargo build --release --target x86_64-unknown-linux-gnu --no-default-features --features slim\nls -lh target/x86_64-unknown-linux-gnu/release/hyprshell\n\ntar --zstd -cf /tmp/hyprshell-slim-x86_64.tar.zst --transform 's,.*/,,' LICENSE README.md docs/CONFIGURE.md docs/DEBUG.md target/x86_64-unknown-linux-gnu/release/hyprshell packaging/hyprshell.service packaging/hyprshell-settings.png packaging/hyprshell-settings.desktop packaging/usr-share.tar\nls -lh /tmp/hyprshell-slim-x86_64.tar.zst\n"
  },
  {
    "path": "scripts/ci/install-gtk4-layer-shell-aarch64.sh",
    "content": "#!/usr/bin/env bash\nset -euxo pipefail\n\nexport PKG_CONFIG_ALLOW_CROSS=1\nexport PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig\n\nsudo tee /etc/apt/sources.list.d/debian-arm64.list >/dev/null <<'EOF'\ndeb [arch=arm64] http://deb.debian.org/debian testing main contrib non-free\nEOF\n\nsudo apt remove -y libpango1.0-dev:amd64 libgtk-4-dev:amd64 libadwaita-1-dev:amd64\nsudo apt autoremove -y\n\nsudo dpkg --add-architecture arm64 && sudo apt update\nsudo apt -y install --no-install-recommends \\\n  crossbuild-essential-arm64 \\\n  libgtk-4-dev:arm64 libadwaita-1-dev:arm64 libpango1.0-dev:arm64 \\\n  libgirepository1.0-dev:arm64 libgtk4-layer-shell-dev:arm64 \\\n  gobject-introspection"
  },
  {
    "path": "scripts/ci/install-gtk4-layer-shell.sh",
    "content": "#!/usr/bin/env bash\nset -euxo pipefail\n\nsudo apt update\nsudo apt -y install --no-install-recommends \\\n  meson ninja-build python3 valac \\\n  libgtk-4-dev libadwaita-1-dev libpango1.0-dev \\\n  libgtk4-layer-shell-dev gobject-introspection zstd"
  },
  {
    "path": "src/cli.rs",
    "content": "use clap::{Args, Parser, Subcommand};\nuse std::fmt::Debug;\nuse std::path::PathBuf;\n\n#[derive(Parser, Debug, Clone)]\n#[command(\n    author,\n    version,\n    about,\n    long_about = \"A modern GTK4-based window switcher and application launcher for Hyprland\"\n)]\npub struct App {\n    #[clap(flatten)]\n    pub global_opts: GlobalOpts,\n\n    #[clap(subcommand)]\n    pub command: Command,\n}\n\n#[derive(Args, Debug, Clone)]\npub struct GlobalOpts {\n    /// Increase the verbosity level (-v: debug, -vv: trace)\n    #[arg(short = 'v', global = true, action = clap::ArgAction::Count)]\n    pub verbose: u8,\n\n    /// Turn off all output\n    #[arg(short = 'q', long, global = true)]\n    pub quiet: bool,\n\n    /// Path to config [default: `$XDG_CONFIG_HOME/hyprshell/config.ron`],\n    /// allowed file types: ron, toml, json5\n    #[arg(short = 'c', long, global = true)]\n    pub config_file: Option<PathBuf>,\n\n    /// Path to css [default: `$XDG_CONFIG_HOME/hyprshell/styles.css`]\n    #[arg(long, short = 's', global = true)]\n    pub css_file: Option<PathBuf>,\n\n    /// Path to data directory [default: `$XDG_DATA_HOME/hyprshell`]\n    #[arg(long, global = true)]\n    pub data_dir: Option<PathBuf>,\n\n    /// Path to cache directory [default: `$XDG_CACHE_HOME/hyprshell`]\n    #[arg(long, global = true)]\n    pub cache_dir: Option<PathBuf>,\n\n    /// Path to system data directory [default: `/usr/share/hyprshell`]\n    #[arg(long, global = true)]\n    pub system_data_dir: Option<PathBuf>,\n}\n\n#[derive(Subcommand, Debug, Clone)]\npub enum Command {\n    /// Run the hyprshell daemon\n    Run {},\n\n    /// Generate or check the config file\n    Config {\n        #[clap(subcommand)]\n        command: ConfigCommand,\n    },\n\n    #[cfg(feature = \"debug_command\")]\n    /// Debug command to debug finding icons for the GUI, desktop files, etc.\n    Debug {\n        #[clap(subcommand)]\n        command: DebugCommand,\n    },\n\n    /// Show data, like launch history, etc.\n    Data {\n        #[clap(subcommand)]\n        command: DataCommand,\n    },\n\n    /// Send json to the hyprshell socket\n    #[clap(hide = true)]\n    Socat {\n        /// JSON to send to the socket\n        json: String,\n    },\n\n    /// Generate completions for shells\n    Completions {\n        /// Shell to generate completion for (if not set completions for all shells will be generated)\n        shell: Option<String>,\n\n        /// BASE Path for completion without filename\n        /// Bash Default: `/usr/share/bash-completion/completions`\n        /// Fish Default: `/usr/share/fish/vendor_completions.d`\n        /// Zsh Default: `/usr/share/zsh/site-functions`\n        #[arg(long, short = 'p')]\n        base_path: Option<PathBuf>,\n\n        /// Delete the generated completion files\n        #[arg(short = 'd', long, default_value_t = false)]\n        delete: bool,\n    },\n}\n\n#[derive(Subcommand, Debug, Clone)]\npub enum ConfigCommand {\n    /// Generate a default config file (just opens GUI editor)\n    Generate {},\n\n    /// Edit the config file with a GUI\n    Edit {},\n\n    /// Check the config file for errors\n    Check {},\n\n    /// Explain how to use the program based on the config\n    Explain {},\n\n    #[cfg(feature = \"ci_config_check\")]\n    /// Check if the provided config is equal to the default config\n    CheckIfDefault {},\n\n    #[cfg(feature = \"ci_config_check\")]\n    /// Check if the provided config is equal to the fully enabled config\n    CheckIfFull {},\n}\n\n#[derive(Subcommand, Debug, Clone)]\npub enum DataCommand {\n    /// Show the history of launched applications\n    LaunchHistory {\n        /// weeks to include in the history, defaults to set config value\n        run_cache_weeks: Option<u8>,\n    },\n}\n\n#[derive(Subcommand, Debug, Clone)]\npub enum DebugCommand {\n    /// List all icons in the theme\n    ListIcons,\n\n    /// List all desktop files\n    ListDesktopFiles,\n\n    /// Search for an icon with a window class\n    CheckClass {\n        /// The class (from `hyprctl clients -j | jq -e \".[] | {title, class}\"`) of a window to find an icon for\n        ///\n        /// If not provided, all open windows will be searched\n        class: Option<String>,\n    },\n\n    /// simulate search in launcher and display search insights\n    Search {\n        /// text entered into the search box\n        text: String,\n\n        /// Show all matches, not just x ones like configured in config\n        #[arg(short = 'a', long)]\n        all: bool,\n    },\n\n    /// get or set default applications for different mime types\n    DefaultApplications {\n        #[clap(subcommand)]\n        command: DefaultApplicationsCommand,\n    },\n\n    /// print debug info\n    Info {},\n}\n\n#[derive(Subcommand, Debug, Clone)]\npub enum DefaultApplicationsCommand {\n    /// Get default app for mimetype\n    Get {\n        /// for example `image/png` of `x-scheme-handler/https`\n        mime: String,\n    },\n\n    /// Sets a default app for a mimetype (if one already exists, it is replaced)\n    Set {\n        /// for example `image/png` of `x-scheme-handler/https`\n        mime: String,\n        /// Name of a desktop file (with .desktop extension)\n        value: String,\n    },\n\n    /// Add an association app for mimetype (if one already exists, this one is placed before)\n    Add {\n        /// for example `image/png` of `x-scheme-handler/https`\n        mime: String,\n        /// Name of a desktop file (with .desktop extension)\n        value: String,\n    },\n\n    /// List default apps for all mimetypes\n    List {\n        /// Show all mimes instead of ony the ones used by hyprshell\n        #[arg(short = 'a', long)]\n        all: bool,\n    },\n\n    /// Check if all entries in all mimetype files point to valid desktop files\n    Check {},\n}\n"
  },
  {
    "path": "src/completions.rs",
    "content": "use crate::cli;\nuse anyhow::{Context, bail};\nuse clap_complete::aot;\nuse std::fs::{create_dir_all, remove_file, write};\nuse std::path::PathBuf;\nuse tracing::info;\n\npub fn generate(shell: &str, path: Option<PathBuf>, delete: bool) -> anyhow::Result<()> {\n    use clap::CommandFactory;\n    let cli = &mut cli::App::command();\n    let mut buffer = Vec::new();\n    match shell {\n        \"bash\" => {\n            let mut path = path.unwrap_or_else(|| \"/usr/share/bash-completion/completions\".into());\n            create_dir_all(&path)\n                .with_context(|| format!(\"failed to create directory: {}\", path.display()))?;\n            path.push(\"hyprshell.bash\");\n            if delete {\n                remove_file(&path)\n                    .with_context(|| format!(\"failed to remove file: {}\", &path.display()))?;\n                info!(\n                    \"Removed existing bash completion script at: {}\",\n                    path.display()\n                );\n            } else {\n                aot::generate(aot::Bash, cli, \"hyprshell\", &mut buffer);\n                write(&path, buffer)\n                    .with_context(|| format!(\"failed to write to file: {}\", &path.display()))?;\n                info!(\"Generated bash completion script at: {}\", path.display());\n            }\n        }\n        \"zsh\" => {\n            let mut path = path.unwrap_or_else(|| \"/usr/share/zsh/site-functions\".into());\n            create_dir_all(&path)\n                .with_context(|| format!(\"failed to create directory: {}\", &path.display()))?;\n            path.push(\"_hyprshell\");\n            if delete {\n                remove_file(&path)\n                    .with_context(|| format!(\"failed to remove file: {}\", &path.display()))?;\n                info!(\n                    \"Removed existing zsh completion script at: {}\",\n                    path.display()\n                );\n            } else {\n                aot::generate(aot::Zsh, cli, \"hyprshell\", &mut buffer);\n                write(&path, buffer)\n                    .with_context(|| format!(\"failed to write to file: {}\", &path.display()))?;\n                info!(\"Generated zsh completion script at: {}\", path.display());\n            }\n        }\n        \"fish\" => {\n            let mut path = path.unwrap_or_else(|| \"/usr/share/fish/vendor_completions.d\".into());\n            create_dir_all(&path)\n                .with_context(|| format!(\"failed to create directory: {}\", &path.display()))?;\n            path.push(\"hyprshell.fish\");\n            if delete {\n                remove_file(&path)\n                    .with_context(|| format!(\"failed to remove file: {}\", &path.display()))?;\n                info!(\n                    \"Removed existing fish completion script at: {}\",\n                    path.display()\n                );\n            } else {\n                aot::generate(aot::Fish, cli, \"hyprshell\", &mut buffer);\n                write(&path, buffer)\n                    .with_context(|| format!(\"failed to write to file: {}\", &path.display()))?;\n                info!(\"Generated fish completion script at: {}\", path.display());\n            }\n        }\n        _ => bail!(\"unknown shell: {shell}\"),\n    }\n    Ok(())\n}\n"
  },
  {
    "path": "src/data.rs",
    "content": "#![allow(clippy::print_stderr, clippy::print_stdout)]\nuse std::fs::read_to_string;\nuse std::path::Path;\nuse tracing::{debug, warn};\n\npub fn launch_history(\n    run_cache_weeks: Option<u8>,\n    config_file: &Path,\n    data_dir: &Path,\n    verbose: u8,\n) {\n    let run_cache_weeks = run_cache_weeks.unwrap_or_else(|| {\n        config_lib::load_and_migrate_config(config_file, true)\n            .ok()\n            .and_then(|c| {\n                c.windows.and_then(|w| {\n                    w.overview\n                        .map(|o| o.launcher)\n                        .and_then(|l| l.plugins.applications.as_ref().map(|a| a.run_cache_weeks))\n                })\n            })\n            .unwrap_or_else(|| {\n                warn!(\"Failed to get run cache weeks from config, falling back to 8 weeks\");\n                8\n            })\n    });\n    debug!(\"showing history for the last {run_cache_weeks} weeks\");\n\n    let runs = launcher_lib::get_applications_stored_runs(run_cache_weeks, data_dir);\n    if runs.is_empty() {\n        println!(\"No runs found\");\n        return;\n    }\n\n    let mut sorted = runs.into_iter().collect::<Vec<_>>();\n    sorted.sort_by(|a, b| b.1.cmp(&a.1));\n    for (path, run) in sorted {\n        // ignore the ini parser for this, just read the file and find, is faster\n        if let Ok(content) = read_to_string(&path) {\n            if let Some(name_line) = content.lines().find(|l| l.starts_with(\"Name=\")) {\n                let name = name_line.trim_start_matches(\"Name=\");\n                // check if verbosity is set, if so, print the name\n                #[allow(clippy::comparison_chain)]\n                if verbose > 0 {\n                    println!(\"{name}: ({run}) {}\", path.display());\n                } else if verbose == 0 {\n                    println!(\"{name}: ({run})\");\n                }\n            } else {\n                println!(\"?{}?: ({run})\", path.display());\n            }\n        } else {\n            println!(\"?{}?: ({run})\", path.display());\n        }\n    }\n}\n"
  },
  {
    "path": "src/debug.rs",
    "content": "#![allow(clippy::print_stderr, clippy::print_stdout)]\n\nuse crate::util;\nuse anyhow::Context;\nuse core_lib::default;\nuse core_lib::ini::IniFile;\nuse std::fs;\nuse std::path::Path;\nuse tracing::{debug, warn};\n\npub fn check_class(class: Option<String>) -> anyhow::Result<()> {\n    util::init_gtk();\n    util::check_themes();\n    util::reload_desktop_data().context(\"Failed to reload desktop data\")?;\n    util::reload_icons(false);\n    windows_lib::reload_class_to_icon_map().context(\"Failed to reload class to icon map\")?;\n    debug!(\"prepared desktop files and icon map\");\n\n    if let Some(class) = class {\n        println!(\"searching for {class}\");\n        check_icon(&class);\n    } else {\n        println!(\"no class provided, iterating over all clients\");\n        for client in exec_lib::get_clients() {\n            let class = client.class;\n            debug!(\"checking {class}\");\n            check_icon(&class);\n        }\n    }\n    Ok(())\n}\n\nfn check_icon(class: &str) {\n    let in_theme = default::theme_has_icon_name(class);\n    println!(\n        \"Icon ({class}) {} in theme (first choice)\",\n        if in_theme { \"is\" } else { \"is not\" }\n    );\n    let icon = windows_lib::get_icon_name_by_name_from_desktop_files(class);\n    println!(\n        \"Icon ({class}) {} in desktop files (second choice) {}\",\n        if icon.is_some() { \"is\" } else { \"is not\" },\n        if let Some(icon) = icon {\n            format!(\"{:?} [icon: {}]\", icon.2, icon.0.display())\n        } else {\n            String::new()\n        }\n    );\n}\n\npub fn list_icons() -> anyhow::Result<()> {\n    util::init_gtk();\n    util::check_themes();\n    util::reload_icons(false);\n    let icons = default::get_all_icons().context(\"Failed to get icons\")?;\n    for icon in icons.iter() {\n        println!(\"{icon}\");\n    }\n    drop(icons);\n    Ok(())\n}\n\npub fn list_desktop_files() {\n    let desktop_files = core_lib::util::collect_desktop_files();\n    for file in desktop_files {\n        let Ok(content) = fs::read_to_string(file.path()) else {\n            eprintln!(\"Failed to read desktop file: {}\", file.path().display());\n            continue;\n        };\n        let ini = IniFile::from_str(&content);\n        println!(\n            \"{}: {} [Type={}] [Terminal={}] [NoDisplay={}]\",\n            file.path().display(),\n            ini.get_section(\"Desktop Entry\")\n                .and_then(|s| s.get_first(\"Name\"))\n                .unwrap_or_default(),\n            ini.get_section(\"Desktop Entry\")\n                .and_then(|s| s.get_first(\"Type\"))\n                .unwrap_or_default(),\n            ini.get_section(\"Desktop Entry\")\n                .and_then(|s| s.get_first(\"Terminal\"))\n                .unwrap_or_default(),\n            ini.get_section(\"Desktop Entry\")\n                .and_then(|s| s.get_first(\"NoDisplay\"))\n                .unwrap_or_default(),\n        );\n    }\n}\n\npub fn search(text: &str, all: bool, config_file: &Path, data_dir: &Path) {\n    let (plugins, max_items) = config_lib::load_and_migrate_config(config_file, true)\n        .ok()\n        .and_then(|c| c.windows)\n        .and_then(|w| w.overview)\n        .map_or_else(\n            || {\n                warn!(\"Failed to get plugins from config, falling back to default\");\n                (config_lib::Launcher::default().plugins, 5)\n            },\n            |o| (o.launcher.plugins, o.launcher.max_items),\n        );\n    launcher_lib::debug::get_matches(&plugins, text, all, max_items, data_dir);\n}\n\npub fn info(\n    data_dir: &Path,\n    cache_dir: &Path,\n    css_file: &Path,\n    config_file: &Path,\n    system_data_dir: &Path,\n) {\n    println!(\"config version: {}\", config_lib::CURRENT_CONFIG_VERSION);\n\n    println!(\"css_file: {}\", css_file.display());\n    println!(\"config_file: {}\", config_file.display());\n    println!(\"data_dir: {}\", data_dir.display());\n    println!(\"cache_dir: {}\", cache_dir.display());\n    println!(\"system_data_dir: {}\", system_data_dir.display());\n\n    let dirs = [\n        (\"data_dir\", data_dir),\n        (\"cache_dir\", cache_dir),\n        (\"system_data_dir\", system_data_dir),\n    ];\n\n    for (name, path) in dirs {\n        if path.exists() && path.is_dir() {\n            let (folders, files) = count_dir(path);\n            println!(\"{name}: {folders} folders, {files} files (recursive)\");\n        } else {\n            println!(\n                \"{name}: not a directory or does not exist: {}\",\n                path.display()\n            );\n        }\n    }\n}\n\nfn count_dir(path: &Path) -> (usize, usize) {\n    let mut folders = 0usize;\n    let mut files = 0usize;\n    let mut stack = vec![path.to_path_buf()];\n    while let Some(p) = stack.pop() {\n        match fs::read_dir(&p) {\n            Ok(rd) => {\n                for entry in rd.flatten() {\n                    let path = entry.path();\n                    match entry.file_type() {\n                        Ok(ft) if ft.is_dir() => {\n                            folders += 1;\n                            stack.push(path);\n                        }\n                        Ok(ft) if ft.is_file() => files += 1,\n                        _ => {}\n                    }\n                }\n            }\n            Err(_) => {}\n        }\n    }\n    (folders, files)\n}\n"
  },
  {
    "path": "src/default_apps.rs",
    "content": "#![allow(clippy::print_stderr, clippy::print_stdout)]\n\nuse crate::util;\nuse anyhow::{Context, bail};\nuse core_lib::default::get_all_mime_files;\nuse core_lib::ini::IniFile;\nuse core_lib::path::get_config_home;\nuse std::collections::HashMap;\nuse std::fs::{read_to_string, write};\nuse tracing::{debug, warn};\n\npub fn get(mime: &str) -> anyhow::Result<()> {\n    util::reload_desktop_data().context(\"Failed to reload desktop data\")?;\n\n    let mut mimes = vec![];\n    for (file, ini) in get_all_mime_files()\n        .context(\"unable to get all mimefiles\")?\n        .iter()\n    {\n        let default = ini.get_section(\"Default Applications\").and_then(|section| {\n            section.get_first(mime).or_else(|| {\n                ini.get_section(\"Added Associations\")\n                    .and_then(|section| section.get_first(mime))\n            })\n        });\n        if let Some(default) = default {\n            mimes.push((\n                default.to_string(),\n                file.path().to_string_lossy().to_string(),\n            ));\n        }\n    }\n    if mimes.is_empty() {\n        println!(\"No default application found for {mime}\");\n    } else {\n        for (value, path) in mimes {\n            println!(\"{mime}: {value} [{path}]\");\n        }\n    }\n    Ok(())\n}\n\npub fn set_default(mime: &str, value: &str) -> anyhow::Result<()> {\n    let desktop_files = core_lib::util::collect_desktop_files();\n\n    // check if valid desktop file\n    let file = desktop_files.iter().find(|f| f.file_name() == value);\n    match file {\n        Some(file) => println!(\n            \"Desktop file {} at {}\",\n            file.file_name().display(),\n            file.path().display()\n        ),\n        None => bail!(\"Invalid desktop file: {value}\"),\n    }\n\n    let file = get_config_home().join(\"mimeapps.list\");\n    let str = if file.exists() {\n        read_to_string(&file).with_context(|| format!(\"Failed to read file {}\", file.display()))?\n    } else {\n        String::new()\n    };\n    let mut ini = IniFile::from_str(&str);\n    let section = ini.section_entry(\"Default Applications\").or_default();\n    section.set_items(mime, vec![value]);\n\n    let str = ini.format();\n    write(&file, str).with_context(|| format!(\"Failed to write file {}\", file.display()))?;\n\n    println!(\"added {mime}: {value} to {}\", file.display());\n    Ok(())\n}\n\npub fn add_association(mime: &str, value: &str) -> anyhow::Result<()> {\n    let desktop_files = core_lib::util::collect_desktop_files();\n\n    // check if valid desktop file\n    let file = desktop_files.iter().find(|f| f.file_name() == value);\n    match file {\n        Some(file) => println!(\n            \"Desktop file {} at {}\",\n            file.file_name().display(),\n            file.path().display()\n        ),\n        None => bail!(\"Invalid desktop file: {value}\"),\n    }\n\n    let file = get_config_home().join(\"mimeapps.list\");\n    let str = if file.exists() {\n        read_to_string(&file).with_context(|| format!(\"Failed to read file {}\", file.display()))?\n    } else {\n        String::new()\n    };\n    let mut ini = IniFile::from_str(&str);\n    let section = ini.section_entry(\"Added Associations\").or_default();\n    section.insert_item_at_front(mime, value);\n\n    let str = ini.format();\n    write(&file, str).with_context(|| format!(\"Failed to write file {}\", file.display()))?;\n\n    println!(\"added {mime}: {value} to {}\", file.display());\n    Ok(())\n}\n\nconst USED_MIME_TYPES: &[&str] = &[\"x-scheme-handler/https\", \"inode/directory\"];\n\npub fn list(all: bool) {\n    let mime_files = core_lib::util::collect_mime_files();\n\n    let mut mimes = HashMap::new();\n    for file in mime_files {\n        if let Ok(str) = read_to_string(file.path()) {\n            let ini = IniFile::from_str(&str);\n            debug!(\"mimeapps.list: {}\", file.path().display());\n            if let Some(section) = ini.get_section(\"Default Applications\") {\n                for (mime, values) in section {\n                    let mut values = values.iter().map(ToString::to_string).collect::<Vec<_>>();\n                    mimes\n                        .entry(mime.to_string())\n                        .or_insert((vec![], vec![]))\n                        .0\n                        .append(&mut values);\n                }\n            }\n            if let Some(section) = ini.get_section(\"Added Associations\") {\n                for (mime, values) in section {\n                    let mut values = values.iter().map(ToString::to_string).collect::<Vec<_>>();\n                    mimes\n                        .entry(mime.to_string())\n                        .or_insert((vec![], vec![]))\n                        .1\n                        .append(&mut values);\n                }\n            }\n        } else {\n            warn!(\"Failed to read file: {:?}\", file.path().display());\n        }\n    }\n\n    if all {\n        let mut mimes = mimes.into_iter().collect::<Vec<_>>();\n        mimes.sort_by(|(a, _), (b, _)| a.cmp(b));\n        for (mime, (defaults, added)) in mimes {\n            println!(\"{mime}: defaults: {defaults:?}, added: {added:?}\");\n        }\n    } else {\n        for mime in USED_MIME_TYPES {\n            if let Some((defaults, added)) = mimes.get(*mime) {\n                println!(\"{mime}: defaults: {defaults:?}, added: {added:?}\");\n            } else {\n                println!(\"{mime}: <not set>\");\n            }\n        }\n    }\n}\n\npub fn check() {\n    let mime_files = core_lib::util::collect_mime_files();\n    let desktop_files = core_lib::util::collect_desktop_files();\n\n    let mut mime_files_map = HashMap::new();\n    for file in mime_files {\n        let mut default_mimes = HashMap::new();\n        let mut added_mimes = HashMap::new();\n        if let Ok(str) = read_to_string(file.path()) {\n            let ini = IniFile::from_str(&str);\n            if let Some(section) = ini.get_section(\"Default Applications\") {\n                debug!(\n                    \"mimeapps.list: {} Default Applications\",\n                    file.path().display()\n                );\n                for (mime, values) in section {\n                    if default_mimes.contains_key(mime) {\n                        warn!(\"{mime} already exists\");\n                    }\n                    for value in values {\n                        default_mimes.insert(mime.to_string(), (*value).to_string());\n                    }\n                }\n            }\n            if let Some(section) = ini.get_section(\"Added Associations\") {\n                debug!(\n                    \"mimeapps.list: {} Added Associations\",\n                    file.path().display()\n                );\n                for (mime, values) in section {\n                    if added_mimes.contains_key(mime) {\n                        warn!(\"{mime} already exists\");\n                    }\n                    for value in values {\n                        added_mimes.insert(mime.to_string(), (*value).to_string());\n                    }\n                }\n            }\n        } else {\n            warn!(\"Failed to read file: {}\", file.path().display());\n        }\n        mime_files_map.insert(file.path(), (default_mimes, added_mimes));\n    }\n\n    for (file, (default_mimes, added_mimes)) in mime_files_map {\n        println!(\"{}\", file.display());\n        let mut vec = default_mimes.iter().collect::<Vec<_>>();\n        vec.sort_by_key(|&(mime, _)| mime.clone());\n        for (mime, value) in vec {\n            if desktop_files.iter().any(|d| *d.file_name() == **value) {\n                debug!(\"default {mime} in has desktop file value: {value}\");\n            } else {\n                println!(\n                    \"default {mime} in has desktop file value: {value}, but this desktop-file does not exist!\",\n                );\n            }\n        }\n        let mut vec = added_mimes.iter().collect::<Vec<_>>();\n        vec.sort_by_key(|&(mime, _)| mime.clone());\n        for (mime, value) in vec {\n            if desktop_files.iter().any(|d| *d.file_name() == **value) {\n                debug!(\"added {mime} in has desktop file value: {value}\");\n            } else {\n                println!(\n                    \"added {mime} in has desktop file value: {value}, but this desktop-file does not exist!\",\n                );\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/default_styles.css",
    "content": "/* no opacity */\n* {\n    opacity: unset;\n}\n\n/* buttons need to be transparent as they wrap everything clickable */\nbutton {\n    background: none;\n    padding: 0;\n    margin: 0;\n    border: none;\n}\n\nframe {\n    border: none;\n}\n\nimage {\n    border: none;\n}\n\n/* no background and no hover effects by default */\nlist {\n    background: transparent;\n}\n\n/* no background and no hover effects by default */\nrow {\n    background: transparent;\n}\n\nentry {\n    background: transparent;\n    color: var(--text-color, rgba(245, 245, 245, 1));\n}\n\nlabel {\n    padding-top: 2px;\n    color: var(--text-color, rgba(245, 245, 245, 1));\n}\n\n/* fix size for icons in launcher */\n.large-icons {\n    -gtk-icon-size: 32px;\n}\n\n/* some fix for the workspaces? */\nflowboxchild {\n    padding: 2px;\n}\n\n.monochrome {\n    filter: grayscale(100%);\n}\n\n.underline {\n    text-decoration-line: underline;\n    text-decoration-style: wavy;\n}\n\n.text-grayed {\n    color: color-mix(in srgb, currentColor 40%, #464444 60%);\n}\n\ntooltip {\n    font-size: 1rem;\n    padding: 5px;\n    background: var(--bg-color, rgba(20, 20, 20, 0.9));\n    border-radius: var(--border-radius, 12px);\n    border: 2px dashed var(--border-color, rgba(90, 90, 120, 0.4));\n}\n\n/* window text color */\n.window {\n    color: var(--text-color, rgba(245, 245, 245, 1));\n    /* padding for the window */\n    padding: 18px;\n}\n"
  },
  {
    "path": "src/explain.rs",
    "content": "use config_lib::{explain, load_and_migrate_config};\nuse core_lib::util::daemon_running;\nuse std::path::Path;\n\n#[allow(clippy::print_stderr, clippy::print_stdout)]\npub fn explain_config(config_file: &Path, add_how_to_explain_again: bool) {\n    let config = match load_and_migrate_config(config_file, true) {\n        Ok(config) => config,\n        Err(err) => {\n            eprintln!(\n                \"\\x1b[1m\\x1b[31mConfig is invalid ({}):\\x1b[0m {err:?}\\n\",\n                config_file.display()\n            );\n            return;\n        }\n    };\n    let info = explain(&config, Some(config_file), true);\n    println!(\"{info}\");\n\n    if daemon_running() {\n        println!(\"Daemon \\x1b[32mrunning\\x1b[0m\");\n    } else {\n        eprintln!(\n            \"Daemon \\x1b[31mnot running\\x1b[0m, start it with `hyprshell run` or `systemctl --user enable --now hyprshell`\"\n        );\n    }\n\n    if add_how_to_explain_again {\n        println!(\"\\nTo explain the config again, run `hyprshell explain`\\n\");\n    }\n}\n"
  },
  {
    "path": "src/keybinds.rs",
    "content": "use anyhow::Context;\nuse config_lib::Config;\nuse core_lib::{WarnWithDetails, notify_warn};\nuse exec_lib::binds::{apply_exec_bind, apply_layerrules};\nuse std::env;\nuse tracing::{debug_span, info, warn};\n\npub fn configure_wm(config: &Config, hyprland_version: &semver::Version) -> anyhow::Result<()> {\n    let _span = debug_span!(\"create_binds\").entered();\n\n    if env::var_os(\"HYPRSHELL_NO_USE_PLUGIN\").is_none() {\n        if let Err(err) = plugin(config, hyprland_version) {\n            notify_warn(\n                \"Unable to load hyprland plugin, restart hyprland if you updated, else please create a issue on github including the error.\",\n            );\n            warn!(\"Failed to load hyprland plugin: {err:?}\");\n            info!(\"Falling back to default keybinds\");\n            apply_binds(config)?;\n        }\n    } else {\n        apply_binds(config)?;\n    }\n\n    apply_layerrules().warn_details(\"Failed to apply layerrules\");\n    Ok(())\n}\n\nfn plugin(config: &Config, hyprland_version: &semver::Version) -> anyhow::Result<()> {\n    if let Some(windows) = &config.windows {\n        let switch = windows.switch.as_ref().map(|s| (s.modifier, s.key.clone()));\n        let overview = windows\n            .overview\n            .as_ref()\n            .map(|o| (o.modifier, o.key.clone()));\n        exec_lib::plugin::load_plugin(switch, overview, hyprland_version)\n            .context(\"Failed to load hyprland plugin\")?;\n    }\n    Ok(())\n}\n\nfn apply_binds(config: &Config) -> anyhow::Result<()> {\n    if let Some(windows) = &config.windows {\n        for bind in windows_lib::generate_open_keybinds(windows) {\n            apply_exec_bind(&bind).context(\"Failed to apply open keybinds for windows\")?;\n        }\n    }\n    Ok(())\n}\n"
  },
  {
    "path": "src/main.rs",
    "content": "use crate::explain::explain_config;\nuse anyhow::{Context, bail};\nuse clap::Parser;\nuse core_lib::WarnWithDetails;\nuse core_lib::path::{\n    get_default_cache_dir, get_default_config_file, get_default_css_file, get_default_data_dir,\n};\nuse core_lib::util::daemon_running;\nuse std::{env, fs};\nuse tracing_subscriber::EnvFilter;\n\nmod cli;\nmod data;\nmod keybinds;\nmod receive_handle;\nmod socket;\nmod start;\nmod util;\n\nmod completions;\n#[cfg(feature = \"debug_command\")]\nmod debug;\n#[cfg(feature = \"debug_command\")]\nmod default_apps;\nmod explain;\n\n#[allow(clippy::too_many_lines)]\nfn main() -> anyhow::Result<()> {\n    let _ = format!(\"{}\", 2);\n    let cli = cli::App::parse();\n    let opts = cli.global_opts.clone();\n\n    let level = if opts.quiet {\n        \"off\"\n    } else {\n        match opts.verbose {\n            0 => \"info\",\n            1 => \"debug\",\n            2.. => \"trace\",\n        }\n    };\n    let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| {\n        format!(\n            \"hyprshell={level},config_lib={level},core_lib={level},exec_lib={level},launcher_lib={level},windows_lib={level},hyprland_plugin={level},hyprshell_clipboard_lib={level},hyprshell_config_edit_lib={level}\"\n        ).into()}\n    );\n    let subscriber = tracing_subscriber::fmt()\n        .with_timer(tracing_subscriber::fmt::time::uptime())\n        .with_target(\n            env::var(\"HYPRSHELL_LOG_MODULE_PATH\")\n                .ok()\n                .and_then(|s| s.parse().ok())\n                .unwrap_or(false),\n        )\n        .with_env_filter(filter)\n        .finish();\n    tracing::subscriber::set_global_default(subscriber)\n        .unwrap_or_else(|e| tracing::warn!(\"Unable to initialize logging: {e}\"));\n\n    check_features();\n    check_env();\n\n    let data_dir = cli\n        .global_opts\n        .data_dir\n        .unwrap_or_else(get_default_data_dir);\n    let cache_dir = cli\n        .global_opts\n        .cache_dir\n        .unwrap_or_else(get_default_cache_dir);\n    let css_file = cli\n        .global_opts\n        .css_file\n        .unwrap_or_else(get_default_css_file);\n    let config_file = cli\n        .global_opts\n        .config_file\n        .unwrap_or_else(get_default_config_file);\n    #[cfg(any(feature = \"gui_settings_editor\", feature = \"debug_command\"))]\n    let system_data_dir = cli\n        .global_opts\n        .system_data_dir\n        .unwrap_or_else(core_lib::path::get_default_system_data_dir);\n\n    match cli.command {\n        cli::Command::Run {} => {\n            let version = exec_lib::check_version()\n                .context(\"Unable to check hyprland version, continuing anyway\")\n                .unwrap_or_else(|_| semver::Version::new(0, 54, 0));\n            if daemon_running() {\n                bail!(\"Daemon already running\");\n            }\n            if env::var_os(\"HYPRSHELL_EXPERIMENTAL\").is_some_and(|v| v.eq(\"1\")) {\n                clipboard_lib::store::test_clipboard(data_dir, cache_dir);\n                return Ok(());\n            }\n\n            start::start(config_file, css_file, data_dir, cache_dir, &version)?;\n        }\n        cli::Command::Config { command } => match command {\n            cli::ConfigCommand::Edit {} => {\n                #[cfg(feature = \"gui_settings_editor\")]\n                config_edit_lib::start(config_file, css_file, system_data_dir, false);\n                #[cfg(not(feature = \"gui_settings_editor\"))]\n                core_lib::notify_warn(\n                    \"GUI settings editor not available, compile with `gui_settings_editor` feature\",\n                );\n            }\n            cli::ConfigCommand::Generate {} => {\n                #[cfg(feature = \"gui_settings_editor\")]\n                config_edit_lib::start(config_file, css_file, system_data_dir, true);\n                #[cfg(not(feature = \"gui_settings_editor\"))]\n                core_lib::notify_warn(\n                    \"GUI settings editor not available, compile with `gui_settings_editor` feature\",\n                );\n            }\n            cli::ConfigCommand::Explain {} => {\n                explain_config(&config_file, false);\n            }\n            cli::ConfigCommand::Check {} => {\n                if let Err(err) = config_lib::load_and_migrate_config(&config_file, true) {\n                    tracing::warn!(\"Failed to load config: {err:?}\");\n                    std::process::exit(1);\n                }\n            }\n            #[cfg(feature = \"ci_config_check\")]\n            cli::ConfigCommand::CheckIfDefault {} => {\n                let config = config_lib::load_and_migrate_config(&config_file, false)?;\n                let config_default = config_lib::Config::default();\n                if config == config_default {\n                    tracing::info!(\"Current config matches the default configuration\");\n                } else {\n                    tracing::warn!(\"Current config does not match the default configuration\");\n                    tracing::info!(\"Default config: {:#?}\", config_default);\n                    tracing::info!(\"Current config: {:#?}\", config);\n                    std::process::exit(1);\n                }\n            }\n            #[cfg(feature = \"ci_config_check\")]\n            cli::ConfigCommand::CheckIfFull {} => {\n                let config = config_lib::load_and_migrate_config(&config_file, false)?;\n                let config_all = config_lib::Config {\n                    windows: Some(config_lib::Windows {\n                        overview: Some(config_lib::Overview::default()),\n                        switch: Some(config_lib::Switch::default()),\n                        ..Default::default()\n                    }),\n                    ..Default::default()\n                };\n                if config == config_all {\n                    tracing::info!(\"Current config matches the default configuration\");\n                } else {\n                    tracing::warn!(\"Current config does not match the full configuration\");\n                    tracing::info!(\"All config: {:#?}\", config_all);\n                    tracing::info!(\"Current config: {:#?}\", config);\n                    std::process::exit(1);\n                }\n            }\n        },\n        #[cfg(feature = \"debug_command\")]\n        #[allow(clippy::print_stderr, clippy::print_stdout)]\n        cli::Command::Debug { command } => {\n            println!(\"run with -vv as args to see all logs\");\n            match command {\n                cli::DebugCommand::ListIcons => {\n                    debug::list_icons().warn_details(\"Failed to list icons\");\n                }\n                cli::DebugCommand::ListDesktopFiles => {\n                    debug::list_desktop_files();\n                }\n                cli::DebugCommand::CheckClass { class } => {\n                    debug::check_class(class).warn_details(\"Failed to check class\");\n                }\n                cli::DebugCommand::Search { text, all } => {\n                    debug::search(&text, all, &config_file, &data_dir);\n                }\n                cli::DebugCommand::DefaultApplications { command } => match command {\n                    cli::DefaultApplicationsCommand::Get { mime } => {\n                        default_apps::get(&mime).context(\"unable to get default app\")?;\n                    }\n                    cli::DefaultApplicationsCommand::Set { mime, value } => {\n                        default_apps::set_default(&mime, &value)\n                            .context(\"unable to set default app\")?;\n                    }\n                    cli::DefaultApplicationsCommand::Add { mime, value } => {\n                        default_apps::add_association(&mime, &value)\n                            .context(\"unable to add association\")?;\n                    }\n                    cli::DefaultApplicationsCommand::List { all } => {\n                        default_apps::list(all);\n                    }\n                    cli::DefaultApplicationsCommand::Check {} => {\n                        default_apps::check();\n                    }\n                },\n                cli::DebugCommand::Info {} => {\n                    debug::info(\n                        &data_dir,\n                        &cache_dir,\n                        &css_file,\n                        &config_file,\n                        &system_data_dir,\n                    );\n                }\n            }\n        }\n        cli::Command::Data { command } => match command {\n            cli::DataCommand::LaunchHistory { run_cache_weeks } => {\n                data::launch_history(run_cache_weeks, &config_file, &data_dir, opts.verbose);\n            }\n        },\n        cli::Command::Completions {\n            shell,\n            base_path,\n            delete,\n        } => {\n            if let Some(shell) = shell {\n                completions::generate(&shell, base_path, delete)\n                    .context(\"Failed to generate completions\")?;\n            } else {\n                for shell in [\"bash\", \"fish\", \"zsh\"] {\n                    completions::generate(shell, None, delete)\n                        .context(\"Failed to generate completions\")?;\n                }\n            }\n        }\n        cli::Command::Socat { json } => core_lib::transfer::send_raw_to_socket(&json)\n            .context(\"Failed to send JSON to socket: is hyprshell running?\")?,\n    }\n    Ok(())\n}\n\nfn check_features() {\n    tracing::debug!(\n        \"FEATURES: json5_config: {}, gui_settings_editor: {}, debug_command: {}, launcher_calc: {}, clipboard_compress_lz4: {}, clipboard_compress_zstd: {}, clipboard_compress_brotli: {}, clipboard_encrypt_chacha20poly1305: {}, clipboard_encrypt_aes_gcm: {}\",\n        cfg!(feature = \"json5_config\"),\n        cfg!(feature = \"gui_settings_editor\"),\n        cfg!(feature = \"debug_command\"),\n        cfg!(feature = \"launcher_calc\"),\n        cfg!(feature = \"clipboard_compress_lz4\"),\n        cfg!(feature = \"clipboard_compress_zstd\"),\n        cfg!(feature = \"clipboard_compress_brotli\"),\n        cfg!(feature = \"clipboard_encrypt_chacha20poly1305\"),\n        cfg!(feature = \"clipboard_encrypt_aes_gcm\"),\n    );\n}\n\nfn check_env() {\n    tracing::debug!(\n        \"ENV: HYPRSHELL_NO_LISTENERS: {}, HYPRSHELL_NO_ALL_ICONS: {}, HYPRSHELL_RELOAD_TIMEOUT: {}, HYPRSHELL_LOG_MODULE_PATH: {}, HYPRSHELL_NO_USE_PLUGIN: {}, HYPRSHELL_EXPERIMENTAL: {}, HYPRSHELL_RUN_ACTIONS_IN_DEBUG: {}\",\n        env::var(\"HYPRSHELL_NO_LISTENERS\").unwrap_or_else(|_| \"-None-\".to_string()),\n        env::var(\"HYPRSHELL_NO_ALL_ICONS\").unwrap_or_else(|_| \"-None-\".to_string()),\n        env::var(\"HYPRSHELL_RELOAD_TIMEOUT\").unwrap_or_else(|_| \"-None-\".to_string()),\n        env::var(\"HYPRSHELL_LOG_MODULE_PATH\").unwrap_or_else(|_| \"-None-\".to_string()),\n        env::var(\"HYPRSHELL_NO_USE_PLUGIN\").unwrap_or_else(|_| \"-None-\".to_string()),\n        env::var(\"HYPRSHELL_EXPERIMENTAL\").unwrap_or_else(|_| \"-None-\".to_string()),\n        env::var(\"HYPRSHELL_RUN_ACTIONS_IN_DEBUG\").unwrap_or_else(|_| \"-None-\".to_string()),\n    );\n    let os_name = fs::read_to_string(\"/etc/os-release\")\n        .ok()\n        .and_then(|content| {\n            content\n                .lines()\n                .find(|line| line.starts_with(\"NAME=\"))\n                .map(ToString::to_string)\n        })\n        .unwrap_or_else(|| \"NAME=Unknown\".to_string());\n\n    tracing::debug!(\n        \"OS: {}, ARCH: {}, {}\",\n        env::consts::OS,\n        env::consts::ARCH,\n        os_name,\n    );\n}\n"
  },
  {
    "path": "src/receive_handle.rs",
    "content": "use crate::start::Globals;\nuse crate::util;\nuse async_channel::{Receiver, Sender};\nuse core_lib::WarnWithDetails;\nuse core_lib::transfer::{\n    CloseOverviewConfig, Direction, OpenSwitch, SwitchOverviewConfig, SwitchSwitchConfig,\n    TransferType,\n};\nuse relm4::adw::gtk::prelude::{ApplicationExt, EntryExt};\nuse relm4::adw::gtk::{gio, glib};\nuse tracing::{debug, trace, warn};\n\n#[allow(clippy::future_not_send)]\npub async fn event_handler(\n    mut globals: Globals,\n    event_receiver: Receiver<TransferType>,\n    event_sender: Sender<TransferType>,\n) {\n    let _span = tracing::span!(tracing::Level::TRACE, \"event_handler\").entered();\n    loop {\n        if let Ok(transfer) = event_receiver.recv().await {\n            let close_socket = matches!(transfer, TransferType::Restart);\n            trace!(\"handling event: {transfer:?}\");\n            match transfer {\n                TransferType::OpenOverview => open_overview(&mut globals, &event_sender),\n                TransferType::OpenSwitch(config) => open_switch(&mut globals, &config),\n                TransferType::SwitchOverview(config) => switch_overview(&mut globals, &config),\n                TransferType::SwitchSwitch(config) => switch_switch(&mut globals, &config),\n                TransferType::Exit => exit(&mut globals),\n                TransferType::Type(text) => r#type(&mut globals, &text, &event_sender),\n                TransferType::CloseOverview(config) => close_overview(&mut globals, config),\n                TransferType::CloseSwitch => close_switch(&mut globals),\n                TransferType::Restart => restart(&globals),\n            }\n            if close_socket {\n                return;\n            }\n        }\n    }\n}\n\nfn r#type(global: &mut Globals, text: &str, event_sender: &Sender<TransferType>) {\n    if let Some(windows) = &mut global.windows\n        && let Some((_overview, launcher)) = &mut windows.overview\n    {\n        launcher_lib::update_launcher(launcher, text, event_sender);\n    }\n}\n\nfn open_overview(global: &mut Globals, event_sender: &Sender<TransferType>) {\n    if let Some(windows) = &mut global.windows {\n        if let Some((overview, launcher)) = &mut windows.overview {\n            if !windows_lib::overview_already_open(overview)\n                && !&windows\n                    .switch\n                    .as_ref()\n                    .is_some_and(windows_lib::switch_already_open)\n            {\n                trace!(\"Opening overview\");\n                windows_lib::open_overview(overview, event_sender)\n                    .warn_details(\"Failed to open overview window\");\n                trace!(\"Opening launcher\");\n                launcher_lib::open_launcher(launcher);\n                trace!(\"Updating Launcher\");\n                launcher_lib::update_launcher(launcher, \"\", event_sender);\n\n                // update desktop data in background\n                trace!(\"Reloading desktop data\");\n                gio::spawn_blocking(util::reload_desktop_data);\n            } else {\n                debug!(\"Overview or Switch already open, closing\");\n                windows_lib::close_overview(overview, None);\n                launcher_lib::close_launcher_by_char(launcher, None); // this will never open a program and need the default terminal\n            }\n        } else {\n            warn!(\"Window overview not active\");\n        }\n    } else {\n        warn!(\"Windows not active\");\n    }\n}\n\nfn open_switch(global: &mut Globals, config: &OpenSwitch) {\n    if let Some(windows) = &mut global.windows {\n        if let Some(switch) = &mut windows.switch {\n            if !windows_lib::switch_already_open(switch)\n                && !&windows\n                    .overview\n                    .as_ref()\n                    .is_some_and(|(o, _)| windows_lib::overview_already_open(o))\n            {\n                windows_lib::open_switch(switch, config)\n                    .warn_details(\"Failed to open switch window\");\n            } else {\n                debug!(\"Switch or Overview already open, converting to SwitchSwitch\");\n                windows_lib::update_switch(\n                    switch,\n                    &SwitchSwitchConfig {\n                        direction: if config.reverse {\n                            Direction::Left\n                        } else {\n                            Direction::Right\n                        },\n                    },\n                );\n            }\n        } else {\n            warn!(\"Window switch not active\");\n        }\n    } else {\n        warn!(\"Windows not active\");\n    }\n}\n\nfn switch_switch(global: &mut Globals, config: &SwitchSwitchConfig) {\n    if let Some(windows) = &mut global.windows {\n        if let Some(switch) = &mut windows.switch {\n            windows_lib::update_switch(switch, config);\n        } else {\n            warn!(\"Window switch not active\");\n        }\n    } else {\n        warn!(\"Windows not active\");\n    }\n}\n\nfn switch_overview(global: &mut Globals, config: &SwitchOverviewConfig) {\n    if let Some(windows) = &mut global.windows {\n        if let Some((overview, launcher)) = &mut windows.overview {\n            // don't switch selected window if launcher is active\n            let launch = launcher.entry.text_length() > 0;\n            if !launch {\n                windows_lib::update_overview(overview, config);\n            }\n        } else {\n            warn!(\"Window switch not active\");\n        }\n    } else {\n        warn!(\"Windows not active\");\n    }\n}\n\nfn exit(global: &mut Globals) {\n    if let Some(windows) = &mut global.windows {\n        if let Some((overview, launcher)) = &mut windows.overview {\n            windows_lib::close_overview(overview, None);\n            launcher_lib::close_launcher_by_char(launcher, None); // this will never open a program and need the default terminal\n        }\n        if let Some(switch) = &mut windows.switch {\n            windows_lib::close_switch(switch, false);\n        }\n    }\n}\n\nfn close_overview(global: &mut Globals, config: CloseOverviewConfig) {\n    if let Some(windows) = &mut global.windows\n        && let Some((overview, launcher)) = &mut windows.overview\n    {\n        if windows_lib::overview_already_hidden(overview) {\n            debug!(\"Overview is already closed\");\n            return;\n        }\n        match config {\n            // return (focus active)\n            CloseOverviewConfig::None => {\n                let launcher_empty = launcher.entry.text_length() == 0;\n                let other_active = overview.active != overview.initial_active;\n                let launcher_no_items = launcher.sorted_matches.is_empty();\n                if launcher_empty && other_active {\n                    // close overview, kill launcher\n                    windows_lib::close_overview(overview, Some(None));\n                    launcher_lib::close_launcher_by_char(launcher, None);\n                } else if launcher_no_items {\n                    debug!(\"Launcher is empty, not closing\");\n                } else {\n                    // kill overview, close launcher\n                    windows_lib::close_overview(overview, None);\n                    launcher_lib::close_launcher_by_char(launcher, Some('0'));\n                }\n            }\n            // clicked on launcher item\n            CloseOverviewConfig::LauncherClick(iden) => {\n                windows_lib::close_overview(overview, None);\n                launcher_lib::close_launcher_by_iden(launcher, &iden);\n            }\n            // typed a character in launcher\n            CloseOverviewConfig::LauncherPress(iden) => {\n                windows_lib::close_overview(overview, None);\n                launcher_lib::close_launcher_by_char(launcher, Some(iden));\n            }\n            // clicked on window\n            CloseOverviewConfig::Windows(iden) => {\n                windows_lib::close_overview(overview, Some(Some(iden)));\n                launcher_lib::close_launcher_by_char(launcher, None);\n            }\n        }\n    }\n}\n\nfn close_switch(global: &mut Globals) {\n    if let Some(windows) = &mut global.windows\n        && let Some(switch) = &mut windows.switch\n    {\n        if windows_lib::switch_already_hidden(switch) {\n            debug!(\"Switch is already closed\");\n            return;\n        }\n        windows_lib::close_switch(switch, true);\n    }\n}\n\nfn restart(global: &Globals) {\n    // TODO block some time after recreating windows\n    if let Some(windows) = &global.windows {\n        if let Some((overview, launcher)) = &windows.overview {\n            windows_lib::stop_overview(overview);\n            launcher_lib::stop_launcher(launcher);\n        }\n        if let Some(switch) = &windows.switch {\n            windows_lib::stop_switch(switch);\n        }\n    }\n    let app = global.app.clone();\n    glib::idle_add_local_once(move || {\n        app.quit();\n    });\n}\n"
  },
  {
    "path": "src/socket.rs",
    "content": "use anyhow::{Context, bail};\nuse async_channel::Sender;\nuse core_lib::transfer;\nuse core_lib::transfer::TransferType;\nuse core_lib::util::get_daemon_socket_path_buff;\nuse std::fs::remove_file;\nuse std::io::{BufRead, BufReader, Write};\nuse std::os::unix::net;\nuse std::os::unix::net::UnixStream;\nuse tracing::{debug_span, info, warn};\n\npub fn socket_handler(event_sender: &Sender<TransferType>) {\n    let _span = debug_span!(\"socket_handler\").entered();\n    let buf = get_daemon_socket_path_buff();\n    let path = buf.as_path();\n    let listener = {\n        // remove old PATH\n        if path.exists() {\n            remove_file(path).expect(\"Unable to remove old socket file\");\n        }\n        net::UnixListener::bind(path)\n            .unwrap_or_else(|_| panic!(\"Failed to bind to socket {}\", path.display()))\n    };\n    info!(\"Starting socket on {path:?}\");\n\n    loop {\n        let path = listener.accept();\n        match path {\n            Ok((conn, _)) => {\n                handle_client(conn, event_sender)\n                    .context(\"Failed to handle client\")\n                    .unwrap_or_else(|e| {\n                        warn!(\"Failed to handle connection {e:?}\");\n                    });\n            }\n            Err(e) => {\n                warn!(\"Failed to accept connection: {e:?}\");\n            }\n        }\n    }\n}\n\nfn handle_client(\n    mut stream: UnixStream,\n    event_sender: &Sender<TransferType>,\n) -> anyhow::Result<()> {\n    let _span = debug_span!(\"handle_client\").entered();\n    let mut buffer = vec![];\n    let mut reader = BufReader::new(&mut stream);\n    reader\n        .read_until(b'\\0', &mut buffer)\n        .context(\"Can't read data from socket\")?;\n    if buffer.is_empty() {\n        return Ok(());\n    }\n    match transfer::receive_from_buffer(buffer) {\n        Ok(transfer) => {\n            event_sender\n                .send_blocking(transfer)\n                .context(\"Failed to send transfer\")?;\n            let _ = stream\n                .write_all(b\"OK\")\n                .and_then(|()| stream.write_all(b\"\\0\"));\n        }\n        Err(err) => {\n            let _ = stream\n                .write_all(b\"ERR\")\n                .and_then(|()| stream.write_all(b\"\\0\"));\n            bail!(\"Invalid transfer received.\\n{err:?}\");\n        }\n    }\n\n    Ok(())\n}\n"
  },
  {
    "path": "src/start.rs",
    "content": "use crate::keybinds::configure_wm;\nuse crate::receive_handle::event_handler;\nuse crate::socket::socket_handler;\nuse crate::util;\nuse crate::util::check_new_version;\nuse anyhow::Context;\nuse async_channel::{Receiver, Sender};\nuse config_lib::Config;\nuse core_lib::listener::{\n    hyprshell_config_block, hyprshell_config_listener, hyprshell_css_listener,\n};\nuse core_lib::transfer::TransferType;\nuse core_lib::{WarnWithDetails, notify, notify_resident, notify_warn};\nuse exec_lib::listener::{hyprland_config_listener, monitor_listener};\nuse launcher_lib::{LauncherData, create_windows_overview_launcher_window};\nuse relm4::adw::gtk::gdk::Display;\nuse relm4::adw::gtk::prelude::*;\nuse relm4::adw::gtk::{\n    Application, CssProvider, STYLE_PROVIDER_PRIORITY_USER, glib,\n    style_context_add_provider_for_display,\n};\nuse std::any::Any;\nuse std::cell::RefCell;\nuse std::cmp::Ordering;\nuse std::path::{Path, PathBuf};\nuse std::rc::Rc;\nuse std::sync::{Mutex, OnceLock};\nuse std::time::Duration;\nuse std::{env, process, thread};\nuse tracing::{debug, debug_span, error, info, trace};\nuse windows_lib::{\n    WindowsOverviewData, WindowsSwitchData, create_windows_overview_window,\n    create_windows_switch_window,\n};\n\npub fn start(\n    config_file: PathBuf,\n    css_path: PathBuf,\n    data_dir: PathBuf,\n    cache_dir: PathBuf,\n    hyprland_version: &semver::Version,\n) -> anyhow::Result<()> {\n    let config_file = Rc::new(config_file);\n    let css_path = Rc::new(css_path);\n    let data_dir = Rc::new(data_dir);\n    let cache_dir = Rc::new(cache_dir);\n\n    util::preactivate().context(\"Failed to preactivate GTK and reload icons\")?;\n    exec_lib::reload_hyprland_config()\n        .context(\"Failed to reload hyprland config\")\n        .warn_details(\"unable to reload hyprland config\");\n\n    let (event_sender, event_receiver) = async_channel::unbounded();\n\n    if env::var_os(\"HYPRSHELL_NO_LISTENERS\").is_none() {\n        register_event_restarter(config_file.clone(), css_path.clone(), event_sender.clone());\n    }\n\n    let event_sender_2 = event_sender.clone();\n    thread::spawn(move || {\n        socket_handler(&event_sender_2);\n    });\n\n    let wayland_socket_index = env::var(\"WAYLAND_DISPLAY\")\n        .ok()\n        .and_then(|s| s.split('-').next_back()?.parse::<i32>().ok())\n        .unwrap_or(1);\n\n    let mut inc = 0;\n\n    info!(\"Starting gui loop\");\n    loop {\n        inc += 1;\n        let id = format!(\n            \"{}-{}-{}{}\",\n            core_lib::APPLICATION_ID,\n            wayland_socket_index,\n            inc,\n            if cfg!(debug_assertions) { \"-test\" } else { \"\" }\n        );\n        trace!(\"Application id: {}\", id);\n        let application = Application::builder().application_id(id).build();\n        debug!(\"Application created\");\n\n        let config_file = config_file.clone();\n        let css_path = css_path.clone();\n        let data_dir = data_dir.clone();\n        let cache_dir = cache_dir.clone();\n        let event_sender = event_sender.clone();\n        let event_receiver = event_receiver.clone();\n        let hyprland_version = hyprland_version.clone();\n        application.connect_activate(move |app| {\n            activate(\n                app,\n                &config_file,\n                &css_path,\n                &data_dir,\n                &cache_dir,\n                event_sender.clone(),\n                event_receiver.clone(),\n                &hyprland_version.clone(),\n            );\n        });\n        let exit = application.run_with_args::<String>(&[]);\n        debug!(\"Application exited with code {exit:?}\");\n    }\n}\n\npub struct Globals {\n    pub windows: Option<WindowsGlobal>,\n    pub app: Application,\n}\n\n#[derive(Debug, Default)]\npub struct WindowsGlobal {\n    pub overview: Option<(WindowsOverviewData, LauncherData)>,\n    pub switch: Option<WindowsSwitchData>,\n}\n\n#[allow(clippy::cognitive_complexity)]\n#[allow(clippy::too_many_arguments)]\nfn activate(\n    app: &Application,\n    config_file: &Path,\n    css_path: &Path,\n    data_dir: &Path,\n    cache_dir: &Path,\n    event_sender: Sender<TransferType>,\n    event_receiver: Receiver<TransferType>,\n    hyprland_version: &semver::Version,\n) {\n    let _span = debug_span!(\"activate\").entered();\n    apply_css(css_path).warn_details(\"Failed to apply CSS\");\n    exec_lib::set_follow_mouse_default().warn_details(\"Failed to set set_remain_focused default\");\n\n    match check_new_version(cache_dir) {\n        Err(err) => {\n            debug!(\"Unable to compare previous to current version.\\n{err:?}\");\n        }\n        Ok((Ordering::Greater, messages)) => {\n            notify(\n                &format!(\n                    \"Hyprshell was updated to a new version ({})\",\n                    env!(\"CARGO_PKG_VERSION\")\n                ),\n                Duration::from_secs(5),\n            );\n            thread::sleep(Duration::from_millis(500));\n            for info in messages {\n                notify_resident(&info, Duration::from_secs(10));\n            }\n        }\n        Ok((Ordering::Less, _)) => {\n            notify_warn(\n                \"Hyprshell was downgraded, downgrading config must be done manually if needed\",\n            );\n        }\n        Ok((Ordering::Equal, _)) => {\n            debug!(\"Hyprshell is up to date\");\n        }\n    }\n\n    let config = match config_lib::load_and_migrate_config(config_file, true) {\n        Ok(config) => config,\n        Err(err) => {\n            notify_warn(&format!(\"Failed to load config: {err:?}\"));\n            if let Err(err) = hyprshell_config_block(config_file) {\n                error!(\"Failed to block config: {err:?}\",);\n                process::exit(1);\n            }\n            info!(\"Trying to rerun application after config reload\");\n            return; // return needed to exit the application\n        }\n    };\n\n    // TODO remove in future if more is available\n    if config.windows.is_none()\n        || matches!(&config.windows, Some(windows) if windows.overview.is_none() && windows.switch.is_none())\n    {\n        notify_warn(\"Nothing is enabled in the config\");\n        if let Err(err) = hyprshell_config_block(config_file) {\n            error!(\"Failed to block config: {err:?}\",);\n            process::exit(1);\n        }\n        info!(\"Trying to rerun application after config reload\");\n        return; // return needed to exit the application\n    }\n\n    if let Err(err) = configure_wm(&config, hyprland_version) {\n        notify_warn(&format!(\"Failed to configure wm: {err:?}\"));\n        if let Err(err) = hyprshell_config_block(config_file) {\n            error!(\"Failed to block config: {err:?}\");\n            process::exit(1);\n        }\n        info!(\"Trying to rerun application after config reload\");\n        return; // return needed to exit the application\n    }\n\n    let globals = match create_windows(app, &config, data_dir, event_sender.clone()) {\n        Ok(data) => data,\n        Err(err) => {\n            notify_warn(&format!(\"Failed to create windows: {err:?}\"));\n            if let Err(err) = hyprshell_config_block(config_file) {\n                error!(\"Failed to block config: {err:?}\");\n                process::exit(1);\n            }\n            info!(\"Trying to rerun application after config reload\");\n            return; // return needed to exit the application\n        }\n    };\n\n    glib::spawn_future_local(async move {\n        event_handler(globals, event_receiver, event_sender).await;\n        info!(\"Application exited, restarting\");\n    });\n\n    info!(\"Application initialized\");\n}\n\nfn create_windows(\n    app: &Application,\n    config: &Config,\n    data_dir: &Path,\n    event_sender: Sender<TransferType>,\n) -> anyhow::Result<Globals> {\n    let mut global = Globals {\n        windows: None,\n        app: app.clone(),\n    };\n    if let Some(windows) = &config.windows {\n        let mut windows_data = WindowsGlobal::default();\n        if let Some(overview) = &windows.overview {\n            let overview_data = create_windows_overview_window(app, overview, windows)\n                .context(\"failed to create overview window\")?;\n            let launcher_data = create_windows_overview_launcher_window(\n                app,\n                &overview.launcher,\n                data_dir,\n                &event_sender,\n            )\n            .context(\"failed to create launcher window\")?;\n            windows_data.overview = Some((overview_data, launcher_data));\n        } else {\n            debug!(\"Windows overview disabled\");\n        }\n        if let Some(switch) = &windows.switch {\n            let switch_data = create_windows_switch_window(app, switch, windows, event_sender)\n                .context(\"failed to create switch window\")?;\n            windows_data.switch = Some(switch_data);\n        } else {\n            debug!(\"Windows switch disabled\");\n        }\n        global.windows = Some(windows_data);\n    } else {\n        debug!(\"Windows disabled\");\n    }\n    Ok(global)\n}\n\nfn apply_css(custom_css: &Path) -> anyhow::Result<()> {\n    let provider_app = CssProvider::new();\n\n    provider_app.load_from_string(include_str!(\"default_styles.css\"));\n    style_context_add_provider_for_display(\n        &Display::default().context(\"Could not connect to a display.\")?,\n        &provider_app,\n        STYLE_PROVIDER_PRIORITY_USER,\n    );\n\n    windows_lib::get_css()?;\n    launcher_lib::get_css()?;\n\n    if custom_css.exists() {\n        debug!(\"Loading custom css file {custom_css:?}\");\n        let provider_user = CssProvider::new();\n        provider_user.load_from_path(custom_css);\n        style_context_add_provider_for_display(\n            &Display::default().context(\"Could not connect to a display.\")?,\n            &provider_user,\n            STYLE_PROVIDER_PRIORITY_USER,\n        );\n    } else {\n        debug!(\"Custom css file {custom_css:?} does not exist\");\n    }\n    Ok(())\n}\n\npub fn register_event_restarter(\n    config_file: Rc<PathBuf>,\n    css_path: Rc<PathBuf>,\n    event_sender: Sender<TransferType>,\n) {\n    // delay for 1.5 seconds to allow the config to be reloaded before listening for reload\n    let delay = env::var(\"HYPRSHELL_RELOAD_TIMEOUT\")\n        .ok()\n        .and_then(|s| s.parse().ok())\n        .unwrap_or(1500);\n    let (restart_sender, restart_receiver) = async_channel::unbounded();\n    glib::timeout_add_local_once(Duration::from_millis(delay), move || {\n        setup_restart_listener(&config_file, &css_path, &restart_sender);\n    });\n\n    // State to track the current debounce timer\n    let debounce_timer = Rc::new(RefCell::new(None::<glib::SourceId>));\n    glib::spawn_future_local(async move {\n        loop {\n            let cause = restart_receiver.recv().await.unwrap_or_default();\n            debug!(\"Received restart request ({cause}), starting debounce timer\");\n\n            // Cancel any existing timer\n            if let Some(timer_id) = debounce_timer.borrow_mut().take() {\n                timer_id.remove();\n                trace!(\"Cancelled previous debounce timer\");\n            }\n\n            // Create new debounce timer\n            let event_sender_clone = event_sender.clone();\n            let debounce_timer_clone = debounce_timer.clone();\n            let timer_id = glib::timeout_add_local_once(Duration::from_millis(delay), move || {\n                trace!(\"Debounce timer expired, triggering restart ({cause})\");\n\n                // Clear the timer reference since it's about to complete\n                *debounce_timer_clone.borrow_mut() = None;\n\n                // Send the restart event\n                let event_sender_inner = event_sender_clone.clone();\n                glib::spawn_future_local(async move {\n                    info!(\"Restarting gui ({cause})\");\n                    event_sender_inner\n                        .send(TransferType::Restart)\n                        .await\n                        .warn_details(\"unable to send restart\");\n                });\n            });\n\n            // Store the timer ID so we can cancel it if needed\n            *debounce_timer.borrow_mut() = Some(timer_id);\n        }\n    });\n}\n\nstatic WATCHERS: OnceLock<Mutex<Vec<Box<dyn Any + Send>>>> = OnceLock::new();\n\nfn setup_restart_listener(config_file: &Path, css_path: &Path, restart_tx: &Sender<&'static str>) {\n    let tx = restart_tx.clone();\n    if let Ok(watcher) = hyprshell_config_listener(config_file, move |mess| {\n        let _ = tx.send_blocking(mess);\n    }) {\n        WATCHERS\n            .get_or_init(|| Mutex::new(Vec::new()))\n            .lock()\n            .expect(\"Failed to lock watchers\")\n            .push(Box::new(watcher));\n    }\n    let tx = restart_tx.clone();\n    if let Ok(watcher) = hyprshell_css_listener(css_path, move |mess| {\n        let _ = tx.send_blocking(mess);\n    }) {\n        WATCHERS\n            .get_or_init(|| Mutex::new(Vec::new()))\n            .lock()\n            .expect(\"Failed to lock watchers\")\n            .push(Box::new(watcher));\n    }\n\n    let tx = restart_tx.clone();\n    glib::spawn_future_local(async move {\n        monitor_listener(move |mess| {\n            let _ = tx.send_blocking(mess);\n        })\n        .await;\n    });\n    let tx = restart_tx.clone();\n    glib::spawn_future_local(async move {\n        hyprland_config_listener(move |mess| {\n            let _ = tx.send_blocking(mess);\n        })\n        .await;\n    });\n}\n"
  },
  {
    "path": "src/util.rs",
    "content": "use anyhow::Context;\nuse core_lib::{Warn, WarnWithDetails, default};\nuse relm4::adw::gtk::{IconTheme, Settings};\nuse relm4::gtk;\nuse semver::Version;\nuse signal_hook::consts::{SIGINT, SIGTERM};\nuse signal_hook::iterator::Signals;\nuse std::cmp::Ordering;\nuse std::fs::{File, read_to_string, write};\nuse std::io::Write;\nuse std::path::{Path, PathBuf};\nuse std::process::exit;\nuse std::{fs, thread, time};\nuse tracing::{debug, info, trace, warn};\n\npub fn preactivate() -> anyhow::Result<()> {\n    let _span = tracing::span!(tracing::Level::TRACE, \"preactivate\").entered();\n    handle_sigterm();\n\n    init_gtk();\n    check_themes();\n\n    reload_icons(true);\n    reload_desktop_data().context(\"Failed to reload desktop data\")?;\n    Ok(())\n}\n\npub fn reload_icons(background: bool) {\n    let data = get_icon_data();\n    default::reload_available_icons(data.0, data.1, background)\n        .context(\"Failed to reload GTK icons\")\n        .warn();\n}\n\npub fn reload_desktop_data() -> anyhow::Result<()> {\n    let start = time::Instant::now();\n    default::reload_default_files().context(\"Failed to reload default files\")?;\n    windows_lib::reload_class_to_icon_map().context(\"Failed to reload class to icon map\")?;\n    launcher_lib::reload_applications_desktop_entries_map()\n        .context(\"Failed to reload desktop entries\")?;\n    debug!(\"Reloaded desktop data in {:?}\", start.elapsed());\n    Ok(())\n}\n\npub fn init_gtk() {\n    gtk::init().expect(\"Failed to initialize GTK\");\n}\n\npub fn check_themes() {\n    if let Some(settings) = Settings::default() {\n        let theme_name = settings.gtk_theme_name();\n        let icon_theme_name = settings.gtk_icon_theme_name();\n        info!(\n            \"Using theme: {theme_name:?} and icon theme: {icon_theme_name:?}, please make sure both exist, else weird icon or graphical issues may occur\"\n        );\n    } else {\n        warn!(\"Unable to check default settings for icon theme\");\n    }\n}\n\nfn handle_sigterm() {\n    let Ok(mut signals) = Signals::new([SIGTERM, SIGINT]) else {\n        warn!(\"Failed to create signal handler for SIGTERM and SIGINT\");\n        return;\n    };\n    thread::spawn(move || {\n        if let Some(sig) = signals.forever().next() {\n            info!(\"Received sig: {sig}, exiting gracefully\");\n            exec_lib::reset_no_follow_mouse()\n                .warn_details(\"Failed to reset follow mouse on SIGTERM\");\n            if let Err(err) = exec_lib::plugin::unload() {\n                warn!(\"Failed to unload plugin: {err:?}\",);\n            }\n            exit(0);\n        }\n    });\n}\n\nfn get_icon_data() -> (Vec<String>, Vec<PathBuf>) {\n    let icon_theme = IconTheme::new();\n    let gtk_icons = icon_theme\n        .icon_names()\n        .into_iter()\n        .map(|s| s.to_string())\n        .collect::<Vec<_>>();\n\n    let search_path = icon_theme\n        .search_path()\n        .into_iter()\n        .filter(|path| path.exists())\n        .collect::<Vec<_>>();\n    trace!(\"Icon theme search path: {search_path:?}\");\n    (gtk_icons, search_path)\n}\n\nconst NEW_VERSION_INFOS: &[(&str, &str)] = &[\n    (\n        \"4.7.0\",\n        \"Version 4.7.0 adds a hyprland plugin to register keypresses, adds shell completion, improves UI, adds usefull commands and much more.\",\n    ),\n    (\n        \"4.8.0\",\n        \"Version 4.8.0 adds a graphical settings Editor, support for special workspaces and vim motions\",\n    ),\n    (\n        \"4.9.0\",\n        \"Version 4.9.0 adds a new Settings Editor, replaces tui config generation\",\n    ),\n];\n\n/// Checks if the current version is newer than the cached version.\n/// If a mayor of minor update is detected, true is returned as second value.\npub fn check_new_version(cache_dir: &Path) -> anyhow::Result<(Ordering, Vec<String>)> {\n    let version_file = cache_dir.join(\"last_version.txt\");\n    let current_version = env!(\"CARGO_PKG_VERSION\");\n    let current_version =\n        Version::parse(current_version).context(\"Failed to parse current version\")?;\n    let cached_version = if version_file.exists() {\n        let contents = read_to_string(&version_file).context(\"Failed to read old version file\")?;\n        Version::parse(contents.trim()).context(\"Failed to parse old version\")?\n    } else {\n        fs::create_dir_all(cache_dir).context(\"Failed to create cache directory\")?;\n        let mut file = File::create(&version_file).context(\"Failed to create version file\")?;\n        file.write_all(current_version.to_string().as_bytes())\n            .context(\"Failed to write current version to file\")?;\n        Version::new(0, 0, 0)\n    };\n    trace!(\n        \"Cached version: {cached_version:?}, current version: {current_version:?}: {:?}\",\n        cached_version.cmp(&current_version)\n    );\n    write(&version_file, current_version.to_string().as_bytes())\n        .context(\"Failed to write current version to file\")?;\n    let versions = filter_version_messages(NEW_VERSION_INFOS, &current_version, &cached_version);\n    Ok((current_version.cmp(&cached_version), versions))\n}\n\nfn filter_version_messages(\n    versions: &[(&str, &str)],\n    current_version: &Version,\n    cached_version: &Version,\n) -> Vec<String> {\n    if current_version <= cached_version {\n        return Vec::new();\n    }\n    let mut parsed: Vec<(Version, String)> = versions\n        .iter()\n        .filter_map(|(k, s)| {\n            let ver_str = (*k).to_string();\n            Version::parse(&ver_str)\n                .ok()\n                .warn_details(\"version in NEW_VERSION_INFOS has invalid semver\")\n                .map(|v| (v, (*s).to_string()))\n        })\n        .collect();\n    parsed.sort_by(|a, b| a.0.cmp(&b.0));\n    parsed\n        .into_iter()\n        .filter_map(|(v, s)| {\n            if v > *cached_version && v <= *current_version {\n                Some(s)\n            } else {\n                None\n            }\n        })\n        .collect()\n}\n\n#[cfg(test)]\nmod tests {\n    use super::filter_version_messages;\n    use semver::Version;\n\n    fn v(s: &str) -> Version {\n        Version::parse(s).expect(\"invalid version\")\n    }\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_empty_versions() {\n        let result = filter_version_messages(&[], &v(\"1.0.0\"), &v(\"0.9.0\"));\n        assert!(result.is_empty());\n    }\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_older_current_version() {\n        let versions = [(\"1.0.0\", \"test\")];\n        let result = filter_version_messages(&versions, &v(\"0.9.0\"), &v(\"1.0.0\"));\n        assert!(result.is_empty());\n    }\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_newer_current_version() {\n        let versions = [(\"1.0.0\", \"1.0.0 test\")];\n        let result = filter_version_messages(&versions, &v(\"1.0.0\"), &v(\"0.9.0\"));\n        assert_eq!(result, vec![\"1.0.0 test\"]);\n    }\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_same_version() {\n        let versions = [(\"1.0.0\", \"test\")];\n        let result = filter_version_messages(&versions, &v(\"1.0.0\"), &v(\"1.0.0\"));\n        assert!(result.is_empty());\n    }\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_multiple_versions() {\n        let versions = [\n            (\"1.0.0\", \"1.0.0 test1\"),\n            (\"2.0.0\", \"2.0.0 test2\"),\n            (\"1.5.0\", \"1.5.0 test3\"),\n        ];\n        let result = filter_version_messages(&versions, &v(\"2.0.0\"), &v(\"1.0.0\"));\n        assert_eq!(result, vec![\"1.5.0 test3\", \"2.0.0 test2\"]);\n    }\n\n    #[test_log::test]\n    #[test_log(default_log_filter = \"trace\")]\n    fn test_all_versions() {\n        let versions = [\n            (\"1.0.0\", \"1.0.0 test1\"),\n            (\"2.0.0\", \"2.0.0 test2\"),\n            (\"1.5.0\", \"1.5.0 test3\"),\n        ];\n        let result = filter_version_messages(&versions, &v(\"2.0.0\"), &v(\"0.0.0\"));\n        assert_eq!(result, vec![\"1.0.0 test1\", \"1.5.0 test3\", \"2.0.0 test2\"]);\n    }\n}\n"
  }
]