[
  {
    "path": ".czrc",
    "content": "{\n  \"path\": \"cz-conventional-changelog\"\n}\n"
  },
  {
    "path": ".envrc",
    "content": "use flake"
  },
  {
    "path": ".gitattributes",
    "content": "*.json text diff"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "github: LGUG2Z\nko_fi: lgug2z\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "content": "name: Bug report\ndescription: File a bug report\nlabels: [bug]\ntitle: \"[BUG]: \"\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Please **do not** open an issue for applications with invisible windows leaving ghost tiles.\n\n        You can run `komorebic visible-windows` when the ghost tile is present on your workspace to retrieve the invisible window's exe, class name and title, and then use that information to [ignore the window](https://lgug2z.github.io/komorebi/common-workflows/ignore-windows.html) responsible for the ghost tile.\n\n        If it is not possible to uniquely identify the invisible window resulting in a ghost tile through a mixture of exe, title and class identifiers, then this is not a bug with komorebi but a bug with the application you are using, and you should open an issue with the developer(s) of that application.\n  - type: textarea\n    validations:\n      required: true\n    attributes:\n      label: Summary\n      description: >\n        Please provide a short summary of the bug, along with any information\n        you feel is relevant to replicating the bug.\n\n        You may include screenshots and videos in this section.\n  - type: textarea\n    validations:\n      required: true\n    attributes:\n      label: Version Information\n      description: >\n        Please provide information about the versions of Windows and komorebi\n        running on your machine.\n\n        Do not submit a bug if you are not using an official version of Windows\n        such as AtlasOS; only official versions of Windows are supported.\n\n        ```\n        systeminfo | findstr /B /C:\"OS Name\" /B /C:\"OS Version\"\n        ```\n\n        ```\n        komorebic --version\n        ```\n  - type: textarea\n    validations:\n      required: true\n    attributes:\n      label: Komorebi Configuration\n      description: >\n        Please provide your configuration file (komorebi.json or komorebi.bar.json)\n      render: json\n  - type: textarea\n    validations:\n      required: true\n    attributes:\n      label: Hotkey Configuration\n      description: >\n        Please provide your whkdrc or komorebi.ahk hotkey configuration file\n      render: shell\n  - type: textarea\n    validations:\n      required: true\n    attributes:\n      label: Output of komorebic check\n      description: >\n        Please provide the output of `komorebic check`\n      render: shell\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: Komorebi Documentation\n    url: https://lgug2z.github.io/komorebi/\n    about: Please search the documentation website before opening an issue\n  - name: Komorebi Quickstart Tutorial Video\n    url: https://www.youtube.com/watch?v=MMZUAtHbTYY\n    about: If you are new, please make sure you watch the quickstart tutorial video\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "content": "name: Feature request\ndescription: Suggest a new feature (Limited to Sponsors, Commercial License Holders, and Collaborators)\nlabels: [enhancement]\ntitle: \"[FEAT]: \"\nbody:\n  - type: dropdown\n    id: Eligibility\n    attributes:\n      label: Eligibility\n      description: >\n        Feature requests are considered from individuals who are current $5+ monthly sponsors to the project, individual commercial use license holders, and approved collaborators.\n\n        Please specify the platform you use to sponsor the project.\n      options:\n        - Individual Commercial Use License\n        - GitHub Sponsor\n        - Ko-fi Sponsor\n        - Approved Collaborator\n      default: 0\n    validations:\n      required: true\n  - type: textarea\n    validations:\n      required: true\n    attributes:\n      label: Suggestion\n      description: >\n        Please share your suggestion here. Be sure to include all necessary context.\n\n        If you sponsor on a platform where you use a different username, please specify the username here.\n  - type: textarea\n    validations:\n      required: true\n    attributes:\n      label: Alternatives Considered\n      description: >\n        Please share share alternatives you have considered here.\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n    assignees:\n      - \"LGUG2Z\"\n    commit-message:\n      prefix: chore\n      include: scope\n\n  - package-ecosystem: \"cargo\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n    assignees:\n      - \"LGUG2Z\"\n    commit-message:\n      prefix: chore\n      include: scope\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "<!--\n  Please follow the Conventional Commits specification.\n\n  If you need to update your PR with changes from `master`, please run `git rebase master`.\n\n  By opening this PR, you confirm that you have read and understood this project's `CONTRIBUTING.md`.\n-->\n"
  },
  {
    "path": ".github/workflows/feature-check.yaml",
    "content": "name: Feature Issue Check\n\non:\n  issues:\n    types: [ opened ]\n\njobs:\n  auto-close:\n    runs-on: ubuntu-latest\n    permissions:\n      issues: write\n\n    steps:\n      - name: Check and close feature issues\n        uses: actions/github-script@v8\n        with:\n          script: |\n            const issue = context.payload.issue;\n            \n            if (issue.title.startsWith('[FEAT]: ')) {\n              const message = `\n                Feature requests on this repository are only open to current [GitHub sponsors](https://github.com/sponsors/LGUG2Z) on the $5/month tier and above, people with a valid [individual commercial use license](https://lgug2z.com/software/komorebi), and approved contributors. \n            \n                This issue has been automatically closed until one of those pre-requisites can be validated.\n              `.replace(/^\\s+/gm, ''); \n            \n              await github.rest.issues.createComment({\n                owner: context.repo.owner,\n                repo: context.repo.repo,\n                issue_number: issue.number,\n                body: message,\n              });\n\n              await github.rest.issues.update({\n                owner: context.repo.owner,\n                repo: context.repo.repo,\n                issue_number: issue.number,\n                state: 'closed'\n              });\n\n              await github.rest.issues.lock({\n                owner: context.repo.owner,\n                repo: context.repo.repo,\n                issue_number: issue.number,\n                state: 'resolved'\n              });\n            }"
  },
  {
    "path": ".github/workflows/windows.yaml",
    "content": "# Adapted from https://github.com/rust-lang/rustup/blob/master/.github/workflows/windows-builds-on-master.yaml\n\nname: Windows\n\non:\n  pull_request:\n    branches:\n      - \"*\"\n  push:\n    branches:\n      - master\n      - feature/*\n      - hotfix/*\n    tags:\n      - v*\n  schedule:\n    - cron: \"30 0 * * 0\" # Every day at 00:30 UTC\n  workflow_dispatch:\n\njobs:\n  cargo-deny:\n    runs-on: ubuntu-22.04\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n      - uses: EmbarkStudios/cargo-deny-action@v2\n\n  build:\n    strategy:\n      fail-fast: true\n      matrix:\n        platform:\n          - os-name: Windows-x86_64\n            runs-on: windows-latest\n            target: x86_64-pc-windows-msvc\n          - os-name: Windows-aarch64\n            runs-on: windows-latest\n            target: aarch64-pc-windows-msvc\n    runs-on: ${{ matrix.platform.runs-on }}\n    permissions: write-all\n    env:\n      RUSTFLAGS: -Ctarget-feature=+crt-static -Dwarnings\n      GH_TOKEN: ${{ github.token }}\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n      - run: rustup toolchain install stable --profile minimal\n      - run: rustup component add --toolchain stable-x86_64-pc-windows-msvc clippy\n      - run: rustup toolchain install nightly --allow-downgrade -c rustfmt\n      - uses: Swatinem/rust-cache@v2\n        with:\n          cache-on-failure: \"true\"\n          cache-all-crates: \"true\"\n          key: ${{ matrix.platform.target }}\n      - run: cargo +nightly fmt --check\n      - run: cargo clippy\n      - run: cargo test\n      - uses: houseabsolute/actions-rust-cross@v1\n        with:\n          command: \"build\"\n          target: ${{ matrix.platform.target }}\n          args: \"--locked --release\"\n      - run: |\n          cargo install cargo-wix\n          cargo wix --no-build -p komorebi --nocapture -I .\\wix\\main.wxs --target ${{ matrix.platform.target }}\n      - uses: actions/upload-artifact@v6\n        with:\n          name: komorebi-${{ matrix.platform.target }}-${{ github.sha }}\n          path: |\n            target/${{ matrix.platform.target }}/release/*.exe\n            target/${{ matrix.platform.target }}/release/*.pdb\n            target/wix/komorebi-*.msi\n          retention-days: 14\n\n  nightly:\n    needs: build\n    runs-on: windows-latest\n    permissions: write-all\n    if: ${{ github.ref == 'refs/heads/master' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' ) }}\n    env:\n      GH_TOKEN: ${{ github.token }}\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n      - shell: bash\n        run: echo \"VERSION=nightly\" >> $GITHUB_ENV\n      - uses: actions/download-artifact@v7\n      - run: |\n          Compress-Archive -Force ./komorebi-x86_64-pc-windows-msvc-${{ github.sha }}/x86_64-pc-windows-msvc/release/*.exe komorebi-$Env:VERSION-x86_64-pc-windows-msvc.zip\n          Copy-Item ./komorebi-x86_64-pc-windows-msvc-${{ github.sha }}/wix/*x86_64.msi -Destination ./komorebi-$Env:VERSION-x86_64.msi\n          echo \"$((Get-FileHash komorebi-$Env:VERSION-x86_64-pc-windows-msvc.zip).Hash.ToLower())  komorebi-$Env:VERSION-x86_64-pc-windows-msvc.zip\" >checksums.txt\n\n          Compress-Archive -Force ./komorebi-aarch64-pc-windows-msvc-${{ github.sha }}/aarch64-pc-windows-msvc/release/*.exe komorebi-$Env:VERSION-aarch64-pc-windows-msvc.zip\n          Copy-Item ./komorebi-aarch64-pc-windows-msvc-${{ github.sha }}/wix/*aarch64.msi -Destination ./komorebi-$Env:VERSION-aarch64.msi\n          echo \"$((Get-FileHash komorebi-$Env:VERSION-aarch64-pc-windows-msvc.zip).Hash.ToLower())  komorebi-$Env:VERSION-aarch64-pc-windows-msvc.zip\" >>checksums.txt\n      - uses: Swatinem/rust-cache@v2\n        with:\n          cache-on-failure: \"true\"\n          cache-all-crates: \"true\"\n      - shell: bash\n        run: |\n          if ! type kokai >/dev/null; then cargo install --locked kokai --force; fi\n          git tag -d nightly || true\n          git tag nightly\n          kokai release --no-emoji --add-links github:commits,issues --ref nightly >\"CHANGELOG.md\"\n      - shell: bash\n        run: |\n          gh release delete nightly --yes || true\n          git push origin :nightly || true\n          gh release create nightly \\\n            --target $GITHUB_SHA \\\n            --prerelease \\\n            --title \"komorebi nightly (${GITHUB_SHA})\" \\\n            --notes-file CHANGELOG.md \\\n            komorebi-nightly-x86_64-pc-windows-msvc.zip \\\n            komorebi-nightly-x86_64.msi \\\n            komorebi-nightly-aarch64-pc-windows-msvc.zip \\\n            komorebi-nightly-aarch64.msi \\\n            checksums.txt\n\n  release-dry-run:\n    needs: build\n    runs-on: windows-latest\n    permissions: write-all\n    if: ${{ github.ref == 'refs/heads/master' }}\n    env:\n      GH_TOKEN: ${{ github.token }}\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n      - shell: bash\n        run: |\n          TAG=${{ github.event.release.tag_name }}\n          echo \"VERSION=${TAG#v}\" >> $GITHUB_ENV\n      - uses: actions/download-artifact@v7\n      - run: |\n          Compress-Archive -Force ./komorebi-x86_64-pc-windows-msvc-${{ github.sha }}/x86_64-pc-windows-msvc/release/*.exe komorebi-$Env:VERSION-x86_64-pc-windows-msvc.zip\n          Copy-Item ./komorebi-x86_64-pc-windows-msvc-${{ github.sha }}/wix/*x86_64.msi -Destination ./komorebi-$Env:VERSION-x86_64.msi\n          echo \"$((Get-FileHash komorebi-$Env:VERSION-x86_64-pc-windows-msvc.zip).Hash.ToLower())  komorebi-$Env:VERSION-x86_64-pc-windows-msvc.zip\" >checksums.txt\n\n          Compress-Archive -Force ./komorebi-aarch64-pc-windows-msvc-${{ github.sha }}/aarch64-pc-windows-msvc/release/*.exe komorebi-$Env:VERSION-aarch64-pc-windows-msvc.zip\n          Copy-Item ./komorebi-aarch64-pc-windows-msvc-${{ github.sha }}/wix/*aarch64.msi -Destination ./komorebi-$Env:VERSION-aarch64.msi\n          echo \"$((Get-FileHash komorebi-$Env:VERSION-aarch64-pc-windows-msvc.zip).Hash.ToLower())  komorebi-$Env:VERSION-aarch64-pc-windows-msvc.zip\" >>checksums.txt\n      - uses: Swatinem/rust-cache@v2\n        with:\n          cache-on-failure: \"true\"\n          cache-all-crates: \"true\"\n      - shell: bash\n        run: |\n          if ! type kokai >/dev/null; then cargo install --locked kokai --force; fi\n          git tag -d nightly || true\n          kokai release --no-emoji --add-links github:commits,issues --ref \"${{ github.ref_name }}\" >\"CHANGELOG.md\"\n      - uses: softprops/action-gh-release@v2\n        with:\n          draft: true\n          body_path: \"CHANGELOG.md\"\n          files: |\n            checksums.txt\n            *.zip\n            *.msi\n\n  release:\n    needs: build\n    runs-on: windows-latest\n    permissions: write-all\n    if: startsWith(github.ref, 'refs/tags/v')\n    env:\n      GH_TOKEN: ${{ github.token }}\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n      - shell: bash\n        run: |\n          TAG=${{ github.ref_name }}\n          echo \"VERSION=${TAG#v}\" >> $GITHUB_ENV\n      - uses: actions/download-artifact@v7\n      - run: |\n          Compress-Archive -Force ./komorebi-x86_64-pc-windows-msvc-${{ github.sha }}/x86_64-pc-windows-msvc/release/*.exe komorebi-$Env:VERSION-x86_64-pc-windows-msvc.zip\n          Copy-Item ./komorebi-x86_64-pc-windows-msvc-${{ github.sha }}/wix/*x86_64.msi -Destination ./komorebi-$Env:VERSION-x86_64.msi\n          echo \"$((Get-FileHash komorebi-$Env:VERSION-x86_64-pc-windows-msvc.zip).Hash.ToLower())  komorebi-$Env:VERSION-x86_64-pc-windows-msvc.zip\" >checksums.txt\n\n          Compress-Archive -Force ./komorebi-aarch64-pc-windows-msvc-${{ github.sha }}/aarch64-pc-windows-msvc/release/*.exe komorebi-$Env:VERSION-aarch64-pc-windows-msvc.zip\n          Copy-Item ./komorebi-aarch64-pc-windows-msvc-${{ github.sha }}/wix/*aarch64.msi -Destination ./komorebi-$Env:VERSION-aarch64.msi\n          echo \"$((Get-FileHash komorebi-$Env:VERSION-aarch64-pc-windows-msvc.zip).Hash.ToLower())  komorebi-$Env:VERSION-aarch64-pc-windows-msvc.zip\" >>checksums.txt\n      - uses: Swatinem/rust-cache@v2\n        with:\n          cache-on-failure: \"true\"\n          cache-all-crates: \"true\"\n      - shell: bash\n        run: |\n          if ! type kokai >/dev/null; then cargo install --locked kokai --force; fi\n          git tag -d nightly || true\n          kokai release --no-emoji --add-links github:commits,issues --ref \"$(git tag --points-at HEAD)\" >\"CHANGELOG.md\"\n      - uses: softprops/action-gh-release@v2\n        with:\n          body_path: \"CHANGELOG.md\"\n          files: |\n            checksums.txt\n            *.zip\n            *.msi\n\n  winget:\n    runs-on: ubuntu-latest\n    needs: release\n    if: startsWith(github.ref, 'refs/tags/v')\n    steps:\n      - uses: vedantmgoyal2009/winget-releaser@main\n        with:\n          identifier: LGUG2Z.komorebi\n          token: ${{ secrets.WINGET_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": ".idea\n/dist\n/target\nCHANGELOG.md\ndummy.go\nkomorebic/applications.yaml\nkomorebic/applications.json\n/.vs\n/bar-schema\n/komorebi-schema\n/.wrangler\n/.xwin-cache\nresult\n/.direnv\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# The Komorebi Code of Conduct\n\nThis document is based on the [Rust Code of\nConduct](https://www.rust-lang.org/policies/code-of-conduct)\n\n## Conduct\n\n- We are committed to providing a friendly, safe and welcoming environment for\nall, regardless of level of experience, gender identity and expression, sexual\norientation, disability, personal appearance, body size, race, ethnicity, age,\nreligion, nationality, or other similar characteristic.\n\n- Please avoid using overtly sexual aliases or other nicknames that might\ndetract from a friendly, safe and welcoming environment for all.\n\n- Please be kind and courteous. There’s no need to be mean or rude.\n\n- Respect that people have differences of opinion and that every design or\nimplementation choice carries a trade-off and numerous costs. There is seldom a\nright answer.\n\n- Please keep unstructured critique to a minimum. If you have solid ideas you\nwant to experiment with, make a fork and see how it works.\n\n- We will exclude you from interaction if you insult, demean or harass anyone.\nThat is not welcome behavior. We interpret the term “harassment” as including\nthe definition in the [Citizen Code of\nConduct](https://github.com/stumpsyn/policies/blob/master/citizen_code_of_conduct.md);\nif you have any lack of clarity about what might be included in that concept,\nplease read their definition. In particular, we don’t tolerate behavior that\nexcludes people in socially marginalized groups.\n\n- Private harassment is also unacceptable. No matter who you are, if you feel\nyou have been or are being harassed or made uncomfortable by a community member,\nplease contact me immediately. Whether you’re a regular contributor or a\nnewcomer, we care about making this community a safe place for you and we’ve got\nyour back.\n\n- Likewise any spamming, trolling, flaming, baiting or other attention-stealing\nbehavior is not welcome.\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to the Project\n\nThe project is a collection of contributions from both the project leaders and\ncommunity members. There are many ways to contribute, this can include content\nin the project repositories, as well as contributing in public and private\nconversation, assisting users, writing blog posts, and many other ways.\n\n## How contributions are made\n\nContributions to the project primarily happen in the project source\nrepositories, but may also occur in other places, such as discussion forums and\npublic and private discourse.\n\n## Contributing content to the Project\n\nIn order for the project leaders to manage sustained progress toward the\nproject goals and maintain project velocity, focus and quality, the project may\nadjust the license terms over time.\n\nContent contributed to the project must therefore be provided under\nsufficiently liberal terms to allow these operations to proceed unimpeded. As\nsuch contributions are accepted with the following understanding:\n\n* Contributed content is licensed under the terms of the 0-BSD license\n* Contributors accept the terms of the project license at the time of\n  contribution\n\nBy making a contribution, you accept both the current project license terms,\nand that all contributions that you have made are provided under the terms of\nthe 0-BSD license.\n\n## Zero-Clause BSD\n\n```\nPermission to use, copy, modify, and/or distribute this software for  \nany purpose with or without fee is hereby granted.\n\nTHE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL  \nWARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES  \nOF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE  \nFOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY  \nDAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN  \nAN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT  \nOF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n```\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[workspace]\n\nresolver = \"2\"\nmembers = [\n  \"komorebi\",\n  \"komorebi-client\",\n  \"komorebi-gui\",\n  \"komorebi-layouts\",\n  \"komorebic\",\n  \"komorebic-no-console\",\n  \"komorebi-bar\",\n  \"komorebi-themes\",\n  \"komorebi-shortcuts\",\n]\n\n[workspace.dependencies]\nclap = { version = \"4\", features = [\"derive\", \"wrap_help\"] }\nchrono-tz = \"0.10\"\nchrono = \"0.4\"\ncrossbeam-channel = \"0.5\"\ncrossbeam-utils = \"0.8\"\ncolor-eyre = \"0.6\"\neframe = \"0.33\"\negui_extras = \"0.33\"\ndirs = \"6\"\ndunce = \"1\"\nhotwatch = \"0.5\"\nschemars = \"1.1\"\nlazy_static = \"1\"\nserde = { version = \"1\", features = [\"derive\"] }\nserde_json = { package = \"serde_json_lenient\", version = \"0.2\" }\nserde_yaml = \"0.9\"\nstrum = { version = \"0.27\", features = [\"derive\"] }\ntracing = \"0.1\"\ntracing-appender = \"0.2\"\ntracing-subscriber = { version = \"0.3\", features = [\"env-filter\"] }\nparking_lot = \"0.12\"\npaste = \"1\"\nsysinfo = \"0.38\"\nuds_windows = \"1\"\nwin32-display-data = { git = \"https://github.com/LGUG2Z/win32-display-data\", rev = \"8c42d8db257d30fe95bc98c2e5cd8f75da861021\" }\nwindows-numerics = { version = \"0.3\" }\nwindows-implement = { version = \"0.60\" }\nwindows-interface = { version = \"0.59\" }\nwindows-core = { version = \"0.62\" }\nshadow-rs = \"1\"\nwhich = \"8\"\n\n[workspace.dependencies.windows]\nversion = \"0.62\"\nfeatures = [\n  \"Foundation_Numerics\",\n  \"Win32_Devices\",\n  \"Win32_Devices_Display\",\n  \"Win32_System_Com\",\n  \"Win32_UI_Shell_Common\",           # for IObjectArray\n  \"Win32_Foundation\",\n  \"Win32_Globalization\",\n  \"Win32_Graphics_Dwm\",\n  \"Win32_Graphics_Gdi\",\n  \"Win32_Graphics_Direct2D\",\n  \"Win32_Graphics_Direct2D_Common\",\n  \"Win32_Graphics_Dxgi_Common\",\n  \"Win32_System_LibraryLoader\",\n  \"Win32_System_Power\",\n  \"Win32_System_RemoteDesktop\",\n  \"Win32_System_Threading\",\n  \"Win32_UI_Accessibility\",\n  \"Win32_UI_HiDpi\",\n  \"Win32_UI_Input_KeyboardAndMouse\",\n  \"Win32_UI_Shell\",\n  \"Win32_UI_Shell_Common\",\n  \"Win32_UI_WindowsAndMessaging\",\n  \"Win32_System_SystemServices\",\n  \"Win32_System_WindowsProgramming\",\n  \"Media\",\n  \"Media_Control\",\n]\n\n[profile.release-opt]\ninherits = \"release\"\nlto = true\npanic = \"abort\"\ncodegen-units = 1\nstrip = true\n\n[workspace.metadata.crane]\nname = \"komorebi-workspace\"\n"
  },
  {
    "path": "LICENSE.md",
    "content": "# Komorebi License\n\nVersion 2.0.0\n\n## Acceptance\n\nIn order to get any license under these terms, you must agree\nto them as both strict obligations and conditions to all\nyour licenses.\n\n## Copyright License\n\nThe licensor grants you a copyright license for the software\nto do everything you might do with the software that would\notherwise infringe the licensor's copyright in it for any\npermitted purpose. However, you may only distribute the source\ncode of the software according to the [Distribution License](\n#distribution-license), you may only make changes according\nto the [Changes License](#changes-license), and you may not\notherwise distribute the software or new works based on the\nsoftware.\n\n## Distribution License\n\nThe licensor grants you an additional copyright license to\ndistribute copies of the source code of the software. Your\nlicense to distribute covers distributing the source code of\nthe software with changes permitted by the [Changes License](\n#changes-license).\n\n## Changes License\n\nThe licensor grants you an additional copyright license to\nmake changes for any permitted purpose.\n\n## Patent License\n\nThe licensor grants you a patent license for the software that\ncovers patent claims the licensor can license, or becomes able\nto license, that you would infringe by using the software.\n\n## Personal Uses\n\nPersonal use for research, experiment, and testing for\nthe benefit of public knowledge, personal study, private\nentertainment, hobby projects, amateur pursuits, or religious\nobservance, without any anticipated commercial application,\nis use for a permitted purpose.\n\n## Fair Use\n\nYou may have \"fair use\" rights for the software under the\nlaw. These terms do not limit them.\n\n## No Other Rights\n\nThese terms do not allow you to sublicense or transfer any of\nyour licenses to anyone else, or prevent the licensor from\ngranting licenses to anyone else. These terms do not imply\nany other licenses.\n\n## Patent Defense\n\nIf you make any written claim that the software infringes or\ncontributes to infringement of any patent, your patent license\nfor the software granted under these terms ends immediately. If\nyour company makes such a claim, your patent license ends\nimmediately for work on behalf of your company.\n\n## Violations\n\nThe first time you are notified in writing that you have\nviolated any of these terms, or done anything with the software\nnot covered by your licenses, your licenses can nonetheless\ncontinue if you come into full compliance with these terms,\nand take practical steps to correct past violations, within\n32 days of receiving notice. Otherwise, all your licenses\nend immediately.\n\n## No Liability\n\n***As far as the law allows, the software comes as is, without\nany warranty or condition, and the licensor will not be liable\nto you for any damages arising out of these terms or the use\nor nature of the software, under any kind of legal claim.***\n\n## Definitions\n\nThe **licensor** is the individual or entity offering these\nterms, and the **software** is the software the licensor makes\navailable under these terms.\n\n**You** refers to the individual or entity agreeing to these\nterms.\n\n**Your company** is any legal entity, sole proprietorship,\nor other kind of organization that you work for, plus all\norganizations that have control over, are under the control of,\nor are under common control with that organization.  **Control**\nmeans ownership of substantially all the assets of an entity,\nor the power to direct its management and policies by vote,\ncontract, or otherwise. Control can be direct or indirect.\n\n**Your licenses** are all the licenses granted to you for the\nsoftware under these terms.\n\n**Use** means anything you do with the software requiring one\nof your licenses.\n"
  },
  {
    "path": "PRIVACY.md",
    "content": "# Privacy Policy for Komorebi\n\nNo data about your device(s) or _komorebi_ usage leave your device.\n\n## Data Maintained by Komorebi\n\n_komorebi_ writes log files to and keeps a list of temporary window handles (HWNDs) currently managed by the process in\nthe `$Env:LOCALAPPDATA\\komorebi\\` directory. This directory is owned by the user running the process."
  },
  {
    "path": "README.md",
    "content": "# komorebi\n\nTiling Window Management for Windows.\n\n<p>\n  <a href=\"https://techforpalestine.org/learn-more\">\n    <img alt=\"Tech for Palestine\" src=\"https://badge.techforpalestine.org/default\">\n  </a>\n  <img alt=\"GitHub Workflow Status\" src=\"https://img.shields.io/github/actions/workflow/status/LGUG2Z/komorebi/.github/workflows/windows.yaml\">\n  <img alt=\"GitHub all releases\" src=\"https://img.shields.io/github/downloads/LGUG2Z/komorebi/total\">\n  <img alt=\"GitHub commits since latest release (by date) for a branch\" src=\"https://img.shields.io/github/commits-since/LGUG2Z/komorebi/latest\">\n  <img alt=\"Active Individual Commercial Use Licenses\" src=\"https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Flgug2z-ecstaticmagentacheetah.web.val.run&query=%24.&label=active%20individual%20commercial%20use%20licenses&cacheSeconds=3600&link=https%3A%2F%2Flgug2z.com%2Fsoftware%2Fkomorebi\">\n  <a href=\"https://discord.gg/mGkn66PHkx\">\n    <img alt=\"Discord\" src=\"https://img.shields.io/discord/898554690126630914\">\n  </a>\n  <a href=\"https://github.com/sponsors/LGUG2Z\">\n    <img alt=\"GitHub Sponsors\" src=\"https://img.shields.io/github/sponsors/LGUG2Z\">\n  </a>\n  <a href=\"https://ko-fi.com/lgug2z\">\n    <img alt=\"Ko-fi\" src=\"https://img.shields.io/badge/kofi-tip-green\">\n  </a>\n  <a href=\"https://notado.app/feeds/jado/software-development\">\n    <img alt=\"Notado Feed\" src=\"https://img.shields.io/badge/Notado-Subscribe-informational\">\n  </a>\n  <a href=\"https://www.youtube.com/channel/UCeai3-do-9O4MNy9_xjO6mg?sub_confirmation=1\">\n    <img alt=\"YouTube\" src=\"https://img.shields.io/youtube/channel/subscribers/UCeai3-do-9O4MNy9_xjO6mg\">\n  </a>\n</p>\n\n![screenshot](https://user-images.githubusercontent.com/13164844/184027064-f5a6cec2-2865-4d65-a549-a1f1da589abf.png)\n\n## Note: Students using devices enrolled in mobile device management (MDM)\n\nYour usage still falls under the [Komorebi License 2.0.0](./LICENSE.md).\n\nYou can email me at the address I sign my commits with (add `.patch` to the end\nof any commit URL on GitHub to find it) from the address associated with your\ninstitution with the subject \"komorebi - student with an MDM device\", and I will\nbe able to remove the splash intended for corporate users, whose usage falls\nunder the [Individual Commercial Use\nLicense](https://lgug2z.com/software/komorebi).\n\nThis is currently a manual process - most days this shouldn't take more than\n12h, and you will receive an email reply from me when the process is complete.\n\nIf you haven't had a reply to your email within 24h you can reach out to me on\nDiscord.\n\n## Note: Unexpected mobile device management (MDM) detection prompts\n\nYou have most likely unintentionally enrolled your device in \"Bring Your Own\nDevice\" (BYOD) MDM. You can confirm if this is the case by running `dsregcmd\n/status` and then take the appropriate steps to remove the MDM profile and take\nback full control of your system.\n\nIf you need help doing this you can ask on Discord.\n\n## Note: komorebi for Mac\n\nkomorebi for Mac lives [here](https://github.com/LGUG2Z/komorebi-for-mac) :)\n\n## Overview\n\n_komorebi_ is a tiling window manager that works as an extension to Microsoft's\n[Desktop Window\nManager](https://docs.microsoft.com/en-us/windows/win32/dwm/dwm-overview) in\nWindows 10 and above.\n\n_komorebi_ allows you to control application windows, virtual workspaces and display monitors with a CLI which can be\nused with third-party software such as [`whkd`](https://github.com/LGUG2Z/whkd)\nand [AutoHotKey](https://github.com/Lexikos/AutoHotkey_L) to set user-defined keyboard shortcuts.\n\n_komorebi_ aims to make _as few modifications as possible_ to the operating\nsystem and desktop environment by default. Users are free to make such\nmodifications in their own configuration files for _komorebi_, but these will\nremain opt-in and off-by-default for the foreseeable future.\n\nPlease refer to the [documentation](https://lgug2z.github.io/komorebi) for instructions on how\nto [install](https://lgug2z.github.io/komorebi/installation.html) and\n[configure](https://lgug2z.github.io/komorebi/example-configurations.html)\n_komorebi_, [common workflows](https://lgug2z.github.io/komorebi/common-workflows/komorebi-config-home.html), a complete\n[configuration schema reference](https://komorebi.lgug2z.com/schema) and a\ncomplete [CLI reference](https://lgug2z.github.io/komorebi/cli/quickstart.html).\n\n## Community\n\nThere is a [Discord server](https://discord.gg/mGkn66PHkx) available for\n_komorebi_-related discussion, help, troubleshooting etc. If you have any\nspecific feature requests or bugs to report, please create an issue in this\nrepository.\n\nThere is a [YouTube\nchannel](https://www.youtube.com/channel/UCeai3-do-9O4MNy9_xjO6mg) where I post\n_komorebi_ development videos, feature previews and release overviews. Subscribing\nto the channel (which is monetized as part of the YouTube Partner Program) and\nwatching videos is a really simple and passive way to contribute financially to\nthe development and maintenance of _komorebi_.\n\nThere is an [Awesome List](https://github.com/LGUG2Z/awesome-komorebi) which\nshowcases the many awesome projects that exist in the _komorebi_ ecosystem.\n\n## Licensing for Personal Use\n\n`komorebi` is [educational source\nsoftware](https://lgug2z.com/articles/educational-source-software/).\n\n`komorebi` is licensed under the [Komorebi 2.0.0\nlicense](https://github.com/LGUG2Z/komorebi-license), which is a fork of the\n[PolyForm Strict 1.0.0\nlicense](https://polyformproject.org/licenses/strict/1.0.0). On a high level\nthis means that you are free to do whatever you want with `komorebi` for\npersonal use other than redistribution, or distribution of new works (i.e.\nhard-forks) based on the software.\n\nAnyone is free to make their own fork of `komorebi` with changes intended either\nfor personal use or for integration back upstream via pull requests.\n\nThe [Komorebi 2.0.0 License](https://github.com/LGUG2Z/komorebi-license) does\nnot permit any kind of commercial use (i.e. using `komorebi` at work).\n\n## Sponsorship for Personal Use\n\n_komorebi_ is a free and educational source project, and one that encourages you\nto make charitable donations if you find the software to be useful and have the\nfinancial means.\n\nI encourage you to make a charitable donation to the [Palestine Children's\nRelief Fund](https://pcrf1.app.neoncrm.com/forms/gaza-recovery) or to contribute\nto a [Gaza Funds campaign](https://gazafunds.com) before you consider sponsoring\nme on GitHub.\n\n[GitHub Sponsors is enabled for this\nproject](https://github.com/sponsors/LGUG2Z). Sponsors can claim custom roles on\nthe Discord server, get shout outs at the end of _komorebi_-related videos on\nYouTube, gain the ability to submit feature requests on the issue tracker, and\nreceive releases of komorebi with \"easter eggs\" on physical media.\n\nIf you would like to tip or sponsor the project but are unable to use GitHub\nSponsors, you may also sponsor through [Ko-fi](https://ko-fi.com/lgug2z), or\nmake an anonymous Bitcoin donation to `bc1qv73wzspc77k46uty4vp85x8sdp24mphvm58f6q`.\n\n## Licensing for Commercial Use\n\nA dedicated Individual Commercial Use License is available for those who want to\nuse `komorebi` at work.\n\nThe Individual Commerical Use License adds “Commercial Use” as a “Permitted Use”\nfor the licensed individual only, for the duration of a valid paid license\nsubscription only. All provisions and restrictions enumerated in the [Komorebi\nLicense](https://github.com/LGUG2Z/komorebi-license) continue to apply.\n\nMore information, pricing and purchase links for Individual Commercial Use\nLicenses [can be found here](https://lgug2z.com/software/komorebi).\n\n# Installation\n\nA [detailed installation and quickstart\nguide](https://lgug2z.github.io/komorebi/installation.html) is available which shows how to get started\nusing `scoop`, `winget` or building from source.\n\n[![Watch the quickstart walkthrough video](https://img.youtube.com/vi/MMZUAtHbTYY/hqdefault.jpg)](https://www.youtube.com/watch?v=MMZUAtHbTYY)\n\n# Comparison With Fancy Zones\n\nCommunity member [Olge](https://www.youtube.com/@polle5555) has created an\nexcellent video which compares the default window management features of\nWindows 11, Fancy Zones and komorebi.\n\nIf you are not familiar with tiling window managers or if you are looking at\nkomorebi and wondering \"how is this different from Fancy Zones? 🤔\", this short\nvideo will answer the majority of your questions.\n\n[![Watch the comparison video](https://img.youtube.com/vi/0LCbS_gm0RA/hqdefault.jpg)](https://www.youtube.com/watch?v=0LCbS_gm0RA)\n\n# Demonstrations\n\n[@amnweb](https://github.com/amnweb) showing _komorebi_ `v0.1.28` running on Windows 11 with window borders,\nunfocused window transparency and animations enabled, using a custom status bar integrated using\n_komorebi_'\ns [Window Manager Event Subscriptions](https://github.com/LGUG2Z/komorebi?tab=readme-ov-file#window-manager-event-subscriptions).\n\nhttps://github.com/LGUG2Z/komorebi/assets/13164844/21be8dc4-fa76-4f70-9b37-1d316f4b40c2\n\n[@haxibami](https://github.com/haxibami) showing _komorebi_ running on Windows\n11 with a terminal emulator, a web browser and a code editor. The original\nvideo can be viewed\n[here](https://twitter.com/haxibami/status/1501560766578659332).\n\nhttps://user-images.githubusercontent.com/13164844/163496447-20c3ff0a-c5d8-40d1-9cc8-156c4cebf12e.mp4\n\n[@aik2mlj](https://github.com/aik2mlj) showing _komorebi_ running on Windows 11\nwith multiple workspaces, terminal emulators, a web browser, and the\n[yasb](https://github.com/DenBot/yasb) status bar with the _komorebi_ workspace\nwidget enabled. The original video can be viewed\n[here](https://zhuanlan.zhihu.com/p/455064481).\n\nhttps://user-images.githubusercontent.com/13164844/163496414-a9cde3d1-b8a7-4a7a-96fb-a8985380bc70.mp4\n\n# Contribution Guidelines\n\nIf you would like to contribute to `komorebi` please take the time to carefully\nread the guidelines below.\n\nPlease see [CONTRIBUTING.md](./CONTRIBUTING.md) for more information about how\ncode contributions to `komorebi` are licensed.\n\n## Commit hygiene\n\n- Flatten all `use` statements\n- Run `cargo +stable clippy` and ensure that all lints and suggestions have been addressed before committing\n- Run `cargo +nightly fmt --all` to ensure consistent formatting before committing\n- Use `git cz` with\n  the [Commitizen CLI](https://github.com/commitizen/cz-cli#conventional-commit-messages-as-a-global-utility) to prepare\n  commit messages\n- Provide **at least** one short sentence or paragraph in your commit message body to describe your thought process for\n  the changes being committed\n\n## PRs should contain only a single feature or bug fix\n\nIt is very difficult to review pull requests which touch multiple unrelated features and parts of the codebase.\n\nPlease do not submit pull requests like this; you will be asked to separate them into smaller PRs that deal only with\none feature or bug fix at a time.\n\nIf you are working on multiple features and bug fixes, I suggest that you cut a branch called `local-trunk`\nfrom `master` which you keep up to date, and rebase the various independent branches you are working on onto that branch\nif you want to test them together or create a build with everything integrated.\n\n## Refactors to the codebase must have prior approval\n\n`komorebi` is a mature codebase with an internal consistency and structure that has developed organically over close to\nhalf a decade.\n\nThere are [countless hours of live coding videos](https://youtube.com/@LGUG2Z) demonstrating work on this project and\nshowing new contributors how to do everything from basic tasks like implementing new `komorebic` commands to\ndistinguishing monitors by manufacturer hardware identifiers and video card ports.\n\nRefactors to the structure of the codebase are not taken lightly and require prior discussion and approval.\n\nPlease do not start refactoring the codebase with the expectation of having your changes integrated until you receive an\nexplicit approval or a request to do so.\n\nSimilarly, when implementing features and bug fixes, please stick to the structure of the codebase as much as possible\nand do not take this as an opportunity to do some \"refactoring along the way\".\n\nIt is extremely difficult to review PRs for features and bug fixes if they are lost in sweeping changes to the structure\nof the codebase.\n\n## Breaking changes to user-facing interfaces are unacceptable\n\nThis includes but is not limited to:\n\n- All `komorebic` commands\n- The `komorebi.json` schema\n- The [\n  `komorebi-application-specific-configuration`](https://github.com/LGUG2Z/komorebi-application-specific-configuration)\n  schema\n\nNo user should ever find that their configuration file has stopped working after upgrading to a new version\nof `komorebi`.\n\nMore often than not there are ways to reformulate changes that may initially seem like they require breaking user-facing\ninterfaces into additive changes.\n\nFor some inspiration please take a look\nat [this commit](https://github.com/LGUG2Z/komorebi/commit/e7d928a065eb63bb4ea1fb864c69c1cae8cc763b) which added the\nability for users to specify colours in `komorebi.json` in Hex format alongside RGB.\n\nThere is also a process in place for graceful, non-breaking, deprecation of configuration options that are no longer\nrequired.\n\n# Development\n\nIf you use IntelliJ, you should enable the following settings to ensure that code generated by macros is recognised by\nthe IDE for completions and navigation:\n\n- Set `Expand declarative macros`\n  to `Use new engine` under \"Settings > Langauges & Frameworks > Rust\"\n- Enable the following experimental features:\n    - `org.rust.cargo.evaluate.build.scripts`\n    - `org.rust.macros.proc`\n\n# Logs and Debugging\n\nLogs from `komorebi` will be appended to `%LOCALAPPDATA%/komorebi/komorebi.log`; this file is never rotated or\noverwritten, so it will keep growing until it is deleted by the user.\n\nWhenever running the `komorebic stop` command or sending a Ctrl-C signal to `komorebi` directly, the `komorebi` process\nensures that all hidden windows are restored before termination.\n\nIf however, you ever end up with windows that are hidden and cannot be restored, a list of window handles known\nto `komorebi` are stored and continuously updated in `%LOCALAPPDATA%/komorebi//komorebi.hwnd.json`.\n\n## Restoring Windows\n\nRunning `komorebic restore-windows` will read the list of window handles and forcibly restore them, regardless of\nwhether the main `komorebi` process is running.\n\n## Panics and Deadlocks\n\nIf `komorebi` ever stops responding, it is most likely either due to either a panic or a deadlock. In the case of a\npanic, this will be reported in the log. In the case of a deadlock, there will not be any errors in the log, but the\nprocess and the log will appear frozen.\n\nIf you believe you have encountered a deadlock, you can compile `komorebi` with `--features deadlock_detection` and try\nreproducing the deadlock again. This will check for deadlocks every 5 seconds in the background, and if a deadlock is\nfound, information about it will appear in the log which can be shared when opening an issue.\n\n# Window Manager State and Integrations\n\nThe current state of the window manager can be queried using the `komorebic state` command, which returns a JSON\nrepresentation of the `State` struct.\n\nThis may also be polled to build further integrations and widgets on top of.\n\n# Window Manager Event Subscriptions\n\n## Named Pipes\n\nIt is possible to subscribe to notifications of every `WindowManagerEvent` and `SocketMessage` handled\nby `komorebi` using [Named Pipes](https://docs.microsoft.com/en-us/windows/win32/ipc/named-pipes).\n\nFirst, your application must create a named pipe. Once the named pipe has been created, run the following command:\n\n```powershell\nkomorebic.exe subscribe-pipe <your pipe name>\n```\n\nNote that you do not have to include the full path of the named pipe, just the name.\n\nIf the named pipe exists, `komorebi` will start pushing JSON data of successfully handled events and messages:\n\n```json lines\n{\"event\":{\"type\":\"AddSubscriber\",\"content\":\"yasb\"},\"state\":{}}\n{\"event\":{\"type\":\"FocusWindow\",\"content\":\"Left\"},\"state\":{}}\n{\"event\":{\"type\":\"FocusChange\",\"content\":[\"SystemForeground\",{\"hwnd\":131444,\"title\":\"komorebi – README.md\",\"exe\":\"idea64.exe\",\"class\":\"SunAwtFrame\",\"rect\":{\"left\":13,\"top\":60,\"right\":1520,\"bottom\":1655}}]},\"state\":{}}\n{\"event\":{\"type\":\"MonitorPoll\",\"content\":[\"ObjectCreate\",{\"hwnd\":5572450,\"title\":\"OLEChannelWnd\",\"exe\":\"explorer.exe\",\"class\":\"OleMainThreadWndClass\",\"rect\":{\"left\":0,\"top\":0,\"right\":0,\"bottom\":0}}]},\"state\":{}}\n{\"event\":{\"type\":\"FocusWindow\",\"content\":\"Right\"},\"state\":{}}\n{\"event\":{\"type\":\"FocusChange\",\"content\":[\"SystemForeground\",{\"hwnd\":132968,\"title\":\"Windows PowerShell\",\"exe\":\"WindowsTerminal.exe\",\"class\":\"CASCADIA_HOSTING_WINDOW_CLASS\",\"rect\":{\"left\":1539,\"top\":60,\"right\":1520,\"bottom\":821}}]},\"state\":{}}\n{\"event\":{\"type\":\"FocusWindow\",\"content\":\"Down\"},\"state\":{}}\n{\"event\":{\"type\":\"FocusChange\",\"content\":[\"SystemForeground\",{\"hwnd\":329264,\"title\":\"den — Mozilla Firefox\",\"exe\":\"firefox.exe\",\"class\":\"MozillaWindowClass\",\"rect\":{\"left\":1539,\"top\":894,\"right\":1520,\"bottom\":821}}]},\"state\":{}}\n{\"event\":{\"type\":\"FocusWindow\",\"content\":\"Up\"},\"state\":{}}\n{\"event\":{\"type\":\"FocusChange\",\"content\":[\"SystemForeground\",{\"hwnd\":132968,\"title\":\"Windows PowerShell\",\"exe\":\"WindowsTerminal.exe\",\"class\":\"CASCADIA_HOSTING_WINDOW_CLASS\",\"rect\":{\"left\":1539,\"top\":60,\"right\":1520,\"bottom\":821}}]},\"state\":{}}\n```\n\nYou may then filter on the `type` key to listen to the events that you are interested in. For a full list of possible\nnotification types, refer to the enum variants of `WindowManagerEvent` in `komorebi` and `SocketMessage`\nin `komorebi::core`.\n\nBelow is an example of how you can subscribe to and filter on events using a named pipe in `nodejs`.\n\n```javascript\nconst { exec } = require(\"child_process\");\nconst net = require(\"net\");\n\nconst pipeName = \"\\\\\\\\.\\\\pipe\\\\komorebi-js\";\nconst server = net.createServer((stream) => {\n  console.log(\"Client connected\");\n\n  // Every time there is a workspace-related event, let's log the names of all\n  // workspaces on the currently focused monitor, and then log the name of the\n  // currently focused workspace on that monitor\n\n  stream.on(\"data\", (data) => {\n    let json = JSON.parse(data.toString());\n    let event = json.event;\n\n    if (event.type.includes(\"Workspace\")) {\n      let monitors = json.state.monitors;\n      let current_monitor = monitors.elements[monitors.focused];\n      let workspaces = monitors.elements[monitors.focused].workspaces;\n      let current_workspace = workspaces.elements[workspaces.focused];\n\n      console.log(\n        workspaces.elements\n          .map((workspace) => workspace.name)\n          .filter((name) => name !== null)\n      );\n      console.log(current_workspace.name);\n    }\n  });\n\n  stream.on(\"end\", () => {\n    console.log(\"Client disconnected\");\n  });\n});\n\nserver.listen(pipeName, () => {\n  console.log(\"Named pipe server listening\");\n});\n\nconst command = \"komorebic subscribe-pipe komorebi-js\";\n\nexec(command, (error, stdout, stderr) => {\n  if (error) {\n    console.error(`Error executing command: ${error}`);\n    return;\n  }\n});\n```\n\n## Unix Domain Sockets\n\nIt is possible to subscribe to notifications of every `WindowManagerEvent` and `SocketMessage` handled\nby `komorebi` using [Unix Domain Sockets](https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/).\n\nUDS are also the only mode of communication between `komorebi` and `komorebic`.\n\nFirst, your application must create a socket in `$ENV:LocalAppData\\komorebi`. Once the socket has been created, run the\nfollowing command:\n\n```powershell\nkomorebic.exe subscribe-socket <your socket name>\n```\n\nIf the socket exists, komorebi will start pushing JSON data of successfully handled events and messages as in the\nexample above in the Named Pipes section.\n\n## Rust Client\n\nAs of `v0.1.22` it is possible to use the `komorebi-client` crate to subscribe to notifications of\nevery `WindowManagerEvent` and `SocketMessage` handled by `komorebi` in a Rust codebase.\n\nBelow is a simple example of how to use `komorebi-client` in a basic Rust application.\n\n```rust\n// komorebi-client = { git = \"https://github.com/LGUG2Z/komorebi\" }\n\nuse anyhow::Result;\nuse komorebi_client::Notification;\nuse komorebi_client::NotificationEvent;\nuse komorebi_client::UnixListener;\nuse komorebi_client::WindowManagerEvent;\nuse std::io::BufRead;\nuse std::io::BufReader;\nuse std::io::Read;\n\npub fn main() -> anyhow::Result<()> {\n  let socket = komorebi_client::subscribe(NAME)?;\n\n  for incoming in socket.incoming() {\n    match incoming {\n      Ok(data) => {\n        let reader = BufReader::new(data.try_clone()?);\n\n        for line in reader.lines().flatten() {\n          let notification: Notification = match serde_json::from_str(&line) {\n            Ok(notification) => notification,\n            Err(error) => {\n              log::debug!(\"discarding malformed komorebi notification: {error}\");\n              continue;\n            }\n          };\n\n          // match and filter on desired notifications\n        }\n      }\n      Err(error) => {\n        log::debug!(\"{error}\");\n      }\n    }\n  }\n\n}\n```\n\nA read-world example can be found\nin [komokana](https://github.com/LGUG2Z/komokana/blob/feature/komorebi-uds/src/main.rs).\n\n## Subscription Event Notification Schema\n\nA [JSON Schema](https://json-schema.org/) of the event notifications emitted to subscribers can be generated with\nthe `komorebic notification-schema` command. The output of this command can be redirected to the clipboard or a file,\nwhich can be used with services such as [Quicktype](https://app.quicktype.io/) to generate type definitions in different\nprogramming languages.\n\n## Communication over TCP\n\nA TCP listener can optionally be exposed on a port of your choosing with the `--tcp-port=N` flag. If this flag is not\nprovided to `komorebi` or `komorebic start`, no TCP listener will be created.\n\nOnce created, your client may send\nany [SocketMessage](https://github.com/LGUG2Z/komorebi/blob/master/komorebi/src/core/mod.rs#L37) to `komorebi` in the\nsame way that `komorebic` would.\n\nThis can be used if you would like to create your own alternative to `komorebic` which incorporates scripting and\nvarious middleware layers, and similarly it can be used if you would like to integrate `komorebi` with\na [custom input handler](https://github.com/LGUG2Z/komorebi/issues/176#issue-1302643961).\n\nIf a client sends an unrecognized message, it will be disconnected and have to reconnect before trying to communicate\nagain.\n\n## Socket Message Schema\n\nA [JSON Schema](https://json-schema.org/) of socket messages used to send instructions to `komorebi` can be generated\nwith the `komorebic socket-schema` command. The output of this command can be redirected to the clipboard or a file,\nwhich can be used with services such as [Quicktype](https://app.quicktype.io/) to generate type definitions in different\nprogramming languages.\n\n# Appreciations\n\n- First and foremost, thank you to my wife, both for naming this project and for her patience throughout its\n  never-ending development\n\n- Thank you to [@sitiom](https://github.com/sitiom) for\n  being [an exemplary open source community leader](https://jeezy.substack.com/p/the-open-source-contributions-i-appreciate)\n\n- Thank you to the developers of [nog](https://github.com/TimUntersberger/nog) who came before me and whose work taught\n  me more than I can ever hope to repay\n\n- Thank you to the developers of [GlazeWM](https://github.com/lars-berger/GlazeWM) for pushing the boundaries of tiling\n  window management on Windows with me and having an excellent spirit of collaboration\n\n- Thank you to [@Ciantic](https://github.com/Ciantic) for helping me bring\n  the [hidden Virtual Desktops cloaking function](https://github.com/Ciantic/AltTabAccessor/issues/1) to `komorebi`\n"
  },
  {
    "path": "check_schema_docs.py",
    "content": "#!/usr/bin/env python3\n\"\"\"\nCheck schema.json and schema.bar.json for missing docstrings and map them to Rust source files.\n\nThis script analyzes the generated JSON schemas and identifies:\n1. Type definitions ($defs) missing top-level descriptions\n2. Enum variants missing descriptions (in oneOf/anyOf)\n3. Enum variants missing titles (object variants in oneOf/anyOf)\n4. Struct properties missing descriptions\n5. Top-level schema properties missing descriptions\n\nFor each missing docstring, it attempts to find the corresponding Rust source\nfile and line number where the docstring should be added.\n\"\"\"\n\nimport json\nimport re\nimport sys\nfrom dataclasses import dataclass\nfrom pathlib import Path\nfrom typing import Optional\n\n\n@dataclass\nclass MissingDoc:\n    type_name: str\n    kind: str  # \"type\", \"variant\", \"property\", \"variant_title\"\n    item_name: Optional[str]  # variant or property name\n    rust_file: Optional[str] = None\n    rust_line: Optional[int] = None\n\n    def __str__(self):\n        location = \"\"\n        if self.rust_file and self.rust_line:\n            location = f\" -> {self.rust_file}:{self.rust_line}\"\n        elif self.rust_file:\n            location = f\" -> {self.rust_file}\"\n\n        if self.kind == \"type\":\n            return f\"[TYPE] {self.type_name}{location}\"\n        elif self.kind == \"variant\":\n            return f\"[VARIANT] {self.type_name}::{self.item_name}{location}\"\n        elif self.kind == \"variant_title\":\n            return f\"[VARIANT_TITLE] {self.type_name}::{self.item_name}{location}\"\n        else:\n            return f\"[PROPERTY] {self.type_name}.{self.item_name}{location}\"\n\n\n@dataclass\nclass SchemaConfig:\n    \"\"\"Configuration for a schema to check.\"\"\"\n\n    schema_file: str\n    search_paths: list[str]\n    display_name: str\n\n\ndef find_rust_definition(\n    type_name: str, item_name: Optional[str], kind: str, search_paths: list[Path]\n) -> tuple[Optional[str], Optional[int]]:\n    \"\"\"Find the Rust file and line number for a type/variant/property definition.\"\"\"\n\n    if kind == \"type\":\n        patterns = [\n            rf\"pub\\s+enum\\s+{type_name}\\b\",\n            rf\"pub\\s+struct\\s+{type_name}\\b\",\n        ]\n    elif kind in (\"variant\", \"variant_title\"):\n        patterns = [\n            rf\"^\\s*{re.escape(item_name)}\\s*[,\\(\\{{]\",\n            rf\"^\\s*{re.escape(item_name)}\\s*$\",\n            rf\"^\\s*#\\[.*\\]\\s*\\n\\s*{re.escape(item_name)}\\b\",\n        ]\n    else:  # property\n        patterns = [rf\"pub\\s+{re.escape(item_name)}\\s*:\"]\n\n    for search_path in search_paths:\n        for rust_file in search_path.rglob(\"*.rs\"):\n            try:\n                content = rust_file.read_text()\n                lines = content.split(\"\\n\")\n\n                if kind == \"type\":\n                    for pattern in patterns:\n                        for i, line in enumerate(lines):\n                            if re.search(pattern, line):\n                                return str(rust_file), i + 1\n\n                elif kind in (\"variant\", \"variant_title\", \"property\"):\n                    parent_pattern = rf\"pub\\s+(?:enum|struct)\\s+{type_name}\\b\"\n                    in_type = False\n                    brace_count = 0\n                    found_open_brace = False\n\n                    for i, line in enumerate(lines):\n                        if re.search(parent_pattern, line):\n                            in_type = True\n                            brace_count = 0\n                            found_open_brace = False\n\n                        if in_type:\n                            if \"{\" in line:\n                                found_open_brace = True\n                            brace_count += line.count(\"{\") - line.count(\"}\")\n\n                            for pattern in patterns:\n                                if re.search(pattern, line):\n                                    return str(rust_file), i + 1\n\n                            if found_open_brace and brace_count <= 0:\n                                in_type = False\n            except Exception:\n                continue\n\n    return None, None\n\n\ndef _get_variant_identifier(variant: dict) -> str:\n    \"\"\"Extract a meaningful identifier for a variant.\n\n    Tries to find the best identifier by checking:\n    1. A top-level const value (e.g., {\"const\": \"Linear\"})\n    2. A property with a const value (e.g., {\"kind\": {\"const\": \"Bar\"}})\n    3. The first required property name\n    4. The type field\n    5. Falls back to \"unknown\"\n    \"\"\"\n    # Check for top-level const value (simple enum variant)\n    if \"const\" in variant:\n        return str(variant[\"const\"])\n\n    properties = variant.get(\"properties\", {})\n\n    # Check for a property with a const value (common pattern for tagged enums)\n    for prop_name, prop_def in properties.items():\n        if isinstance(prop_def, dict) and \"const\" in prop_def:\n            return str(prop_def[\"const\"])\n\n    # Fall back to first required property name\n    required = variant.get(\"required\", [])\n    if required:\n        return str(required[0])\n\n    # Fall back to type\n    if \"type\" in variant:\n        return str(variant[\"type\"])\n\n    return \"unknown\"\n\n\ndef check_type_description(type_name: str, type_def: dict) -> list[MissingDoc]:\n    \"\"\"Check if a type definition has proper documentation.\"\"\"\n    missing = []\n    has_top_description = \"description\" in type_def\n\n    # Always check for top-level type description first\n    # (except for types that are purely references or have special handling)\n    needs_type_description = True\n\n    # Check oneOf variants (tagged enums with variant descriptions)\n    if \"oneOf\" in type_def:\n        # oneOf types should have a top-level description\n        if not has_top_description:\n            missing.append(MissingDoc(type_name, \"type\", None, None, None))\n\n        for variant in type_def[\"oneOf\"]:\n            # Case 1: Simple const variant (e.g., {\"const\": \"Swap\", \"description\": \"...\"})\n            variant_name = variant.get(\"const\") or variant.get(\"title\")\n            if variant_name and \"description\" not in variant:\n                missing.append(\n                    MissingDoc(type_name, \"variant\", str(variant_name), None, None)\n                )\n\n            # Case 2: String enum inside oneOf (e.g., {\"type\": \"string\", \"enum\": [...]})\n            # These variants don't have individual descriptions in the schema\n            if \"enum\" in variant and variant.get(\"type\") == \"string\":\n                for enum_variant in variant[\"enum\"]:\n                    missing.append(\n                        MissingDoc(type_name, \"variant\", str(enum_variant), None, None)\n                    )\n\n            # Case 3: Object variant with properties (e.g., CubicBezier)\n            if \"properties\" in variant and \"description\" not in variant:\n                for prop_name in variant.get(\"required\", []):\n                    missing.append(\n                        MissingDoc(type_name, \"variant\", str(prop_name), None, None)\n                    )\n\n            # Case 4: Object variant missing title (needed for schema UI display)\n            # Object variants should have a title or const for proper display in editors\n            if (\n                \"properties\" in variant\n                and \"title\" not in variant\n                and \"const\" not in variant\n            ):\n                # Try to find a good identifier for the variant (for display only)\n                variant_id = _get_variant_identifier(variant)\n                missing.append(\n                    MissingDoc(type_name, \"variant_title\", str(variant_id), None, None)\n                )\n\n    # Check anyOf variants - check each variant individually\n    elif \"anyOf\" in type_def:\n        # anyOf types should have a top-level description\n        if not has_top_description:\n            missing.append(MissingDoc(type_name, \"type\", None, None, None))\n\n        # Check each variant for description (skip pure $ref and null types)\n        for variant in type_def[\"anyOf\"]:\n            # Skip null type variants (used for Option<T>)\n            if variant.get(\"type\") == \"null\":\n                continue\n            # Skip pure $ref variants (the referenced type is checked separately)\n            if \"$ref\" in variant and len(variant) == 1:\n                continue\n            # Skip $ref variants that have a description\n            if \"$ref\" in variant and \"description\" in variant:\n                continue\n            # Variant with $ref but no description\n            if \"$ref\" in variant and \"description\" not in variant:\n                # Extract the type name from the $ref\n                ref_name = variant[\"$ref\"].split(\"/\")[-1]\n                missing.append(MissingDoc(type_name, \"variant\", ref_name, None, None))\n            # Non-ref variant without description\n            elif \"description\" not in variant and \"$ref\" not in variant:\n                # Try to identify the variant by its type or const\n                variant_id = variant.get(\"const\") or variant.get(\"type\") or \"unknown\"\n                missing.append(\n                    MissingDoc(type_name, \"variant\", str(variant_id), None, None)\n                )\n\n            # Check for missing title on object variants in anyOf\n            if (\n                \"properties\" in variant\n                and \"title\" not in variant\n                and \"const\" not in variant\n            ):\n                variant_id = _get_variant_identifier(variant)\n                missing.append(\n                    MissingDoc(type_name, \"variant_title\", str(variant_id), None, None)\n                )\n\n    # Check simple string enums (no oneOf means no variant descriptions possible in schema)\n    elif \"enum\" in type_def:\n        if not has_top_description:\n            missing.append(MissingDoc(type_name, \"type\", None, None, None))\n        # Each enum variant needs a docstring - these can't have descriptions in simple enum format\n        for variant in type_def[\"enum\"]:\n            missing.append(MissingDoc(type_name, \"variant\", str(variant), None, None))\n\n    # Check struct properties\n    elif \"properties\" in type_def:\n        # Structs should always have a top-level description\n        if not has_top_description:\n            missing.append(MissingDoc(type_name, \"type\", None, None, None))\n\n        for prop_name, prop_def in type_def[\"properties\"].items():\n            if \"description\" not in prop_def:\n                missing.append(MissingDoc(type_name, \"property\", prop_name, None, None))\n\n    # Simple type without description (like PathBuf, Hex)\n    elif not has_top_description:\n        # Only flag if it has a concrete type (not just a $ref)\n        if type_def.get(\"type\") is not None:\n            missing.append(MissingDoc(type_name, \"type\", None, None, None))\n\n    return missing\n\n\ndef check_top_level_properties(schema: dict, root_type_name: str) -> list[MissingDoc]:\n    \"\"\"Check top-level schema properties for missing descriptions.\"\"\"\n    missing = []\n    properties = schema.get(\"properties\", {})\n\n    for prop_name, prop_def in properties.items():\n        if \"description\" not in prop_def:\n            missing.append(\n                MissingDoc(root_type_name, \"property\", prop_name, None, None)\n            )\n\n    return missing\n\n\ndef check_schema(\n    schema_path: Path,\n    search_paths: list[Path],\n    project_root: Path,\n    display_name: str,\n) -> tuple[list[MissingDoc], int]:\n    \"\"\"Check a single schema file and return missing docs and exit code.\"\"\"\n    if not schema_path.exists():\n        print(f\"Error: {schema_path.name} not found at {schema_path}\")\n        return [], 1\n\n    with open(schema_path) as f:\n        schema = json.load(f)\n\n    all_missing: list[MissingDoc] = []\n\n    # Check top-level schema properties\n    root_type_name = schema.get(\"title\", \"Root\")\n    all_missing.extend(check_top_level_properties(schema, root_type_name))\n\n    # Check all type definitions\n    for type_name, type_def in sorted(schema.get(\"$defs\", {}).items()):\n        # Skip PerAnimationPrefixConfig2/3 as they're generated variants\n        if (\n            type_name.startswith(\"PerAnimationPrefixConfig\")\n            and type_name != \"PerAnimationPrefixConfig\"\n        ):\n            continue\n        all_missing.extend(check_type_description(type_name, type_def))\n\n    # Find Rust source locations\n    print(f\"Scanning Rust source files for {display_name}...\", file=sys.stderr)\n    for doc in all_missing:\n        doc.rust_file, doc.rust_line = find_rust_definition(\n            doc.type_name, doc.item_name, doc.kind, search_paths\n        )\n        if doc.rust_file:\n            try:\n                doc.rust_file = str(Path(doc.rust_file).relative_to(project_root))\n            except ValueError:\n                pass\n\n    return all_missing, 0\n\n\ndef print_results(all_missing: list[MissingDoc], display_name: str) -> None:\n    \"\"\"Print the results for a schema check.\"\"\"\n    # Group by file\n    by_file: dict[str, list[MissingDoc]] = {}\n    external: list[MissingDoc] = []\n\n    for doc in all_missing:\n        if doc.rust_file:\n            by_file.setdefault(doc.rust_file, []).append(doc)\n        else:\n            external.append(doc)\n\n    # Print summary\n    print(\"\\n\" + \"=\" * 70)\n    print(f\"MISSING DOCSTRINGS IN SCHEMA ({display_name})\")\n    print(\"=\" * 70)\n\n    type_count = sum(1 for d in all_missing if d.kind == \"type\")\n    variant_count = sum(1 for d in all_missing if d.kind == \"variant\")\n    variant_title_count = sum(1 for d in all_missing if d.kind == \"variant_title\")\n    prop_count = sum(1 for d in all_missing if d.kind == \"property\")\n\n    print(f\"\\nTotal: {len(all_missing)} missing docstrings/titles\")\n    print(f\"  - {type_count} types\")\n    print(f\"  - {variant_count} variants\")\n    print(f\"  - {variant_title_count} variant titles\")\n    print(f\"  - {prop_count} properties\")\n\n    # Print by file\n    for rust_file in sorted(by_file.keys()):\n        docs = sorted(by_file[rust_file], key=lambda d: d.rust_line or 0)\n        print(f\"\\n{rust_file}:\")\n        print(\"-\" * len(rust_file))\n        for doc in docs:\n            print(f\"  {doc}\")\n\n    # Print external items (types not found in source)\n    if external:\n        print(f\"\\nExternal/Unknown location:\")\n        print(\"-\" * 25)\n        for doc in external:\n            print(f\"  {doc}\")\n\n    print(\"\\n\" + \"=\" * 70)\n\n\ndef main():\n    project_root = Path.cwd()\n\n    # Define schemas to check with their respective search paths\n    schemas = [\n        SchemaConfig(\n            schema_file=\"schema.json\",\n            search_paths=[\"komorebi/src\", \"komorebi-themes/src\"],\n            display_name=\"komorebi\",\n        ),\n        SchemaConfig(\n            schema_file=\"schema.bar.json\",\n            search_paths=[\"komorebi-bar/src\", \"komorebi-themes/src\"],\n            display_name=\"komorebi-bar\",\n        ),\n    ]\n\n    total_missing = 0\n    has_errors = False\n\n    for schema_config in schemas:\n        schema_path = project_root / schema_config.schema_file\n        search_paths = [\n            project_root / p\n            for p in schema_config.search_paths\n            if (project_root / p).exists()\n        ]\n\n        missing, error_code = check_schema(\n            schema_path,\n            search_paths,\n            project_root,\n            schema_config.display_name,\n        )\n\n        if error_code != 0:\n            has_errors = True\n            continue\n\n        print_results(missing, schema_config.display_name)\n        total_missing += len(missing)\n\n    # Print combined summary\n    if len(schemas) > 1:\n        print(\"\\n\" + \"=\" * 70)\n        print(\"COMBINED SUMMARY\")\n        print(\"=\" * 70)\n        print(f\"Total missing docstrings across all schemas: {total_missing}\")\n        print(\"=\" * 70)\n\n    if has_errors:\n        return 1\n\n    return 1 if total_missing > 0 else 0\n\n\nif __name__ == \"__main__\":\n    sys.exit(main())\n"
  },
  {
    "path": "deny.toml",
    "content": "[graph]\ntargets = [\n  \"x86_64-pc-windows-msvc\",\n  \"i686-pc-windows-msvc\",\n  \"aarch64-pc-windows-msvc\",\n]\nall-features = false\nno-default-features = false\n\n[output]\nfeature-depth = 1\n\n[advisories]\nignore = [\n  { id = \"RUSTSEC-2020-0016\", reason = \"local tcp connectivity is an opt-in feature, and there is no upgrade path for TcpStreamExt\" },\n  { id = \"RUSTSEC-2024-0436\", reason = \"paste being unmaintained is not an issue in our use\" },\n  { id = \"RUSTSEC-2024-0320\", reason = \"not using any yaml features from this library\" },\n  { id = \"RUSTSEC-2025-0056\", reason = \"only used for colour palette generation\" },\n]\n\n[licenses]\nallow = [\n  \"0BSD\",\n  \"Apache-2.0\",\n  \"Apache-2.0 WITH LLVM-exception\",\n  \"Artistic-2.0\",\n  \"BSD-2-Clause\",\n  \"BSD-3-Clause\",\n  \"BSL-1.0\",\n  \"CC0-1.0\",\n  \"ISC\",\n  \"MIT\",\n  \"MIT-0\",\n  \"MPL-2.0\",\n  \"OFL-1.1\",\n  \"Ubuntu-font-1.0\",\n  \"Unicode-3.0\",\n  \"Zlib\",\n  \"LicenseRef-Komorebi-2.0\",\n]\nconfidence-threshold = 0.8\n\n[[licenses.clarify]]\ncrate = \"komorebi\"\nexpression = \"LicenseRef-Komorebi-2.0\"\nlicense-files = []\n\n[[licenses.clarify]]\ncrate = \"komorebi-client\"\nexpression = \"LicenseRef-Komorebi-2.0\"\nlicense-files = []\n\n[[licenses.clarify]]\ncrate = \"komorebi-layouts\"\nexpression = \"LicenseRef-Komorebi-2.0\"\nlicense-files = []\n\n[[licenses.clarify]]\ncrate = \"komorebic\"\nexpression = \"LicenseRef-Komorebi-2.0\"\nlicense-files = []\n\n[[licenses.clarify]]\ncrate = \"komorebic-no-console\"\nexpression = \"LicenseRef-Komorebi-2.0\"\nlicense-files = []\n\n[[licenses.clarify]]\ncrate = \"komorebi-themes\"\nexpression = \"LicenseRef-Komorebi-2.0\"\nlicense-files = []\n\n[[licenses.clarify]]\ncrate = \"komorebi-gui\"\nexpression = \"LicenseRef-Komorebi-2.0\"\nlicense-files = []\n\n[[licenses.clarify]]\ncrate = \"komorebi-bar\"\nexpression = \"LicenseRef-Komorebi-2.0\"\nlicense-files = []\n\n[[licenses.clarify]]\ncrate = \"komorebi-shortcuts\"\nexpression = \"LicenseRef-Komorebi-2.0\"\nlicense-files = []\n\n[[licenses.clarify]]\ncrate = \"whkd-core\"\nexpression = \"LicenseRef-Komorebi-2.0\"\nlicense-files = []\n\n[[licenses.clarify]]\ncrate = \"whkd-parser\"\nexpression = \"LicenseRef-Komorebi-2.0\"\nlicense-files = []\n\n[[licenses.clarify]]\ncrate = \"base16-egui-themes\"\nexpression = \"MIT\"\nlicense-files = []\n\n[[licenses.clarify]]\ncrate = \"win32-display-data\"\nexpression = \"0BSD\"\nlicense-files = []\n\n[bans]\nmultiple-versions = \"allow\"\nwildcards = \"allow\"\nhighlight = \"all\"\nworkspace-default-features = \"allow\"\nexternal-default-features = \"allow\"\n\n[sources]\nunknown-registry = \"deny\"\nunknown-git = \"deny\"\nallow-registry = [\"https://github.com/rust-lang/crates.io-index\"]\nallow-git = [\n  \"https://github.com/LGUG2Z/base16-egui-themes\",\n  \"https://github.com/LGUG2Z/windows-icons\",\n  \"https://github.com/LGUG2Z/win32-display-data\",\n  \"https://github.com/LGUG2Z/flavours\",\n  \"https://github.com/LGUG2Z/base16_color_scheme\",\n  \"https://github.com/LGUG2Z/whkd\",\n  \"https://github.com/LGUG2Z/catppuccin-egui\",\n  \"https://github.com/amPerl/egui-phosphor\",\n]\n"
  },
  {
    "path": "dependencies.json",
    "content": "{\n  \"licenses\": [\n    [\n      \"0BSD\",\n      [\n        \"adler 1.0.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"adler2 2.0.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"win32-display-data 0.1.0 git+https://github.com/LGUG2Z/win32-display-data?rev=8c42d8db257d30fe95bc98c2e5cd8f75da861021\"\n      ]\n    ],\n    [\n      \"Apache-2.0\",\n      [\n        \"ab_glyph 0.2.32 registry+https://github.com/rust-lang/crates.io-index\",\n        \"ab_glyph_rasterizer 0.1.10 registry+https://github.com/rust-lang/crates.io-index\",\n        \"accesskit 0.21.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"accesskit_consumer 0.31.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"accesskit_windows 0.29.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"accesskit_winit 0.29.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"adler 1.0.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"adler2 2.0.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"ahash 0.8.12 registry+https://github.com/rust-lang/crates.io-index\",\n        \"aligned 0.4.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"allocator-api2 0.2.21 registry+https://github.com/rust-lang/crates.io-index\",\n        \"anstream 0.6.21 registry+https://github.com/rust-lang/crates.io-index\",\n        \"anstyle 1.0.13 registry+https://github.com/rust-lang/crates.io-index\",\n        \"anstyle-parse 0.2.7 registry+https://github.com/rust-lang/crates.io-index\",\n        \"anstyle-query 1.1.5 registry+https://github.com/rust-lang/crates.io-index\",\n        \"anstyle-wincon 3.0.11 registry+https://github.com/rust-lang/crates.io-index\",\n        \"anyhow 1.0.101 registry+https://github.com/rust-lang/crates.io-index\",\n        \"approx 0.3.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"arboard 3.6.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"arrayvec 0.7.6 registry+https://github.com/rust-lang/crates.io-index\",\n        \"as-slice 0.2.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"atomic-waker 1.1.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"autocfg 1.5.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"backtrace 0.3.76 registry+https://github.com/rust-lang/crates.io-index\",\n        \"backtrace-ext 0.2.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"base16_color_scheme 0.3.2 git+https://github.com/LGUG2Z/base16_color_scheme\",\n        \"base64 0.22.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"beef 0.5.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"bit_field 0.10.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"bitflags 1.3.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"bitflags 2.11.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"bitstream-io 4.9.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"block-buffer 0.10.4 registry+https://github.com/rust-lang/crates.io-index\",\n        \"bytemuck 1.25.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"bytemuck_derive 1.10.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"cc 1.2.56 registry+https://github.com/rust-lang/crates.io-index\",\n        \"cfg-if 0.1.10 registry+https://github.com/rust-lang/crates.io-index\",\n        \"cfg-if 1.0.4 registry+https://github.com/rust-lang/crates.io-index\",\n        \"chrono 0.4.43 registry+https://github.com/rust-lang/crates.io-index\",\n        \"chrono-tz 0.10.4 registry+https://github.com/rust-lang/crates.io-index\",\n        \"clap 4.5.58 registry+https://github.com/rust-lang/crates.io-index\",\n        \"clap_builder 4.5.58 registry+https://github.com/rust-lang/crates.io-index\",\n        \"clap_derive 4.5.55 registry+https://github.com/rust-lang/crates.io-index\",\n        \"clap_lex 1.0.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"color-eyre 0.6.5 registry+https://github.com/rust-lang/crates.io-index\",\n        \"color-spantrace 0.3.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"colorchoice 1.0.4 registry+https://github.com/rust-lang/crates.io-index\",\n        \"core2 0.4.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"cpufeatures 0.2.17 registry+https://github.com/rust-lang/crates.io-index\",\n        \"crc32fast 1.5.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"crossbeam-channel 0.5.15 registry+https://github.com/rust-lang/crates.io-index\",\n        \"crossbeam-deque 0.8.6 registry+https://github.com/rust-lang/crates.io-index\",\n        \"crossbeam-epoch 0.9.18 registry+https://github.com/rust-lang/crates.io-index\",\n        \"crossbeam-utils 0.8.21 registry+https://github.com/rust-lang/crates.io-index\",\n        \"crypto-common 0.1.7 registry+https://github.com/rust-lang/crates.io-index\",\n        \"ctrlc 3.5.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"cursor-icon 1.2.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"curve25519-dalek-derive 0.1.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"deflate 0.8.6 registry+https://github.com/rust-lang/crates.io-index\",\n        \"deranged 0.5.6 registry+https://github.com/rust-lang/crates.io-index\",\n        \"digest 0.10.7 registry+https://github.com/rust-lang/crates.io-index\",\n        \"dirs 3.0.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"dirs 4.0.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"dirs 6.0.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"dirs-sys 0.3.7 registry+https://github.com/rust-lang/crates.io-index\",\n        \"dirs-sys 0.5.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"displaydoc 0.2.5 registry+https://github.com/rust-lang/crates.io-index\",\n        \"document-features 0.2.12 registry+https://github.com/rust-lang/crates.io-index\",\n        \"dpi 0.1.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"dunce 1.0.5 registry+https://github.com/rust-lang/crates.io-index\",\n        \"dyn-clone 1.0.20 registry+https://github.com/rust-lang/crates.io-index\",\n        \"ecolor 0.33.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"ed25519 2.2.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"eframe 0.33.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"egui 0.33.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"egui-phosphor 0.10.0 git+https://github.com/amPerl/egui-phosphor?rev=d13688738478ecd12b426e3e74c59d6577a85b59\",\n        \"egui-winit 0.33.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"egui_extras 0.33.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"egui_glow 0.33.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"either 1.15.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"emath 0.33.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"encoding_rs 0.8.35 registry+https://github.com/rust-lang/crates.io-index\",\n        \"enum-map 2.7.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"enum-map-derive 0.17.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"env_home 0.1.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"epaint 0.33.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"epaint_default_fonts 0.33.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"equivalent 1.0.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"eyre 0.6.12 registry+https://github.com/rust-lang/crates.io-index\",\n        \"fastrand 2.3.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"fdeflate 0.3.7 registry+https://github.com/rust-lang/crates.io-index\",\n        \"filetime 0.2.27 registry+https://github.com/rust-lang/crates.io-index\",\n        \"find-msvc-tools 0.1.9 registry+https://github.com/rust-lang/crates.io-index\",\n        \"flate2 1.1.9 registry+https://github.com/rust-lang/crates.io-index\",\n        \"fnv 1.0.7 registry+https://github.com/rust-lang/crates.io-index\",\n        \"form_urlencoded 1.2.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"futures 0.3.31 registry+https://github.com/rust-lang/crates.io-index\",\n        \"futures-channel 0.3.31 registry+https://github.com/rust-lang/crates.io-index\",\n        \"futures-core 0.3.31 registry+https://github.com/rust-lang/crates.io-index\",\n        \"futures-executor 0.3.31 registry+https://github.com/rust-lang/crates.io-index\",\n        \"futures-io 0.3.31 registry+https://github.com/rust-lang/crates.io-index\",\n        \"futures-macro 0.3.31 registry+https://github.com/rust-lang/crates.io-index\",\n        \"futures-sink 0.3.31 registry+https://github.com/rust-lang/crates.io-index\",\n        \"futures-task 0.3.31 registry+https://github.com/rust-lang/crates.io-index\",\n        \"futures-util 0.3.31 registry+https://github.com/rust-lang/crates.io-index\",\n        \"getrandom 0.1.16 registry+https://github.com/rust-lang/crates.io-index\",\n        \"getrandom 0.2.17 registry+https://github.com/rust-lang/crates.io-index\",\n        \"getrandom 0.3.4 registry+https://github.com/rust-lang/crates.io-index\",\n        \"getrandom 0.4.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"gif 0.11.4 registry+https://github.com/rust-lang/crates.io-index\",\n        \"gif 0.14.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"git2 0.20.4 registry+https://github.com/rust-lang/crates.io-index\",\n        \"gl_generator 0.14.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"glob 0.3.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"glow 0.16.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"glutin 0.32.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"glutin_egl_sys 0.7.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"glutin_wgl_sys 0.6.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"half 2.7.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"hashbrown 0.12.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"hashbrown 0.14.5 registry+https://github.com/rust-lang/crates.io-index\",\n        \"hashbrown 0.15.5 registry+https://github.com/rust-lang/crates.io-index\",\n        \"hashbrown 0.16.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"heck 0.5.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"hex 0.4.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"hex_color 3.0.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"hotwatch 0.5.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"http 1.4.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"httparse 1.10.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"hyper-tls 0.6.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"iana-time-zone 0.1.65 registry+https://github.com/rust-lang/crates.io-index\",\n        \"ident_case 1.0.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"idna 1.1.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"idna_adapter 1.2.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"image 0.25.9 registry+https://github.com/rust-lang/crates.io-index\",\n        \"image-webp 0.2.4 registry+https://github.com/rust-lang/crates.io-index\",\n        \"imgref 1.12.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"indenter 0.3.4 registry+https://github.com/rust-lang/crates.io-index\",\n        \"indexmap 1.9.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"indexmap 2.13.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"ipnet 2.11.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"iri-string 0.7.10 registry+https://github.com/rust-lang/crates.io-index\",\n        \"is_debug 1.1.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"is_terminal_polyfill 1.70.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"itertools 0.14.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"itoa 1.0.17 registry+https://github.com/rust-lang/crates.io-index\",\n        \"jobserver 0.1.34 registry+https://github.com/rust-lang/crates.io-index\",\n        \"jpeg-decoder 0.1.22 registry+https://github.com/rust-lang/crates.io-index\",\n        \"khronos_api 3.1.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"lazy_static 1.5.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"libc 0.2.182 registry+https://github.com/rust-lang/crates.io-index\",\n        \"libgit2-sys 0.18.3+1.9.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"libz-sys 1.1.23 registry+https://github.com/rust-lang/crates.io-index\",\n        \"linked-hash-map 0.5.6 registry+https://github.com/rust-lang/crates.io-index\",\n        \"litrs 1.0.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"lock_api 0.4.14 registry+https://github.com/rust-lang/crates.io-index\",\n        \"log 0.4.29 registry+https://github.com/rust-lang/crates.io-index\",\n        \"logos 0.14.4 registry+https://github.com/rust-lang/crates.io-index\",\n        \"logos-codegen 0.14.4 registry+https://github.com/rust-lang/crates.io-index\",\n        \"logos-derive 0.14.4 registry+https://github.com/rust-lang/crates.io-index\",\n        \"miette 7.6.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"miette-derive 7.6.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"mime 0.3.17 registry+https://github.com/rust-lang/crates.io-index\",\n        \"minimal-lexical 0.2.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"miniz_oxide 0.4.4 registry+https://github.com/rust-lang/crates.io-index\",\n        \"miniz_oxide 0.8.9 registry+https://github.com/rust-lang/crates.io-index\",\n        \"miow 0.6.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"moxcms 0.7.11 registry+https://github.com/rust-lang/crates.io-index\",\n        \"native-tls 0.2.16 registry+https://github.com/rust-lang/crates.io-index\",\n        \"net2 0.2.39 registry+https://github.com/rust-lang/crates.io-index\",\n        \"nohash-hasher 0.2.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"ntapi 0.4.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"num 0.4.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"num-bigint 0.4.6 registry+https://github.com/rust-lang/crates.io-index\",\n        \"num-complex 0.4.6 registry+https://github.com/rust-lang/crates.io-index\",\n        \"num-conv 0.2.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"num-derive 0.4.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"num-integer 0.1.46 registry+https://github.com/rust-lang/crates.io-index\",\n        \"num-iter 0.1.45 registry+https://github.com/rust-lang/crates.io-index\",\n        \"num-rational 0.3.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"num-rational 0.4.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"num-traits 0.2.19 registry+https://github.com/rust-lang/crates.io-index\",\n        \"object 0.37.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"once_cell 1.21.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"once_cell_polyfill 1.70.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"owned_ttf_parser 0.25.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"palette 0.5.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"palette_derive 0.5.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"parking_lot 0.12.5 registry+https://github.com/rust-lang/crates.io-index\",\n        \"parking_lot_core 0.9.12 registry+https://github.com/rust-lang/crates.io-index\",\n        \"paste 1.0.15 registry+https://github.com/rust-lang/crates.io-index\",\n        \"pastey 0.1.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"percent-encoding 2.3.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"pin-project-lite 0.2.16 registry+https://github.com/rust-lang/crates.io-index\",\n        \"pin-utils 0.1.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"pkg-config 0.3.32 registry+https://github.com/rust-lang/crates.io-index\",\n        \"png 0.16.8 registry+https://github.com/rust-lang/crates.io-index\",\n        \"png 0.18.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"powerfmt 0.2.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"ppv-lite86 0.2.21 registry+https://github.com/rust-lang/crates.io-index\",\n        \"proc-macro2 1.0.106 registry+https://github.com/rust-lang/crates.io-index\",\n        \"profiling 1.0.17 registry+https://github.com/rust-lang/crates.io-index\",\n        \"profiling-procmacros 1.0.17 registry+https://github.com/rust-lang/crates.io-index\",\n        \"psm 0.1.30 registry+https://github.com/rust-lang/crates.io-index\",\n        \"pxfm 0.1.27 registry+https://github.com/rust-lang/crates.io-index\",\n        \"qoi 0.4.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"quick-error 2.0.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"quote 1.0.44 registry+https://github.com/rust-lang/crates.io-index\",\n        \"rand 0.7.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"rand 0.8.5 registry+https://github.com/rust-lang/crates.io-index\",\n        \"rand 0.9.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"rand_chacha 0.2.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"rand_chacha 0.3.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"rand_chacha 0.9.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"rand_core 0.5.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"rand_core 0.6.4 registry+https://github.com/rust-lang/crates.io-index\",\n        \"rand_core 0.9.5 registry+https://github.com/rust-lang/crates.io-index\",\n        \"rand_pcg 0.2.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"raw-window-handle 0.6.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"rayon 1.11.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"rayon-core 1.13.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"ref-cast 1.0.25 registry+https://github.com/rust-lang/crates.io-index\",\n        \"ref-cast-impl 1.0.25 registry+https://github.com/rust-lang/crates.io-index\",\n        \"regex 1.12.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"regex-automata 0.4.14 registry+https://github.com/rust-lang/crates.io-index\",\n        \"regex-syntax 0.8.9 registry+https://github.com/rust-lang/crates.io-index\",\n        \"reqwest 0.12.28 registry+https://github.com/rust-lang/crates.io-index\",\n        \"roxmltree 0.20.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"rustc-demangle 0.1.27 registry+https://github.com/rust-lang/crates.io-index\",\n        \"rustc_version 0.4.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"rustls-pki-types 1.14.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"ryu 1.0.23 registry+https://github.com/rust-lang/crates.io-index\",\n        \"scopeguard 1.2.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"semver 1.0.27 registry+https://github.com/rust-lang/crates.io-index\",\n        \"serde 1.0.228 registry+https://github.com/rust-lang/crates.io-index\",\n        \"serde_core 1.0.228 registry+https://github.com/rust-lang/crates.io-index\",\n        \"serde_derive 1.0.228 registry+https://github.com/rust-lang/crates.io-index\",\n        \"serde_derive_internals 0.29.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"serde_json 1.0.149 registry+https://github.com/rust-lang/crates.io-index\",\n        \"serde_json_lenient 0.2.4 registry+https://github.com/rust-lang/crates.io-index\",\n        \"serde_urlencoded 0.7.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"serde_variant 0.1.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"serde_with 3.16.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"serde_with_macros 3.16.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"serde_yaml 0.8.26 registry+https://github.com/rust-lang/crates.io-index\",\n        \"serde_yaml 0.9.34+deprecated registry+https://github.com/rust-lang/crates.io-index\",\n        \"sha2 0.10.9 registry+https://github.com/rust-lang/crates.io-index\",\n        \"shadow-rs 1.7.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"shell-words 1.1.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"shellexpand 2.1.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"shlex 1.3.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"signature 2.2.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"siphasher 0.3.11 registry+https://github.com/rust-lang/crates.io-index\",\n        \"siphasher 1.0.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"smallvec 1.15.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"smol_str 0.2.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"socket2 0.6.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"stable_deref_trait 1.2.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"stacker 0.1.23 registry+https://github.com/rust-lang/crates.io-index\",\n        \"static_assertions 1.1.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"supports-color 3.0.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"supports-hyperlinks 3.2.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"supports-unicode 3.0.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"syn 1.0.109 registry+https://github.com/rust-lang/crates.io-index\",\n        \"syn 2.0.115 registry+https://github.com/rust-lang/crates.io-index\",\n        \"sync_wrapper 1.0.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"tempfile 3.25.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"terminal_size 0.4.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"thiserror 2.0.18 registry+https://github.com/rust-lang/crates.io-index\",\n        \"thiserror-impl 2.0.18 registry+https://github.com/rust-lang/crates.io-index\",\n        \"thread_local 1.1.9 registry+https://github.com/rust-lang/crates.io-index\",\n        \"time 0.3.47 registry+https://github.com/rust-lang/crates.io-index\",\n        \"time-core 0.1.8 registry+https://github.com/rust-lang/crates.io-index\",\n        \"toml 0.5.11 registry+https://github.com/rust-lang/crates.io-index\",\n        \"ttf-parser 0.25.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"typenum 1.19.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"tz-rs 0.7.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"tzdb 0.7.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"tzdb_data 0.2.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"unicase 2.9.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"unicode-ident 1.0.23 registry+https://github.com/rust-lang/crates.io-index\",\n        \"unicode-linebreak 0.1.5 registry+https://github.com/rust-lang/crates.io-index\",\n        \"unicode-segmentation 1.12.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"unicode-width 0.1.14 registry+https://github.com/rust-lang/crates.io-index\",\n        \"unicode-width 0.2.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"unicode-xid 0.2.6 registry+https://github.com/rust-lang/crates.io-index\",\n        \"uom 0.37.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"url 2.5.8 registry+https://github.com/rust-lang/crates.io-index\",\n        \"utf8_iter 1.0.4 registry+https://github.com/rust-lang/crates.io-index\",\n        \"utf8parse 0.2.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"vcpkg 0.2.15 registry+https://github.com/rust-lang/crates.io-index\",\n        \"version_check 0.9.5 registry+https://github.com/rust-lang/crates.io-index\",\n        \"web-time 1.1.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"webbrowser 1.1.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"weezl 0.1.12 registry+https://github.com/rust-lang/crates.io-index\",\n        \"win-msgbox 0.2.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"winapi 0.3.9 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows 0.57.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows 0.58.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows 0.60.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows 0.61.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows 0.62.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-collections 0.1.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-collections 0.2.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-collections 0.3.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-core 0.57.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-core 0.58.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-core 0.60.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-core 0.61.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-core 0.62.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-future 0.1.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-future 0.2.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-future 0.3.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-implement 0.57.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-implement 0.58.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-implement 0.59.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-implement 0.60.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-interface 0.57.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-interface 0.58.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-interface 0.59.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-link 0.1.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-link 0.2.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-numerics 0.1.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-numerics 0.2.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-numerics 0.3.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-registry 0.6.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-result 0.1.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-result 0.2.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-result 0.3.4 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-result 0.4.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-strings 0.1.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-strings 0.3.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-strings 0.4.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-strings 0.5.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-sys 0.48.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-sys 0.52.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-sys 0.59.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-sys 0.60.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-sys 0.61.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-targets 0.48.5 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-targets 0.52.6 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-targets 0.53.5 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-threading 0.1.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-threading 0.2.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows_aarch64_msvc 0.48.5 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows_aarch64_msvc 0.52.6 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows_aarch64_msvc 0.53.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows_i686_msvc 0.48.5 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows_i686_msvc 0.52.6 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows_i686_msvc 0.53.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows_x86_64_msvc 0.48.5 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows_x86_64_msvc 0.52.6 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows_x86_64_msvc 0.53.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"winit 0.30.12 registry+https://github.com/rust-lang/crates.io-index\",\n        \"wmi 0.15.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"yaml-rust 0.4.5 registry+https://github.com/rust-lang/crates.io-index\",\n        \"zerocopy 0.8.39 registry+https://github.com/rust-lang/crates.io-index\",\n        \"zerocopy-derive 0.8.39 registry+https://github.com/rust-lang/crates.io-index\",\n        \"zeroize 1.8.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"zune-core 0.4.12 registry+https://github.com/rust-lang/crates.io-index\",\n        \"zune-core 0.5.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"zune-inflate 0.2.54 registry+https://github.com/rust-lang/crates.io-index\",\n        \"zune-jpeg 0.4.21 registry+https://github.com/rust-lang/crates.io-index\",\n        \"zune-jpeg 0.5.12 registry+https://github.com/rust-lang/crates.io-index\"\n      ]\n    ],\n    [\n      \"Apache-2.0 WITH LLVM-exception\",\n      [\n        \"ar_archive_writer 0.5.1 registry+https://github.com/rust-lang/crates.io-index\"\n      ]\n    ],\n    [\n      \"Artistic-2.0\",\n      [\n        \"file-id 0.1.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"notify-debouncer-full 0.1.0 registry+https://github.com/rust-lang/crates.io-index\"\n      ]\n    ],\n    [\n      \"BSD-2-Clause\",\n      [\n        \"av1-grain 0.2.5 registry+https://github.com/rust-lang/crates.io-index\",\n        \"rav1e 0.8.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"v_frame 0.3.9 registry+https://github.com/rust-lang/crates.io-index\",\n        \"zerocopy 0.8.39 registry+https://github.com/rust-lang/crates.io-index\",\n        \"zerocopy-derive 0.8.39 registry+https://github.com/rust-lang/crates.io-index\"\n      ]\n    ],\n    [\n      \"BSD-3-Clause\",\n      [\n        \"alloc-no-stdlib 2.0.4 registry+https://github.com/rust-lang/crates.io-index\",\n        \"alloc-stdlib 0.2.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"avif-serialize 0.8.8 registry+https://github.com/rust-lang/crates.io-index\",\n        \"brotli 8.0.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"brotli-decompressor 5.0.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"curve25519-dalek 4.1.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"ed25519-dalek 2.2.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"encoding_rs 0.8.35 registry+https://github.com/rust-lang/crates.io-index\",\n        \"exr 1.74.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"lebe 0.5.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"moxcms 0.7.11 registry+https://github.com/rust-lang/crates.io-index\",\n        \"pxfm 0.1.27 registry+https://github.com/rust-lang/crates.io-index\",\n        \"ravif 0.12.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"subtle 2.6.1 registry+https://github.com/rust-lang/crates.io-index\"\n      ]\n    ],\n    [\n      \"BSL-1.0\",\n      [\n        \"clipboard-win 5.4.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"error-code 3.3.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"ryu 1.0.23 registry+https://github.com/rust-lang/crates.io-index\"\n      ]\n    ],\n    [\n      \"CC0-1.0\",\n      [\n        \"dunce 1.0.5 registry+https://github.com/rust-lang/crates.io-index\",\n        \"file-id 0.1.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"imgref 1.12.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"notify 6.1.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"notify-debouncer-full 0.1.0 registry+https://github.com/rust-lang/crates.io-index\"\n      ]\n    ],\n    [\n      \"ISC\",\n      [\n        \"is_ci 1.2.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"libloading 0.8.9 registry+https://github.com/rust-lang/crates.io-index\",\n        \"starship-battery 0.10.3 registry+https://github.com/rust-lang/crates.io-index\"\n      ]\n    ],\n    [\n      \"MIT\",\n      [\n        \"accesskit 0.21.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"accesskit_consumer 0.31.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"accesskit_windows 0.29.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"adler 1.0.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"adler2 2.0.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"ahash 0.8.12 registry+https://github.com/rust-lang/crates.io-index\",\n        \"aho-corasick 1.1.4 registry+https://github.com/rust-lang/crates.io-index\",\n        \"aligned 0.4.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"aligned-vec 0.6.4 registry+https://github.com/rust-lang/crates.io-index\",\n        \"allocator-api2 0.2.21 registry+https://github.com/rust-lang/crates.io-index\",\n        \"anstream 0.6.21 registry+https://github.com/rust-lang/crates.io-index\",\n        \"anstyle 1.0.13 registry+https://github.com/rust-lang/crates.io-index\",\n        \"anstyle-parse 0.2.7 registry+https://github.com/rust-lang/crates.io-index\",\n        \"anstyle-query 1.1.5 registry+https://github.com/rust-lang/crates.io-index\",\n        \"anstyle-wincon 3.0.11 registry+https://github.com/rust-lang/crates.io-index\",\n        \"anyhow 1.0.101 registry+https://github.com/rust-lang/crates.io-index\",\n        \"arboard 3.6.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"arg_enum_proc_macro 0.3.4 registry+https://github.com/rust-lang/crates.io-index\",\n        \"arrayvec 0.7.6 registry+https://github.com/rust-lang/crates.io-index\",\n        \"as-slice 0.2.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"atomic-waker 1.1.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"autocfg 1.5.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"av-scenechange 0.14.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"backtrace 0.3.76 registry+https://github.com/rust-lang/crates.io-index\",\n        \"backtrace-ext 0.2.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"base16_color_scheme 0.3.2 git+https://github.com/LGUG2Z/base16_color_scheme\",\n        \"base64 0.22.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"beef 0.5.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"bit_field 0.10.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"bitflags 1.3.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"bitflags 2.11.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"bitstream-io 4.9.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"block-buffer 0.10.4 registry+https://github.com/rust-lang/crates.io-index\",\n        \"brotli 8.0.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"brotli-decompressor 5.0.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"built 0.8.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"bytemuck 1.25.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"bytemuck_derive 1.10.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"byteorder 1.5.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"byteorder-lite 0.1.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"bytes 1.11.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"calm_io 0.1.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"calmio_filters 0.1.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"catppuccin-egui 5.6.0 git+https://github.com/LGUG2Z/catppuccin-egui?rev=b2f95cbf441d1dd99f3c955ef10dcb84ce23c20a\",\n        \"cc 1.2.56 registry+https://github.com/rust-lang/crates.io-index\",\n        \"cfg-if 0.1.10 registry+https://github.com/rust-lang/crates.io-index\",\n        \"cfg-if 1.0.4 registry+https://github.com/rust-lang/crates.io-index\",\n        \"cfg_aliases 0.2.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"chrono 0.4.43 registry+https://github.com/rust-lang/crates.io-index\",\n        \"chrono-tz 0.10.4 registry+https://github.com/rust-lang/crates.io-index\",\n        \"chumsky 0.9.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"clap 4.5.58 registry+https://github.com/rust-lang/crates.io-index\",\n        \"clap_builder 4.5.58 registry+https://github.com/rust-lang/crates.io-index\",\n        \"clap_derive 4.5.55 registry+https://github.com/rust-lang/crates.io-index\",\n        \"clap_lex 1.0.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"color-eyre 0.6.5 registry+https://github.com/rust-lang/crates.io-index\",\n        \"color-spantrace 0.3.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"color-thief 0.2.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"color_quant 1.1.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"colorchoice 1.0.4 registry+https://github.com/rust-lang/crates.io-index\",\n        \"core2 0.4.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"cpufeatures 0.2.17 registry+https://github.com/rust-lang/crates.io-index\",\n        \"crc32fast 1.5.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"crossbeam-channel 0.5.15 registry+https://github.com/rust-lang/crates.io-index\",\n        \"crossbeam-deque 0.8.6 registry+https://github.com/rust-lang/crates.io-index\",\n        \"crossbeam-epoch 0.9.18 registry+https://github.com/rust-lang/crates.io-index\",\n        \"crossbeam-utils 0.8.21 registry+https://github.com/rust-lang/crates.io-index\",\n        \"crypto-common 0.1.7 registry+https://github.com/rust-lang/crates.io-index\",\n        \"ctrlc 3.5.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"cursor-icon 1.2.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"curve25519-dalek-derive 0.1.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"darling 0.21.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"darling_core 0.21.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"darling_macro 0.21.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"deflate 0.8.6 registry+https://github.com/rust-lang/crates.io-index\",\n        \"deranged 0.5.6 registry+https://github.com/rust-lang/crates.io-index\",\n        \"digest 0.10.7 registry+https://github.com/rust-lang/crates.io-index\",\n        \"dirs 3.0.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"dirs 4.0.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"dirs 6.0.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"dirs-sys 0.3.7 registry+https://github.com/rust-lang/crates.io-index\",\n        \"dirs-sys 0.5.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"displaydoc 0.2.5 registry+https://github.com/rust-lang/crates.io-index\",\n        \"document-features 0.2.12 registry+https://github.com/rust-lang/crates.io-index\",\n        \"dpi 0.1.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"dyn-clone 1.0.20 registry+https://github.com/rust-lang/crates.io-index\",\n        \"ecolor 0.33.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"ed25519 2.2.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"eframe 0.33.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"egui 0.33.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"egui-phosphor 0.10.0 git+https://github.com/amPerl/egui-phosphor?rev=d13688738478ecd12b426e3e74c59d6577a85b59\",\n        \"egui-winit 0.33.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"egui_extras 0.33.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"egui_glow 0.33.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"either 1.15.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"emath 0.33.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"encoding_rs 0.8.35 registry+https://github.com/rust-lang/crates.io-index\",\n        \"enum-map 2.7.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"enum-map-derive 0.17.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"env_home 0.1.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"epaint 0.33.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"epaint_default_fonts 0.33.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"equator 0.4.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"equator-macro 0.4.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"equivalent 1.0.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"eyre 0.6.12 registry+https://github.com/rust-lang/crates.io-index\",\n        \"fastrand 2.3.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"fax 0.2.6 registry+https://github.com/rust-lang/crates.io-index\",\n        \"fax_derive 0.2.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"fdeflate 0.3.7 registry+https://github.com/rust-lang/crates.io-index\",\n        \"filetime 0.2.27 registry+https://github.com/rust-lang/crates.io-index\",\n        \"find-msvc-tools 0.1.9 registry+https://github.com/rust-lang/crates.io-index\",\n        \"flate2 1.1.9 registry+https://github.com/rust-lang/crates.io-index\",\n        \"flavours 0.7.2 git+https://github.com/LGUG2Z/flavours\",\n        \"fnv 1.0.7 registry+https://github.com/rust-lang/crates.io-index\",\n        \"font-loader 0.11.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"form_urlencoded 1.2.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"fs-tail 0.1.4 registry+https://github.com/rust-lang/crates.io-index\",\n        \"futures 0.3.31 registry+https://github.com/rust-lang/crates.io-index\",\n        \"futures-channel 0.3.31 registry+https://github.com/rust-lang/crates.io-index\",\n        \"futures-core 0.3.31 registry+https://github.com/rust-lang/crates.io-index\",\n        \"futures-executor 0.3.31 registry+https://github.com/rust-lang/crates.io-index\",\n        \"futures-io 0.3.31 registry+https://github.com/rust-lang/crates.io-index\",\n        \"futures-macro 0.3.31 registry+https://github.com/rust-lang/crates.io-index\",\n        \"futures-sink 0.3.31 registry+https://github.com/rust-lang/crates.io-index\",\n        \"futures-task 0.3.31 registry+https://github.com/rust-lang/crates.io-index\",\n        \"futures-util 0.3.31 registry+https://github.com/rust-lang/crates.io-index\",\n        \"generic-array 0.14.7 registry+https://github.com/rust-lang/crates.io-index\",\n        \"getrandom 0.1.16 registry+https://github.com/rust-lang/crates.io-index\",\n        \"getrandom 0.2.17 registry+https://github.com/rust-lang/crates.io-index\",\n        \"getrandom 0.3.4 registry+https://github.com/rust-lang/crates.io-index\",\n        \"getrandom 0.4.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"gif 0.11.4 registry+https://github.com/rust-lang/crates.io-index\",\n        \"gif 0.14.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"git2 0.20.4 registry+https://github.com/rust-lang/crates.io-index\",\n        \"glob 0.3.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"glow 0.16.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"glutin-winit 0.5.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"h2 0.4.13 registry+https://github.com/rust-lang/crates.io-index\",\n        \"half 2.7.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"hashbrown 0.12.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"hashbrown 0.14.5 registry+https://github.com/rust-lang/crates.io-index\",\n        \"hashbrown 0.15.5 registry+https://github.com/rust-lang/crates.io-index\",\n        \"hashbrown 0.16.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"heck 0.5.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"hex 0.4.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"hex_color 3.0.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"hotwatch 0.5.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"http 1.4.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"http-body 1.0.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"http-body-util 0.1.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"httparse 1.10.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"hyper 1.8.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"hyper-tls 0.6.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"hyper-util 0.1.20 registry+https://github.com/rust-lang/crates.io-index\",\n        \"iana-time-zone 0.1.65 registry+https://github.com/rust-lang/crates.io-index\",\n        \"ident_case 1.0.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"idna 1.1.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"idna_adapter 1.2.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"image 0.23.14 registry+https://github.com/rust-lang/crates.io-index\",\n        \"image 0.25.9 registry+https://github.com/rust-lang/crates.io-index\",\n        \"image-webp 0.2.4 registry+https://github.com/rust-lang/crates.io-index\",\n        \"indenter 0.3.4 registry+https://github.com/rust-lang/crates.io-index\",\n        \"indexmap 1.9.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"indexmap 2.13.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"ipnet 2.11.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"iri-string 0.7.10 registry+https://github.com/rust-lang/crates.io-index\",\n        \"is_debug 1.1.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"is_terminal_polyfill 1.70.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"itertools 0.14.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"itoa 1.0.17 registry+https://github.com/rust-lang/crates.io-index\",\n        \"jobserver 0.1.34 registry+https://github.com/rust-lang/crates.io-index\",\n        \"jpeg-decoder 0.1.22 registry+https://github.com/rust-lang/crates.io-index\",\n        \"lazy_static 1.5.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"libc 0.2.182 registry+https://github.com/rust-lang/crates.io-index\",\n        \"libgit2-sys 0.18.3+1.9.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"libz-sys 1.1.23 registry+https://github.com/rust-lang/crates.io-index\",\n        \"linked-hash-map 0.5.6 registry+https://github.com/rust-lang/crates.io-index\",\n        \"litrs 1.0.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"lock_api 0.4.14 registry+https://github.com/rust-lang/crates.io-index\",\n        \"log 0.4.29 registry+https://github.com/rust-lang/crates.io-index\",\n        \"logos 0.14.4 registry+https://github.com/rust-lang/crates.io-index\",\n        \"logos-codegen 0.14.4 registry+https://github.com/rust-lang/crates.io-index\",\n        \"logos-derive 0.14.4 registry+https://github.com/rust-lang/crates.io-index\",\n        \"loop9 0.1.5 registry+https://github.com/rust-lang/crates.io-index\",\n        \"mac-addr 0.3.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"matchers 0.2.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"maybe-rayon 0.1.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"memchr 2.8.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"memoffset 0.9.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"mime 0.3.17 registry+https://github.com/rust-lang/crates.io-index\",\n        \"mime_guess2 2.3.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"minimal-lexical 0.2.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"miniz_oxide 0.3.7 registry+https://github.com/rust-lang/crates.io-index\",\n        \"miniz_oxide 0.4.4 registry+https://github.com/rust-lang/crates.io-index\",\n        \"miniz_oxide 0.8.9 registry+https://github.com/rust-lang/crates.io-index\",\n        \"mio 1.1.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"miow 0.6.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"nanoid 0.4.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"native-tls 0.2.16 registry+https://github.com/rust-lang/crates.io-index\",\n        \"net2 0.2.39 registry+https://github.com/rust-lang/crates.io-index\",\n        \"netdev 0.40.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"new_debug_unreachable 1.0.6 registry+https://github.com/rust-lang/crates.io-index\",\n        \"nohash-hasher 0.2.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"nom 7.1.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"nom 8.0.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"noop_proc_macro 0.3.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"ntapi 0.4.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"nu-ansi-term 0.50.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"num 0.4.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"num-bigint 0.4.6 registry+https://github.com/rust-lang/crates.io-index\",\n        \"num-complex 0.4.6 registry+https://github.com/rust-lang/crates.io-index\",\n        \"num-conv 0.2.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"num-derive 0.4.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"num-integer 0.1.46 registry+https://github.com/rust-lang/crates.io-index\",\n        \"num-iter 0.1.45 registry+https://github.com/rust-lang/crates.io-index\",\n        \"num-rational 0.3.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"num-rational 0.4.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"num-traits 0.2.19 registry+https://github.com/rust-lang/crates.io-index\",\n        \"object 0.37.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"once_cell 1.21.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"once_cell_polyfill 1.70.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"open 5.3.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"os_info 3.14.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"owo-colors 4.2.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"palette 0.5.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"palette_derive 0.5.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"parking_lot 0.12.5 registry+https://github.com/rust-lang/crates.io-index\",\n        \"parking_lot_core 0.9.12 registry+https://github.com/rust-lang/crates.io-index\",\n        \"paste 1.0.15 registry+https://github.com/rust-lang/crates.io-index\",\n        \"pastey 0.1.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"percent-encoding 2.3.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"phf 0.11.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"phf 0.12.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"phf 0.8.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"phf_codegen 0.8.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"phf_generator 0.8.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"phf_shared 0.11.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"phf_shared 0.12.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"phf_shared 0.8.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"pin-project-lite 0.2.16 registry+https://github.com/rust-lang/crates.io-index\",\n        \"pin-utils 0.1.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"pkg-config 0.3.32 registry+https://github.com/rust-lang/crates.io-index\",\n        \"png 0.16.8 registry+https://github.com/rust-lang/crates.io-index\",\n        \"png 0.18.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"powerfmt 0.2.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"powershell_script 1.1.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"ppv-lite86 0.2.21 registry+https://github.com/rust-lang/crates.io-index\",\n        \"proc-macro2 1.0.106 registry+https://github.com/rust-lang/crates.io-index\",\n        \"profiling 1.0.17 registry+https://github.com/rust-lang/crates.io-index\",\n        \"profiling-procmacros 1.0.17 registry+https://github.com/rust-lang/crates.io-index\",\n        \"psm 0.1.30 registry+https://github.com/rust-lang/crates.io-index\",\n        \"qoi 0.4.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"quick-error 2.0.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"quote 1.0.44 registry+https://github.com/rust-lang/crates.io-index\",\n        \"rand 0.7.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"rand 0.8.5 registry+https://github.com/rust-lang/crates.io-index\",\n        \"rand 0.9.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"rand_chacha 0.2.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"rand_chacha 0.3.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"rand_chacha 0.9.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"rand_core 0.5.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"rand_core 0.6.4 registry+https://github.com/rust-lang/crates.io-index\",\n        \"rand_core 0.9.5 registry+https://github.com/rust-lang/crates.io-index\",\n        \"rand_pcg 0.2.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"random_word 0.5.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"raw-window-handle 0.6.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"rayon 1.11.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"rayon-core 1.13.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"ref-cast 1.0.25 registry+https://github.com/rust-lang/crates.io-index\",\n        \"ref-cast-impl 1.0.25 registry+https://github.com/rust-lang/crates.io-index\",\n        \"regex 1.12.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"regex-automata 0.4.14 registry+https://github.com/rust-lang/crates.io-index\",\n        \"regex-syntax 0.8.9 registry+https://github.com/rust-lang/crates.io-index\",\n        \"reqwest 0.12.28 registry+https://github.com/rust-lang/crates.io-index\",\n        \"rgb 0.8.52 registry+https://github.com/rust-lang/crates.io-index\",\n        \"roxmltree 0.20.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"rustc-demangle 0.1.27 registry+https://github.com/rust-lang/crates.io-index\",\n        \"rustc_version 0.4.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"rustls-pki-types 1.14.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"same-file 1.0.6 registry+https://github.com/rust-lang/crates.io-index\",\n        \"schannel 0.1.28 registry+https://github.com/rust-lang/crates.io-index\",\n        \"schemars 1.2.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"schemars_derive 1.2.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"scoped_threadpool 0.1.9 registry+https://github.com/rust-lang/crates.io-index\",\n        \"scopeguard 1.2.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"semver 1.0.27 registry+https://github.com/rust-lang/crates.io-index\",\n        \"serde 1.0.228 registry+https://github.com/rust-lang/crates.io-index\",\n        \"serde_core 1.0.228 registry+https://github.com/rust-lang/crates.io-index\",\n        \"serde_derive 1.0.228 registry+https://github.com/rust-lang/crates.io-index\",\n        \"serde_derive_internals 0.29.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"serde_json 1.0.149 registry+https://github.com/rust-lang/crates.io-index\",\n        \"serde_json_lenient 0.2.4 registry+https://github.com/rust-lang/crates.io-index\",\n        \"serde_urlencoded 0.7.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"serde_variant 0.1.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"serde_with 3.16.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"serde_with_macros 3.16.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"serde_yaml 0.8.26 registry+https://github.com/rust-lang/crates.io-index\",\n        \"serde_yaml 0.9.34+deprecated registry+https://github.com/rust-lang/crates.io-index\",\n        \"sha2 0.10.9 registry+https://github.com/rust-lang/crates.io-index\",\n        \"shadow-rs 1.7.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"sharded-slab 0.1.7 registry+https://github.com/rust-lang/crates.io-index\",\n        \"shell-words 1.1.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"shellexpand 2.1.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"shlex 1.3.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"signature 2.2.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"simd-adler32 0.3.8 registry+https://github.com/rust-lang/crates.io-index\",\n        \"simd_helpers 0.1.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"siphasher 0.3.11 registry+https://github.com/rust-lang/crates.io-index\",\n        \"siphasher 1.0.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"slab 0.4.12 registry+https://github.com/rust-lang/crates.io-index\",\n        \"smallvec 1.15.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"smol_str 0.2.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"socket2 0.6.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"stable_deref_trait 1.2.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"stacker 0.1.23 registry+https://github.com/rust-lang/crates.io-index\",\n        \"static_assertions 1.1.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"strsim 0.11.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"strum 0.27.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"strum_macros 0.27.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"syn 1.0.109 registry+https://github.com/rust-lang/crates.io-index\",\n        \"syn 2.0.115 registry+https://github.com/rust-lang/crates.io-index\",\n        \"synstructure 0.13.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"sysinfo 0.33.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"sysinfo 0.38.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"tempfile 3.25.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"terminal_size 0.4.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"textwrap 0.16.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"thiserror 2.0.18 registry+https://github.com/rust-lang/crates.io-index\",\n        \"thiserror-impl 2.0.18 registry+https://github.com/rust-lang/crates.io-index\",\n        \"thread_local 1.1.9 registry+https://github.com/rust-lang/crates.io-index\",\n        \"tiff 0.10.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"tiff 0.6.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"time 0.3.47 registry+https://github.com/rust-lang/crates.io-index\",\n        \"time-core 0.1.8 registry+https://github.com/rust-lang/crates.io-index\",\n        \"tokio 1.49.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"tokio-native-tls 0.3.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"tokio-util 0.7.18 registry+https://github.com/rust-lang/crates.io-index\",\n        \"toml 0.5.11 registry+https://github.com/rust-lang/crates.io-index\",\n        \"tower 0.5.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"tower-http 0.6.8 registry+https://github.com/rust-lang/crates.io-index\",\n        \"tower-layer 0.3.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"tower-service 0.3.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"tracing 0.1.44 registry+https://github.com/rust-lang/crates.io-index\",\n        \"tracing-appender 0.2.4 registry+https://github.com/rust-lang/crates.io-index\",\n        \"tracing-attributes 0.1.31 registry+https://github.com/rust-lang/crates.io-index\",\n        \"tracing-core 0.1.36 registry+https://github.com/rust-lang/crates.io-index\",\n        \"tracing-error 0.2.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"tracing-log 0.2.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"tracing-subscriber 0.3.22 registry+https://github.com/rust-lang/crates.io-index\",\n        \"try-lock 0.2.5 registry+https://github.com/rust-lang/crates.io-index\",\n        \"ttf-parser 0.25.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"typenum 1.19.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"tz-rs 0.7.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"tzdb_data 0.2.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"uds_windows 1.1.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"unicase 2.9.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"unicode-ident 1.0.23 registry+https://github.com/rust-lang/crates.io-index\",\n        \"unicode-segmentation 1.12.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"unicode-width 0.1.14 registry+https://github.com/rust-lang/crates.io-index\",\n        \"unicode-width 0.2.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"unicode-xid 0.2.6 registry+https://github.com/rust-lang/crates.io-index\",\n        \"unsafe-libyaml 0.2.11 registry+https://github.com/rust-lang/crates.io-index\",\n        \"uom 0.37.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"url 2.5.8 registry+https://github.com/rust-lang/crates.io-index\",\n        \"utf8_iter 1.0.4 registry+https://github.com/rust-lang/crates.io-index\",\n        \"utf8parse 0.2.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"vcpkg 0.2.15 registry+https://github.com/rust-lang/crates.io-index\",\n        \"version_check 0.9.5 registry+https://github.com/rust-lang/crates.io-index\",\n        \"walkdir 2.5.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"want 0.3.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"web-time 1.1.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"webbrowser 1.1.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"weezl 0.1.12 registry+https://github.com/rust-lang/crates.io-index\",\n        \"which 8.0.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"win-msgbox 0.2.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"winapi 0.3.9 registry+https://github.com/rust-lang/crates.io-index\",\n        \"winapi-util 0.1.11 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows 0.57.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows 0.58.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows 0.60.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows 0.61.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows 0.62.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-collections 0.1.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-collections 0.2.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-collections 0.3.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-core 0.57.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-core 0.58.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-core 0.60.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-core 0.61.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-core 0.62.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-future 0.1.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-future 0.2.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-future 0.3.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-icons 0.1.0 git+https://github.com/LGUG2Z/windows-icons?rev=0c9d7ee1b807347c507d3a9862dd007b4d3f4354\",\n        \"windows-icons 0.1.0 git+https://github.com/LGUG2Z/windows-icons?rev=d67cc9920aa9b4883393e411fb4fa2ddd4c498b5\",\n        \"windows-implement 0.57.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-implement 0.58.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-implement 0.59.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-implement 0.60.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-interface 0.57.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-interface 0.58.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-interface 0.59.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-link 0.1.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-link 0.2.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-numerics 0.1.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-numerics 0.2.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-numerics 0.3.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-registry 0.6.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-result 0.1.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-result 0.2.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-result 0.3.4 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-result 0.4.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-strings 0.1.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-strings 0.3.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-strings 0.4.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-strings 0.5.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-sys 0.48.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-sys 0.52.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-sys 0.59.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-sys 0.60.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-sys 0.61.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-targets 0.48.5 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-targets 0.52.6 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-targets 0.53.5 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-threading 0.1.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows-threading 0.2.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows_aarch64_msvc 0.48.5 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows_aarch64_msvc 0.52.6 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows_aarch64_msvc 0.53.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows_i686_msvc 0.48.5 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows_i686_msvc 0.52.6 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows_i686_msvc 0.53.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows_x86_64_msvc 0.48.5 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows_x86_64_msvc 0.52.6 registry+https://github.com/rust-lang/crates.io-index\",\n        \"windows_x86_64_msvc 0.53.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"winput 0.2.5 registry+https://github.com/rust-lang/crates.io-index\",\n        \"winreg 0.55.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"winsafe 0.0.19 registry+https://github.com/rust-lang/crates.io-index\",\n        \"wmi 0.15.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"xml-rs 0.8.28 registry+https://github.com/rust-lang/crates.io-index\",\n        \"y4m 0.8.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"yaml-rust 0.4.5 registry+https://github.com/rust-lang/crates.io-index\",\n        \"zerocopy 0.8.39 registry+https://github.com/rust-lang/crates.io-index\",\n        \"zerocopy-derive 0.8.39 registry+https://github.com/rust-lang/crates.io-index\",\n        \"zeroize 1.8.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"zmij 1.0.21 registry+https://github.com/rust-lang/crates.io-index\",\n        \"zune-core 0.4.12 registry+https://github.com/rust-lang/crates.io-index\",\n        \"zune-core 0.5.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"zune-inflate 0.2.54 registry+https://github.com/rust-lang/crates.io-index\",\n        \"zune-jpeg 0.4.21 registry+https://github.com/rust-lang/crates.io-index\",\n        \"zune-jpeg 0.5.12 registry+https://github.com/rust-lang/crates.io-index\"\n      ]\n    ],\n    [\n      \"MIT-0\",\n      [\n        \"dunce 1.0.5 registry+https://github.com/rust-lang/crates.io-index\",\n        \"tzdb_data 0.2.3 registry+https://github.com/rust-lang/crates.io-index\"\n      ]\n    ],\n    [\n      \"MPL-2.0\",\n      [\n        \"option-ext 0.2.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"ramhorns 1.0.1 registry+https://github.com/rust-lang/crates.io-index\"\n      ]\n    ],\n    [\n      \"OFL-1.1\",\n      [\n        \"epaint_default_fonts 0.33.3 registry+https://github.com/rust-lang/crates.io-index\"\n      ]\n    ],\n    [\n      \"Ubuntu-font-1.0\",\n      [\n        \"epaint_default_fonts 0.33.3 registry+https://github.com/rust-lang/crates.io-index\"\n      ]\n    ],\n    [\n      \"Unicode-3.0\",\n      [\n        \"icu_collections 2.1.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"icu_locale_core 2.1.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"icu_normalizer 2.1.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"icu_normalizer_data 2.1.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"icu_properties 2.1.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"icu_properties_data 2.1.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"icu_provider 2.1.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"litemap 0.8.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"potential_utf 0.1.4 registry+https://github.com/rust-lang/crates.io-index\",\n        \"tinystr 0.8.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"unicode-ident 1.0.23 registry+https://github.com/rust-lang/crates.io-index\",\n        \"writeable 0.6.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"yoke 0.8.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"yoke-derive 0.8.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"zerofrom 0.1.6 registry+https://github.com/rust-lang/crates.io-index\",\n        \"zerofrom-derive 0.1.6 registry+https://github.com/rust-lang/crates.io-index\",\n        \"zerotrie 0.2.3 registry+https://github.com/rust-lang/crates.io-index\",\n        \"zerovec 0.11.5 registry+https://github.com/rust-lang/crates.io-index\",\n        \"zerovec-derive 0.11.2 registry+https://github.com/rust-lang/crates.io-index\"\n      ]\n    ],\n    [\n      \"Unlicense\",\n      [\n        \"aho-corasick 1.1.4 registry+https://github.com/rust-lang/crates.io-index\",\n        \"byteorder 1.5.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"byteorder-lite 0.1.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"memchr 2.8.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"same-file 1.0.6 registry+https://github.com/rust-lang/crates.io-index\",\n        \"walkdir 2.5.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"winapi-util 0.1.11 registry+https://github.com/rust-lang/crates.io-index\"\n      ]\n    ],\n    [\n      \"Zlib\",\n      [\n        \"adler32 1.2.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"bytemuck 1.25.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"bytemuck_derive 1.10.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"const_format 0.2.35 registry+https://github.com/rust-lang/crates.io-index\",\n        \"const_format_proc_macros 0.2.34 registry+https://github.com/rust-lang/crates.io-index\",\n        \"cursor-icon 1.2.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"foldhash 0.1.5 registry+https://github.com/rust-lang/crates.io-index\",\n        \"glow 0.16.0 registry+https://github.com/rust-lang/crates.io-index\",\n        \"miniz_oxide 0.4.4 registry+https://github.com/rust-lang/crates.io-index\",\n        \"miniz_oxide 0.8.9 registry+https://github.com/rust-lang/crates.io-index\",\n        \"raw-window-handle 0.6.2 registry+https://github.com/rust-lang/crates.io-index\",\n        \"zune-core 0.4.12 registry+https://github.com/rust-lang/crates.io-index\",\n        \"zune-core 0.5.1 registry+https://github.com/rust-lang/crates.io-index\",\n        \"zune-inflate 0.2.54 registry+https://github.com/rust-lang/crates.io-index\",\n        \"zune-jpeg 0.4.21 registry+https://github.com/rust-lang/crates.io-index\",\n        \"zune-jpeg 0.5.12 registry+https://github.com/rust-lang/crates.io-index\"\n      ]\n    ]\n  ]\n}\n"
  },
  {
    "path": "docs/cli/adjust-container-padding.md",
    "content": "# adjust-container-padding\n\n```\nAdjust container padding on the focused workspace\n\nUsage: komorebic.exe adjust-container-padding <SIZING> <ADJUSTMENT>\n\nArguments:\n  <SIZING>\n          [possible values: increase, decrease]\n\n  <ADJUSTMENT>\n          Pixels to adjust by as an integer\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/adjust-workspace-padding.md",
    "content": "# adjust-workspace-padding\n\n```\nAdjust workspace padding on the focused workspace\n\nUsage: komorebic.exe adjust-workspace-padding <SIZING> <ADJUSTMENT>\n\nArguments:\n  <SIZING>\n          [possible values: increase, decrease]\n\n  <ADJUSTMENT>\n          Pixels to adjust by as an integer\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/ahk-app-specific-configuration.md",
    "content": "# ahk-app-specific-configuration\n\n```\nGenerate common app-specific configurations and fixes to use in komorebi.ahk\n\nUsage: komorebic.exe ahk-app-specific-configuration <PATH> [OVERRIDE_PATH]\n\nArguments:\n  <PATH>\n          YAML file from which the application-specific configurations should be loaded\n\n  [OVERRIDE_PATH]\n          Optional YAML file of overrides to apply over the first file\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/animation-duration.md",
    "content": "# animation-duration\n\n```\nSet the duration for movement animations in ms\n\nUsage: komorebic.exe animation-duration [OPTIONS] <DURATION>\n\nArguments:\n  <DURATION>\n          Desired animation durations in ms\n\nOptions:\n  -a, --animation-type <ANIMATION_TYPE>\n          Animation type to apply the duration to. If not specified, sets global duration\n          \n          [possible values: movement, transparency]\n\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/animation-fps.md",
    "content": "# animation-fps\n\n```\nSet the frames per second for movement animations\n\nUsage: komorebic.exe animation-fps <FPS>\n\nArguments:\n  <FPS>\n          Desired animation frames per second\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/animation-style.md",
    "content": "# animation-style\n\n```\nSet the ease function for movement animations\n\nUsage: komorebic.exe animation-style [OPTIONS]\n\nOptions:\n  -s, --style <STYLE>\n          Desired ease function for animation\n          \n          [default: linear]\n          [possible values: linear, ease-in-sine, ease-out-sine, ease-in-out-sine, ease-in-quad, ease-out-quad, ease-in-out-quad, ease-in-cubic, ease-in-out-cubic, ease-in-quart, ease-out-quart, ease-in-out-quart, ease-in-quint, ease-out-quint, ease-in-out-quint, ease-in-expo,\n          ease-out-expo, ease-in-out-expo, ease-in-circ, ease-out-circ, ease-in-out-circ, ease-in-back, ease-out-back, ease-in-out-back, ease-in-elastic, ease-out-elastic, ease-in-out-elastic, ease-in-bounce, ease-out-bounce, ease-in-out-bounce]\n\n  -a, --animation-type <ANIMATION_TYPE>\n          Animation type to apply the style to. If not specified, sets global style\n          \n          [possible values: movement, transparency]\n\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/animation.md",
    "content": "# animation\n\n```\nEnable or disable movement animations\n\nUsage: komorebic.exe animation [OPTIONS] <BOOLEAN_STATE>\n\nArguments:\n  <BOOLEAN_STATE>\n          [possible values: enable, disable]\n\nOptions:\n  -a, --animation-type <ANIMATION_TYPE>\n          Animation type to apply the state to. If not specified, sets global state\n          \n          [possible values: movement, transparency]\n\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/application-specific-configuration-schema.md",
    "content": "# application-specific-configuration-schema\n\n```\nGenerate a JSON Schema for applications.json\n\nUsage: komorebic.exe application-specific-configuration-schema\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/bar-configuration.md",
    "content": "# bar-configuration\n\n```\nShow the path to komorebi.bar.json\n\nUsage: komorebic.exe bar-configuration\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/border-colour.md",
    "content": "# border-colour\n\n```\nSet the colour for a window border kind\n\nUsage: komorebic.exe border-colour [OPTIONS] <R> <G> <B>\n\nArguments:\n  <R>\n          Red\n\n  <G>\n          Green\n\n  <B>\n          Blue\n\nOptions:\n  -w, --window-kind <WINDOW_KIND>\n          [default: single]\n          [possible values: single, stack, monocle, unfocused, unfocused-locked, floating]\n\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/border-implementation.md",
    "content": "# border-implementation\n\n```\nSet the border implementation\n\nUsage: komorebic.exe border-implementation <STYLE>\n\nArguments:\n  <STYLE>\n          Desired border implementation\n\n          Possible values:\n          - komorebi: Use the adjustable komorebi border implementation\n          - windows:  Use the thin Windows accent border implementation\n\nOptions:\n  -h, --help\n          Print help (see a summary with '-h')\n\n```\n"
  },
  {
    "path": "docs/cli/border-offset.md",
    "content": "# border-offset\n\n```\nSet the border offset\n\nUsage: komorebic.exe border-offset <OFFSET>\n\nArguments:\n  <OFFSET>\n          Desired offset of the window border\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/border-style.md",
    "content": "# border-style\n\n```\nSet the border style\n\nUsage: komorebic.exe border-style <STYLE>\n\nArguments:\n  <STYLE>\n          Desired border style\n\n          Possible values:\n          - system:  Use the system border style\n          - rounded: Use the Windows 11-style rounded borders\n          - square:  Use the Windows 10-style square borders\n\nOptions:\n  -h, --help\n          Print help (see a summary with '-h')\n\n```\n"
  },
  {
    "path": "docs/cli/border-width.md",
    "content": "# border-width\n\n```\nSet the border width\n\nUsage: komorebic.exe border-width <WIDTH>\n\nArguments:\n  <WIDTH>\n          Desired width of the window border\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/border.md",
    "content": "# border\n\n```\nEnable or disable borders\n\nUsage: komorebic.exe border <BOOLEAN_STATE>\n\nArguments:\n  <BOOLEAN_STATE>\n          [possible values: enable, disable]\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/cancel-preselect.md",
    "content": "# cancel-preselect\n\n```\nCancel a workspace preselect set by the preselect-direction command, if one exists\n\nUsage: komorebic.exe cancel-preselect\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/change-layout.md",
    "content": "# change-layout\n\n```\nSet the layout on the focused workspace\n\nUsage: komorebic.exe change-layout <DEFAULT_LAYOUT>\n\nArguments:\n  <DEFAULT_LAYOUT>\n          [possible values: bsp, columns, rows, vertical-stack, horizontal-stack, ultrawide-vertical-stack, grid, right-main-vertical-stack, scrolling]\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/check.md",
    "content": "# check\n\n```\nCheck komorebi configuration and related files for common errors\n\nUsage: komorebic.exe check [OPTIONS]\n\nOptions:\n  -k, --komorebi-config <KOMOREBI_CONFIG>\n          Path to a static configuration JSON file\n\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/clear-all-workspace-rules.md",
    "content": "# clear-all-workspace-rules\n\n```\nRemove all application association rules for all workspaces\n\nUsage: komorebic.exe clear-all-workspace-rules\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/clear-named-workspace-layout-rules.md",
    "content": "# clear-named-workspace-layout-rules\n\n```\nClear all dynamic layout rules for the specified workspace\n\nUsage: komorebic.exe clear-named-workspace-layout-rules <WORKSPACE>\n\nArguments:\n  <WORKSPACE>\n          Target workspace name\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/clear-named-workspace-rules.md",
    "content": "# clear-named-workspace-rules\n\n```\nRemove all application association rules for a named workspace\n\nUsage: komorebic.exe clear-named-workspace-rules <WORKSPACE>\n\nArguments:\n  <WORKSPACE>\n          Name of a workspace\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/clear-session-float-rules.md",
    "content": "# clear-session-float-rules\n\n```\nClear all session float rules\n\nUsage: komorebic.exe clear-session-float-rules\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/clear-workspace-layout-rules.md",
    "content": "# clear-workspace-layout-rules\n\n```\nClear all dynamic layout rules for the specified workspace\n\nUsage: komorebic.exe clear-workspace-layout-rules <MONITOR> <WORKSPACE>\n\nArguments:\n  <MONITOR>\n          Monitor index (zero-indexed)\n\n  <WORKSPACE>\n          Workspace index on the specified monitor (zero-indexed)\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/clear-workspace-rules.md",
    "content": "# clear-workspace-rules\n\n```\nRemove all application association rules for a workspace by monitor and workspace index\n\nUsage: komorebic.exe clear-workspace-rules <MONITOR> <WORKSPACE>\n\nArguments:\n  <MONITOR>\n          Monitor index (zero-indexed)\n\n  <WORKSPACE>\n          Workspace index on the specified monitor (zero-indexed)\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/close-workspace.md",
    "content": "# close-workspace\n\n```\nClose the focused workspace (must be empty and unnamed)\n\nUsage: komorebic.exe close-workspace\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/close.md",
    "content": "# close\n\n```\nClose the focused window\n\nUsage: komorebic.exe close\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/complete-configuration.md",
    "content": "# complete-configuration\n\n```\nFor legacy komorebi.ahk or komorebi.ps1 configurations, signal that the final configuration option has been sent\n\nUsage: komorebic.exe complete-configuration\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/configuration.md",
    "content": "# configuration\n\n```\nShow the path to komorebi.json\n\nUsage: komorebic.exe configuration\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/container-padding.md",
    "content": "# container-padding\n\n```\nSet the container padding for the specified workspace\n\nUsage: komorebic.exe container-padding <MONITOR> <WORKSPACE> <SIZE>\n\nArguments:\n  <MONITOR>\n          Monitor index (zero-indexed)\n\n  <WORKSPACE>\n          Workspace index on the specified monitor (zero-indexed)\n\n  <SIZE>\n          Pixels to pad with as an integer\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/convert-app-specific-configuration.md",
    "content": "# convert-app-specific-configuration\n\n```\nConvert a v1 ASC YAML file to a v2 ASC JSON file\n\nUsage: komorebic.exe convert-app-specific-configuration <PATH>\n\nArguments:\n  <PATH>\n          YAML file from which the application-specific configurations should be loaded\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/cross-monitor-move-behaviour.md",
    "content": "# cross-monitor-move-behaviour\n\n```\nSet the behaviour when moving windows across monitor boundaries\n\nUsage: komorebic.exe cross-monitor-move-behaviour <MOVE_BEHAVIOUR>\n\nArguments:\n  <MOVE_BEHAVIOUR>\n          Possible values:\n          - swap:   Swap the window container with the window container at the edge of the adjacent monitor\n          - insert: Insert the window container into the focused workspace on the adjacent monitor\n          - no-op:  Do nothing if trying to move a window container in the direction of an adjacent monitor\n\nOptions:\n  -h, --help\n          Print help (see a summary with '-h')\n\n```\n"
  },
  {
    "path": "docs/cli/cycle-empty-workspace.md",
    "content": "# cycle-empty-workspace\n\n```\nFocus the next empty workspace in the given cycle direction (if one exists)\n\nUsage: komorebic.exe cycle-empty-workspace <CYCLE_DIRECTION>\n\nArguments:\n  <CYCLE_DIRECTION>\n          [possible values: previous, next]\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/cycle-focus.md",
    "content": "# cycle-focus\n\n```\nChange focus to the window in the specified cycle direction\n\nUsage: komorebic.exe cycle-focus <CYCLE_DIRECTION>\n\nArguments:\n  <CYCLE_DIRECTION>\n          [possible values: previous, next]\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/cycle-layout.md",
    "content": "# cycle-layout\n\n```\nCycle between available layouts\n\nUsage: komorebic.exe cycle-layout <CYCLE_DIRECTION>\n\nArguments:\n  <CYCLE_DIRECTION>\n          [possible values: previous, next]\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/cycle-monitor.md",
    "content": "# cycle-monitor\n\n```\nFocus the monitor in the given cycle direction\n\nUsage: komorebic.exe cycle-monitor <CYCLE_DIRECTION>\n\nArguments:\n  <CYCLE_DIRECTION>\n          [possible values: previous, next]\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/cycle-move-to-monitor.md",
    "content": "# cycle-move-to-monitor\n\n```\nMove the focused window to the monitor in the given cycle direction\n\nUsage: komorebic.exe cycle-move-to-monitor <CYCLE_DIRECTION>\n\nArguments:\n  <CYCLE_DIRECTION>\n          [possible values: previous, next]\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/cycle-move-to-workspace.md",
    "content": "# cycle-move-to-workspace\n\n```\nMove the focused window to the workspace in the given cycle direction\n\nUsage: komorebic.exe cycle-move-to-workspace <CYCLE_DIRECTION>\n\nArguments:\n  <CYCLE_DIRECTION>\n          [possible values: previous, next]\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/cycle-move-workspace-to-monitor.md",
    "content": "# cycle-move-workspace-to-monitor\n\n```\nMove the focused workspace monitor in the given cycle direction\n\nUsage: komorebic.exe cycle-move-workspace-to-monitor <CYCLE_DIRECTION>\n\nArguments:\n  <CYCLE_DIRECTION>\n          [possible values: previous, next]\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/cycle-move.md",
    "content": "# cycle-move\n\n```\nMove the focused window in the specified cycle direction\n\nUsage: komorebic.exe cycle-move <CYCLE_DIRECTION>\n\nArguments:\n  <CYCLE_DIRECTION>\n          [possible values: previous, next]\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/cycle-send-to-monitor.md",
    "content": "# cycle-send-to-monitor\n\n```\nSend the focused window to the monitor in the given cycle direction\n\nUsage: komorebic.exe cycle-send-to-monitor <CYCLE_DIRECTION>\n\nArguments:\n  <CYCLE_DIRECTION>\n          [possible values: previous, next]\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/cycle-send-to-workspace.md",
    "content": "# cycle-send-to-workspace\n\n```\nSend the focused window to the workspace in the given cycle direction\n\nUsage: komorebic.exe cycle-send-to-workspace <CYCLE_DIRECTION>\n\nArguments:\n  <CYCLE_DIRECTION>\n          [possible values: previous, next]\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/cycle-stack-index.md",
    "content": "# cycle-stack-index\n\n```\nCycle the index of the focused window in the focused stack in the specified cycle direction\n\nUsage: komorebic.exe cycle-stack-index <CYCLE_DIRECTION>\n\nArguments:\n  <CYCLE_DIRECTION>\n          [possible values: previous, next]\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/cycle-stack.md",
    "content": "# cycle-stack\n\n```\nCycle the focused stack in the specified cycle direction\n\nUsage: komorebic.exe cycle-stack <CYCLE_DIRECTION>\n\nArguments:\n  <CYCLE_DIRECTION>\n          [possible values: previous, next]\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/cycle-workspace.md",
    "content": "# cycle-workspace\n\n```\nFocus the workspace in the given cycle direction\n\nUsage: komorebic.exe cycle-workspace <CYCLE_DIRECTION>\n\nArguments:\n  <CYCLE_DIRECTION>\n          [possible values: previous, next]\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/data-directory.md",
    "content": "# data-directory\n\n```\nShow the path to komorebi's data directory in %LOCALAPPDATA%\n\nUsage: komorebic.exe data-directory\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/disable-autostart.md",
    "content": "# disable-autostart\n\n```\nDeletes the komorebi.lnk shortcut in shell:startup to disable autostart\n\nUsage: komorebic.exe disable-autostart\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/display-index-preference.md",
    "content": "# display-index-preference\n\n```\nSet the display index preference for a monitor identified using its display name\n\nUsage: komorebic.exe display-index-preference <INDEX_PREFERENCE> <DISPLAY>\n\nArguments:\n  <INDEX_PREFERENCE>\n          Preferred monitor index (zero-indexed)\n\n  <DISPLAY>\n          Display name as identified in komorebic state\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/eager-focus.md",
    "content": "# eager-focus\n\n```\nFocus the first managed window matching the given exe\n\nUsage: komorebic.exe eager-focus <EXE>\n\nArguments:\n  <EXE>\n          Case-sensitive exe identifier\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/enable-autostart.md",
    "content": "# enable-autostart\n\n```\nGenerates the komorebi.lnk shortcut in shell:startup to autostart komorebi\n\nUsage: komorebic.exe enable-autostart [OPTIONS]\n\nOptions:\n  -c, --config <CONFIG>\n          Path to a static configuration JSON file\n\n      --whkd\n          Enable autostart of whkd\n\n      --bar\n          Enable autostart of komorebi-bar\n\n      --masir\n          Enable autostart of masir\n\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/enforce-workspace-rules.md",
    "content": "# enforce-workspace-rules\n\n```\nEnforce all workspace rules, including initial workspace rules that have already been applied\n\nUsage: komorebic.exe enforce-workspace-rules\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/ensure-named-workspaces.md",
    "content": "# ensure-named-workspaces\n\n```\nCreate these many named workspaces for the specified monitor\n\nUsage: komorebic.exe ensure-named-workspaces <MONITOR> [NAMES]...\n\nArguments:\n  <MONITOR>\n          Monitor index (zero-indexed)\n\n  [NAMES]...\n          Names of desired workspaces\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/ensure-workspaces.md",
    "content": "# ensure-workspaces\n\n```\nCreate at least this many workspaces for the specified monitor\n\nUsage: komorebic.exe ensure-workspaces <MONITOR> <WORKSPACE_COUNT>\n\nArguments:\n  <MONITOR>\n          Monitor index (zero-indexed)\n\n  <WORKSPACE_COUNT>\n          Number of desired workspaces\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/fetch-app-specific-configuration.md",
    "content": "# fetch-app-specific-configuration\n\n```\nFetch the latest version of applications.json from komorebi-application-specific-configuration\n\nUsage: komorebic.exe fetch-app-specific-configuration\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/flip-layout.md",
    "content": "# flip-layout\n\n```\nFlip the layout on the focused workspace\n\nUsage: komorebic.exe flip-layout <AXIS>\n\nArguments:\n  <AXIS>\n          [possible values: horizontal, vertical, horizontal-and-vertical]\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/focus-last-workspace.md",
    "content": "# focus-last-workspace\n\n```\nFocus the last focused workspace on the focused monitor\n\nUsage: komorebic.exe focus-last-workspace\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/focus-monitor-at-cursor.md",
    "content": "# focus-monitor-at-cursor\n\n```\nFocus the monitor at the current cursor location\n\nUsage: komorebic.exe focus-monitor-at-cursor\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/focus-monitor-workspace.md",
    "content": "# focus-monitor-workspace\n\n```\nFocus the specified workspace on the target monitor\n\nUsage: komorebic.exe focus-monitor-workspace <TARGET_MONITOR> <TARGET_WORKSPACE>\n\nArguments:\n  <TARGET_MONITOR>\n          Target monitor index (zero-indexed)\n\n  <TARGET_WORKSPACE>\n          Workspace index on the target monitor (zero-indexed)\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/focus-monitor.md",
    "content": "# focus-monitor\n\n```\nFocus the specified monitor\n\nUsage: komorebic.exe focus-monitor <TARGET>\n\nArguments:\n  <TARGET>\n          Target index (zero-indexed)\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/focus-named-workspace.md",
    "content": "# focus-named-workspace\n\n```\nFocus the specified workspace\n\nUsage: komorebic.exe focus-named-workspace <WORKSPACE>\n\nArguments:\n  <WORKSPACE>\n          Target workspace name\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/focus-stack-window.md",
    "content": "# focus-stack-window\n\n```\nFocus the specified window index in the focused stack\n\nUsage: komorebic.exe focus-stack-window <TARGET>\n\nArguments:\n  <TARGET>\n          Target index (zero-indexed)\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/focus-workspace.md",
    "content": "# focus-workspace\n\n```\nFocus the specified workspace on the focused monitor\n\nUsage: komorebic.exe focus-workspace <TARGET>\n\nArguments:\n  <TARGET>\n          Target index (zero-indexed)\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/focus-workspaces.md",
    "content": "# focus-workspaces\n\n```\nFocus the specified workspace on all monitors\n\nUsage: komorebic.exe focus-workspaces <TARGET>\n\nArguments:\n  <TARGET>\n          Target index (zero-indexed)\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/focus.md",
    "content": "# focus\n\n```\nChange focus to the window in the specified direction\n\nUsage: komorebic.exe focus <OPERATION_DIRECTION>\n\nArguments:\n  <OPERATION_DIRECTION>\n          [possible values: left, right, up, down]\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/focused-workspace-container-padding.md",
    "content": "# focused-workspace-container-padding\n\n```\nSet container padding on the focused workspace\n\nUsage: komorebic.exe focused-workspace-container-padding <SIZE>\n\nArguments:\n  <SIZE>\n          Pixels size to set as an integer\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/focused-workspace-padding.md",
    "content": "# focused-workspace-padding\n\n```\nSet workspace padding on the focused workspace\n\nUsage: komorebic.exe focused-workspace-padding <SIZE>\n\nArguments:\n  <SIZE>\n          Pixels size to set as an integer\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/force-focus.md",
    "content": "# force-focus\n\n```\nForcibly focus the window at the cursor with a left mouse click\n\nUsage: komorebic.exe force-focus\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/generate-static-config.md",
    "content": "# generate-static-config\n\n```\nGenerates a static configuration JSON file based on the current window manager state\n\nUsage: komorebic.exe generate-static-config\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/global-state.md",
    "content": "# global-state\n\n```\nShow a JSON representation of the current global state\n\nUsage: komorebic.exe global-state\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/global-work-area-offset.md",
    "content": "# global-work-area-offset\n\n```\nSet offsets to exclude parts of the work area from tiling\n\nUsage: komorebic.exe global-work-area-offset <LEFT> <TOP> <RIGHT> <BOTTOM>\n\nArguments:\n  <LEFT>\n          Size of the left work area offset (set right to left * 2 to maintain right padding)\n\n  <TOP>\n          Size of the top work area offset (set bottom to the same value to maintain bottom padding)\n\n  <RIGHT>\n          Size of the right work area offset\n\n  <BOTTOM>\n          Size of the bottom work area offset\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/gui.md",
    "content": "# gui\n\n```\nLaunch the komorebi-gui debugging tool\n\nUsage: komorebic.exe gui\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/identify-layered-application.md",
    "content": "# identify-layered-application\n\n```\nIdentify an application that has WS_EX_LAYERED, but should still be managed\n\nUsage: komorebic.exe identify-layered-application <IDENTIFIER> <ID>\n\nArguments:\n  <IDENTIFIER>\n          [possible values: exe, class, title, path]\n\n  <ID>\n          Identifier as a string\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/identify-object-name-change-application.md",
    "content": "# identify-object-name-change-application\n\n```\nIdentify an application that sends EVENT_OBJECT_NAMECHANGE on launch\n\nUsage: komorebic.exe identify-object-name-change-application <IDENTIFIER> <ID>\n\nArguments:\n  <IDENTIFIER>\n          [possible values: exe, class, title, path]\n\n  <ID>\n          Identifier as a string\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/identify-tray-application.md",
    "content": "# identify-tray-application\n\n```\nIdentify an application that closes to the system tray\n\nUsage: komorebic.exe identify-tray-application <IDENTIFIER> <ID>\n\nArguments:\n  <IDENTIFIER>\n          [possible values: exe, class, title, path]\n\n  <ID>\n          Identifier as a string\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/ignore-rule.md",
    "content": "# ignore-rule\n\n```\nAdd a rule to ignore the specified application\n\nUsage: komorebic.exe ignore-rule <IDENTIFIER> <ID>\n\nArguments:\n  <IDENTIFIER>\n          [possible values: exe, class, title, path]\n\n  <ID>\n          Identifier as a string\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/initial-named-workspace-rule.md",
    "content": "# initial-named-workspace-rule\n\n```\nAdd a rule to associate an application with a named workspace on first show\n\nUsage: komorebic.exe initial-named-workspace-rule <IDENTIFIER> <ID> <WORKSPACE>\n\nArguments:\n  <IDENTIFIER>\n          [possible values: exe, class, title, path]\n\n  <ID>\n          Identifier as a string\n\n  <WORKSPACE>\n          Name of a workspace\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/initial-workspace-rule.md",
    "content": "# initial-workspace-rule\n\n```\nAdd a rule to associate an application with a workspace on first show\n\nUsage: komorebic.exe initial-workspace-rule <IDENTIFIER> <ID> <MONITOR> <WORKSPACE>\n\nArguments:\n  <IDENTIFIER>\n          [possible values: exe, class, title, path]\n\n  <ID>\n          Identifier as a string\n\n  <MONITOR>\n          Monitor index (zero-indexed)\n\n  <WORKSPACE>\n          Workspace index on the specified monitor (zero-indexed)\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/invisible-borders.md",
    "content": "# invisible-borders\n\n```\nSet the invisible border dimensions around each window\n\nUsage: komorebic.exe invisible-borders <LEFT> <TOP> <RIGHT> <BOTTOM>\n\nArguments:\n  <LEFT>\n          Size of the left invisible border\n\n  <TOP>\n          Size of the top invisible border (usually 0)\n\n  <RIGHT>\n          Size of the right invisible border (usually left * 2)\n\n  <BOTTOM>\n          Size of the bottom invisible border (usually the same as left)\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/kill.md",
    "content": "# kill\n\n```\nKill background processes started by komorebic\n\nUsage: komorebic.exe kill [OPTIONS]\n\nOptions:\n      --whkd\n          Kill whkd if it is running as a background process\n\n      --bar\n          Kill komorebi-bar if it is running as a background process\n\n      --masir\n          Kill masir if it is running as a background process\n\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/license.md",
    "content": "# license\n\n```\nSpecify an email associated with an Individual Commercial Use License\n\nUsage: komorebic.exe license <EMAIL>\n\nArguments:\n  <EMAIL>\n          Email address associated with an Individual Commercial Use License\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/load-resize.md",
    "content": "# load-resize\n\n```\nLoad the resize layout dimensions from a file\n\nUsage: komorebic.exe load-resize <PATH>\n\nArguments:\n  <PATH>\n          File from which the resize layout dimensions should be loaded\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/log.md",
    "content": "# log\n\n```\nTail komorebi.exe's process logs (cancel with Ctrl-C)\n\nUsage: komorebic.exe log\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/manage-rule.md",
    "content": "# manage-rule\n\n```\nAdd a rule to always manage the specified application\n\nUsage: komorebic.exe manage-rule <IDENTIFIER> <ID>\n\nArguments:\n  <IDENTIFIER>\n          [possible values: exe, class, title, path]\n\n  <ID>\n          Identifier as a string\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/manage.md",
    "content": "# manage\n\n```\nForce komorebi to manage the focused window\n\nUsage: komorebic.exe manage\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/minimize.md",
    "content": "# minimize\n\n```\nMinimize the focused window\n\nUsage: komorebic.exe minimize\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/monitor-index-preference.md",
    "content": "# monitor-index-preference\n\n```\nSet the monitor index preference for a monitor identified using its size\n\nUsage: komorebic.exe monitor-index-preference <INDEX_PREFERENCE> <LEFT> <TOP> <RIGHT> <BOTTOM>\n\nArguments:\n  <INDEX_PREFERENCE>\n          Preferred monitor index (zero-indexed)\n\n  <LEFT>\n          Left value of the monitor's size Rect\n\n  <TOP>\n          Top value of the monitor's size Rect\n\n  <RIGHT>\n          Right value of the monitor's size Rect\n\n  <BOTTOM>\n          Bottom value of the monitor's size Rect\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/monitor-information.md",
    "content": "# monitor-information\n\n```\nShow information about connected monitors\n\nUsage: komorebic.exe monitor-information\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/monitor-work-area-offset.md",
    "content": "# monitor-work-area-offset\n\n```\nSet offsets for a monitor to exclude parts of the work area from tiling\n\nUsage: komorebic.exe monitor-work-area-offset <MONITOR> <LEFT> <TOP> <RIGHT> <BOTTOM>\n\nArguments:\n  <MONITOR>\n          Monitor index (zero-indexed)\n\n  <LEFT>\n          Size of the left work area offset (set right to left * 2 to maintain right padding)\n\n  <TOP>\n          Size of the top work area offset (set bottom to the same value to maintain bottom padding)\n\n  <RIGHT>\n          Size of the right work area offset\n\n  <BOTTOM>\n          Size of the bottom work area offset\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/mouse-follows-focus.md",
    "content": "# mouse-follows-focus\n\n```\nEnable or disable mouse follows focus on all workspaces\n\nUsage: komorebic.exe mouse-follows-focus <BOOLEAN_STATE>\n\nArguments:\n  <BOOLEAN_STATE>\n          [possible values: enable, disable]\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/move-to-last-workspace.md",
    "content": "# move-to-last-workspace\n\n```\nMove the focused window to the last focused monitor workspace\n\nUsage: komorebic.exe move-to-last-workspace\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/move-to-monitor-workspace.md",
    "content": "# move-to-monitor-workspace\n\n```\nMove the focused window to the specified monitor workspace\n\nUsage: komorebic.exe move-to-monitor-workspace <TARGET_MONITOR> <TARGET_WORKSPACE>\n\nArguments:\n  <TARGET_MONITOR>\n          Target monitor index (zero-indexed)\n\n  <TARGET_WORKSPACE>\n          Workspace index on the target monitor (zero-indexed)\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/move-to-monitor.md",
    "content": "# move-to-monitor\n\n```\nMove the focused window to the specified monitor\n\nUsage: komorebic.exe move-to-monitor <TARGET>\n\nArguments:\n  <TARGET>\n          Target index (zero-indexed)\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/move-to-named-workspace.md",
    "content": "# move-to-named-workspace\n\n```\nMove the focused window to the specified workspace\n\nUsage: komorebic.exe move-to-named-workspace <WORKSPACE>\n\nArguments:\n  <WORKSPACE>\n          Target workspace name\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/move-to-workspace.md",
    "content": "# move-to-workspace\n\n```\nMove the focused window to the specified workspace\n\nUsage: komorebic.exe move-to-workspace <TARGET>\n\nArguments:\n  <TARGET>\n          Target index (zero-indexed)\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/move-workspace-to-monitor.md",
    "content": "# move-workspace-to-monitor\n\n```\nMove the focused workspace to the specified monitor\n\nUsage: komorebic.exe move-workspace-to-monitor <TARGET>\n\nArguments:\n  <TARGET>\n          Target index (zero-indexed)\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/move.md",
    "content": "# move\n\n```\nMove the focused window in the specified direction\n\nUsage: komorebic.exe move <OPERATION_DIRECTION>\n\nArguments:\n  <OPERATION_DIRECTION>\n          [possible values: left, right, up, down]\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/named-workspace-container-padding.md",
    "content": "# named-workspace-container-padding\n\n```\nSet the container padding for the specified workspace\n\nUsage: komorebic.exe named-workspace-container-padding <WORKSPACE> <SIZE>\n\nArguments:\n  <WORKSPACE>\n          Target workspace name\n\n  <SIZE>\n          Pixels to pad with as an integer\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/named-workspace-layout-rule.md",
    "content": "# named-workspace-layout-rule\n\n```\nAdd a dynamic layout rule for the specified workspace\n\nUsage: komorebic.exe named-workspace-layout-rule <WORKSPACE> <AT_CONTAINER_COUNT> <LAYOUT>\n\nArguments:\n  <WORKSPACE>\n          Target workspace name\n\n  <AT_CONTAINER_COUNT>\n          The number of window containers on-screen required to trigger this layout rule\n\n  <LAYOUT>\n          [possible values: bsp, columns, rows, vertical-stack, horizontal-stack, ultrawide-vertical-stack, grid, right-main-vertical-stack, scrolling]\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/named-workspace-layout.md",
    "content": "# named-workspace-layout\n\n```\nSet the layout for the specified workspace\n\nUsage: komorebic.exe named-workspace-layout <WORKSPACE> <VALUE>\n\nArguments:\n  <WORKSPACE>\n          Target workspace name\n\n  <VALUE>\n          [possible values: bsp, columns, rows, vertical-stack, horizontal-stack, ultrawide-vertical-stack, grid, right-main-vertical-stack, scrolling]\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/named-workspace-padding.md",
    "content": "# named-workspace-padding\n\n```\nSet the workspace padding for the specified workspace\n\nUsage: komorebic.exe named-workspace-padding <WORKSPACE> <SIZE>\n\nArguments:\n  <WORKSPACE>\n          Target workspace name\n\n  <SIZE>\n          Pixels to pad with as an integer\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/named-workspace-rule.md",
    "content": "# named-workspace-rule\n\n```\nAdd a rule to associate an application with a named workspace\n\nUsage: komorebic.exe named-workspace-rule <IDENTIFIER> <ID> <WORKSPACE>\n\nArguments:\n  <IDENTIFIER>\n          [possible values: exe, class, title, path]\n\n  <ID>\n          Identifier as a string\n\n  <WORKSPACE>\n          Name of a workspace\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/named-workspace-tiling.md",
    "content": "# named-workspace-tiling\n\n```\nEnable or disable window tiling for the specified workspace\n\nUsage: komorebic.exe named-workspace-tiling <WORKSPACE> <VALUE>\n\nArguments:\n  <WORKSPACE>\n          Target workspace name\n\n  <VALUE>\n          [possible values: enable, disable]\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/new-workspace.md",
    "content": "# new-workspace\n\n```\nCreate and append a new workspace on the focused monitor\n\nUsage: komorebic.exe new-workspace\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/notification-schema.md",
    "content": "# notification-schema\n\n```\nGenerate a JSON Schema of subscription notifications\n\nUsage: komorebic.exe notification-schema\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/preselect-direction.md",
    "content": "# preselect-direction\n\n```\nPreselect the specified direction for the next window to be spawned on supported layouts\n\nUsage: komorebic.exe preselect-direction <OPERATION_DIRECTION>\n\nArguments:\n  <OPERATION_DIRECTION>\n          [possible values: left, right, up, down]\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/promote-focus.md",
    "content": "# promote-focus\n\n```\nPromote the user focus to the top of the tree\n\nUsage: komorebic.exe promote-focus\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/promote-swap.md",
    "content": "# promote-swap\n\n```\nPromote the focused window to the largest tile by swapping container indices with the largest tile\n\nUsage: komorebic.exe promote-swap\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/promote-window.md",
    "content": "# promote-window\n\n```\nPromote the window in the specified direction\n\nUsage: komorebic.exe promote-window <OPERATION_DIRECTION>\n\nArguments:\n  <OPERATION_DIRECTION>\n          [possible values: left, right, up, down]\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/promote.md",
    "content": "# promote\n\n```\nPromote the focused window to the largest tile via container removal and re-insertion\n\nUsage: komorebic.exe promote\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/pwsh-app-specific-configuration.md",
    "content": "# pwsh-app-specific-configuration\n\n```\nGenerate common app-specific configurations and fixes in a PowerShell script\n\nUsage: komorebic.exe pwsh-app-specific-configuration <PATH> [OVERRIDE_PATH]\n\nArguments:\n  <PATH>\n          YAML file from which the application-specific configurations should be loaded\n\n  [OVERRIDE_PATH]\n          Optional YAML file of overrides to apply over the first file\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/query.md",
    "content": "# query\n\n```\nQuery the current window manager state\n\nUsage: komorebic.exe query <STATE_QUERY>\n\nArguments:\n  <STATE_QUERY>\n          [possible values: focused-monitor-index, focused-workspace-index, focused-container-index, focused-window-index, focused-workspace-name, focused-workspace-layout, focused-container-kind, version]\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/quick-load-resize.md",
    "content": "# quick-load-resize\n\n```\nLoad the last quicksaved resize layout dimensions\n\nUsage: komorebic.exe quick-load-resize\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/quick-save-resize.md",
    "content": "# quick-save-resize\n\n```\nQuicksave the current resize layout dimensions\n\nUsage: komorebic.exe quick-save-resize\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/quickstart.md",
    "content": "# quickstart\n\n```\nGather example configurations for a new-user quickstart\n\nUsage: komorebic.exe quickstart\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/reload-configuration.md",
    "content": "# reload-configuration\n\n```\nReload legacy komorebi.ahk or komorebi.ps1 configurations (if they exist)\n\nUsage: komorebic.exe reload-configuration\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/remove-title-bar.md",
    "content": "# remove-title-bar\n\n```\nWhitelist an application for title bar removal\n\nUsage: komorebic.exe remove-title-bar <IDENTIFIER> <ID>\n\nArguments:\n  <IDENTIFIER>\n          [possible values: exe, class, title, path]\n\n  <ID>\n          Identifier as a string\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/replace-configuration.md",
    "content": "# replace-configuration\n\n```\nReplace the configuration of a running instance of komorebi from a static configuration file\n\nUsage: komorebic.exe replace-configuration <PATH>\n\nArguments:\n  <PATH>\n          Static configuration JSON file from which the configuration should be loaded\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/resize-axis.md",
    "content": "# resize-axis\n\n```\nResize the focused window or primary column along the specified axis\n\nUsage: komorebic.exe resize-axis <AXIS> <SIZING>\n\nArguments:\n  <AXIS>\n          [possible values: horizontal, vertical, horizontal-and-vertical]\n\n  <SIZING>\n          [possible values: increase, decrease]\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/resize-delta.md",
    "content": "# resize-delta\n\n```\nSet the resize delta (used by resize-edge and resize-axis)\n\nUsage: komorebic.exe resize-delta <PIXELS>\n\nArguments:\n  <PIXELS>\n          The delta of pixels by which to increase or decrease window dimensions when resizing\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/resize-edge.md",
    "content": "# resize-edge\n\n```\nResize the focused window in the specified direction\n\nUsage: komorebic.exe resize-edge <EDGE> <SIZING>\n\nArguments:\n  <EDGE>\n          [possible values: left, right, up, down]\n\n  <SIZING>\n          [possible values: increase, decrease]\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/restore-windows.md",
    "content": "# restore-windows\n\n```\nRestore all hidden windows (debugging command)\n\nUsage: komorebic.exe restore-windows\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/retile.md",
    "content": "# retile\n\n```\nForce the retiling of all managed windows\n\nUsage: komorebic.exe retile\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/save-resize.md",
    "content": "# save-resize\n\n```\nSave the current resize layout dimensions to a file\n\nUsage: komorebic.exe save-resize <PATH>\n\nArguments:\n  <PATH>\n          File to which the resize layout dimensions should be saved\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/scrolling-layout-columns.md",
    "content": "# scrolling-layout-columns\n\n```\nSet the number of visible columns for the Scrolling layout on the focused workspace\n\nUsage: komorebic.exe scrolling-layout-columns <COUNT>\n\nArguments:\n  <COUNT>\n          Desired number of visible columns\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/send-to-last-workspace.md",
    "content": "# send-to-last-workspace\n\n```\nSend the focused window to the last focused monitor workspace\n\nUsage: komorebic.exe send-to-last-workspace\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/send-to-monitor-workspace.md",
    "content": "# send-to-monitor-workspace\n\n```\nSend the focused window to the specified monitor workspace\n\nUsage: komorebic.exe send-to-monitor-workspace <TARGET_MONITOR> <TARGET_WORKSPACE>\n\nArguments:\n  <TARGET_MONITOR>\n          Target monitor index (zero-indexed)\n\n  <TARGET_WORKSPACE>\n          Workspace index on the target monitor (zero-indexed)\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/send-to-monitor.md",
    "content": "# send-to-monitor\n\n```\nSend the focused window to the specified monitor\n\nUsage: komorebic.exe send-to-monitor <TARGET>\n\nArguments:\n  <TARGET>\n          Target index (zero-indexed)\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/send-to-named-workspace.md",
    "content": "# send-to-named-workspace\n\n```\nSend the focused window to the specified workspace\n\nUsage: komorebic.exe send-to-named-workspace <WORKSPACE>\n\nArguments:\n  <WORKSPACE>\n          Target workspace name\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/send-to-workspace.md",
    "content": "# send-to-workspace\n\n```\nSend the focused window to the specified workspace\n\nUsage: komorebic.exe send-to-workspace <TARGET>\n\nArguments:\n  <TARGET>\n          Target index (zero-indexed)\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/session-float-rule.md",
    "content": "# session-float-rule\n\n```\nAdd a rule to float the foreground window for the rest of this session\n\nUsage: komorebic.exe session-float-rule\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/session-float-rules.md",
    "content": "# session-float-rules\n\n```\nShow all session float rules\n\nUsage: komorebic.exe session-float-rules\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/socket-schema.md",
    "content": "# socket-schema\n\n```\nGenerate a JSON Schema of socket messages\n\nUsage: komorebic.exe socket-schema\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/stack-all.md",
    "content": "# stack-all\n\n```\nStack all windows on the focused workspace\n\nUsage: komorebic.exe stack-all\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/stack.md",
    "content": "# stack\n\n```\nStack the focused window in the specified direction\n\nUsage: komorebic.exe stack <OPERATION_DIRECTION>\n\nArguments:\n  <OPERATION_DIRECTION>\n          [possible values: left, right, up, down]\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/stackbar-mode.md",
    "content": "# stackbar-mode\n\n```\nSet the stackbar mode\n\nUsage: komorebic.exe stackbar-mode <MODE>\n\nArguments:\n  <MODE>\n          Desired stackbar mode\n          \n          [possible values: always, never, on-stack]\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/start.md",
    "content": "# start\n\n```\nStart komorebi.exe as a background process\n\nUsage: komorebic.exe start [OPTIONS]\n\nOptions:\n  -c, --config <CONFIG>\n          Path to a static configuration JSON file\n\n  -a, --await-configuration\n          Wait for 'komorebic complete-configuration' to be sent before processing events\n\n  -t, --tcp-port <TCP_PORT>\n          Start a TCP server on the given port to allow the direct sending of SocketMessages\n\n      --whkd\n          Start whkd in a background process\n\n      --bar\n          Start komorebi-bar in a background process\n\n      --masir\n          Start masir in a background process for focus-follows-mouse\n\n      --clean-state\n          Do not attempt to auto-apply a dumped state temp file from a previously running instance of komorebi\n\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/state.md",
    "content": "# state\n\n```\nShow a JSON representation of the current window manager state\n\nUsage: komorebic.exe state\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/static-config-schema.md",
    "content": "# static-config-schema\n\n```\nGenerate a JSON Schema of the static configuration file\n\nUsage: komorebic.exe static-config-schema\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/stop.md",
    "content": "# stop\n\n```\nStop the komorebi.exe process and restore all hidden windows\n\nUsage: komorebic.exe stop [OPTIONS]\n\nOptions:\n      --whkd\n          Stop whkd if it is running as a background process\n\n      --bar\n          Stop komorebi-bar if it is running as a background process\n\n      --masir\n          Stop masir if it is running as a background process\n\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/subscribe-pipe.md",
    "content": "# subscribe-pipe\n\n```\nSubscribe to komorebi events using a Named Pipe\n\nUsage: komorebic.exe subscribe-pipe <NAMED_PIPE>\n\nArguments:\n  <NAMED_PIPE>\n          Name of the pipe to send event notifications to (without \"\\\\.\\pipe\\\" prepended)\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/subscribe-socket.md",
    "content": "# subscribe-socket\n\n```\nSubscribe to komorebi events using a Unix Domain Socket\n\nUsage: komorebic.exe subscribe-socket <SOCKET>\n\nArguments:\n  <SOCKET>\n          Name of the socket to send event notifications to\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/swap-workspaces-with-monitor.md",
    "content": "# swap-workspaces-with-monitor\n\n```\nSwap focused monitor workspaces with specified monitor\n\nUsage: komorebic.exe swap-workspaces-with-monitor <TARGET>\n\nArguments:\n  <TARGET>\n          Target index (zero-indexed)\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/toggle-cross-monitor-move-behaviour.md",
    "content": "# toggle-cross-monitor-move-behaviour\n\n```\nToggle the behaviour when moving windows across monitor boundaries\n\nUsage: komorebic.exe toggle-cross-monitor-move-behaviour\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/toggle-float-override.md",
    "content": "# toggle-float-override\n\n```\nEnable or disable float override, which makes it so every new window opens in floating mode\n\nUsage: komorebic.exe toggle-float-override\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/toggle-float.md",
    "content": "# toggle-float\n\n```\nToggle floating mode for the focused window\n\nUsage: komorebic.exe toggle-float\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/toggle-lock.md",
    "content": "# toggle-lock\n\n```\nToggle a lock for the focused container, ensuring it will not be displaced by any new windows\n\nUsage: komorebic.exe toggle-lock\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/toggle-maximize.md",
    "content": "# toggle-maximize\n\n```\nToggle native maximization for the focused window\n\nUsage: komorebic.exe toggle-maximize\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/toggle-monocle.md",
    "content": "# toggle-monocle\n\n```\nToggle monocle mode for the focused container\n\nUsage: komorebic.exe toggle-monocle\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/toggle-mouse-follows-focus.md",
    "content": "# toggle-mouse-follows-focus\n\n```\nToggle mouse follows focus on all workspaces\n\nUsage: komorebic.exe toggle-mouse-follows-focus\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/toggle-pause.md",
    "content": "# toggle-pause\n\n```\nToggle the paused state for all window tiling\n\nUsage: komorebic.exe toggle-pause\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/toggle-shortcuts.md",
    "content": "# toggle-shortcuts\n\n```\nToggle the komorebi-shortcuts helper\n\nUsage: komorebic.exe toggle-shortcuts\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/toggle-tiling.md",
    "content": "# toggle-tiling\n\n```\nToggle window tiling on the focused workspace\n\nUsage: komorebic.exe toggle-tiling\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/toggle-title-bars.md",
    "content": "# toggle-title-bars\n\n```\nToggle title bars for whitelisted applications\n\nUsage: komorebic.exe toggle-title-bars\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/toggle-transparency.md",
    "content": "# toggle-transparency\n\n```\nToggle transparency for unfocused windows\n\nUsage: komorebic.exe toggle-transparency\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/toggle-window-based-work-area-offset.md",
    "content": "# toggle-window-based-work-area-offset\n\n```\nToggle application of the window-based work area offset for the focused workspace\n\nUsage: komorebic.exe toggle-window-based-work-area-offset\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/toggle-window-container-behaviour.md",
    "content": "# toggle-window-container-behaviour\n\n```\nToggle the behaviour for new windows (stacking or dynamic tiling)\n\nUsage: komorebic.exe toggle-window-container-behaviour\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/toggle-workspace-float-override.md",
    "content": "# toggle-workspace-float-override\n\n```\nEnable or disable float override, which makes it so every new window opens in floating mode, for the currently focused workspace. If there was no override value set for the workspace previously it takes the opposite of the global value\n\nUsage: komorebic.exe toggle-workspace-float-override\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/toggle-workspace-layer.md",
    "content": "# toggle-workspace-layer\n\n```\nToggle between the Tiling and Floating layers on the focused workspace\n\nUsage: komorebic.exe toggle-workspace-layer\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/toggle-workspace-window-container-behaviour.md",
    "content": "# toggle-workspace-window-container-behaviour\n\n```\nToggle the behaviour for new windows (stacking or dynamic tiling) for currently focused workspace. If there was no behaviour set for the workspace previously it takes the opposite of the global value\n\nUsage: komorebic.exe toggle-workspace-window-container-behaviour\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/transparency-alpha.md",
    "content": "# transparency-alpha\n\n```\nSet the alpha value for unfocused window transparency\n\nUsage: komorebic.exe transparency-alpha <ALPHA>\n\nArguments:\n  <ALPHA>\n          Alpha\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/transparency.md",
    "content": "# transparency\n\n```\nEnable or disable transparency for unfocused windows\n\nUsage: komorebic.exe transparency <BOOLEAN_STATE>\n\nArguments:\n  <BOOLEAN_STATE>\n          [possible values: enable, disable]\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/unmanage.md",
    "content": "# unmanage\n\n```\nUnmanage a window that was forcibly managed\n\nUsage: komorebic.exe unmanage\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/unmanaged-window-operation-behaviour.md",
    "content": "# unmanaged-window-operation-behaviour\n\n```\nSet the operation behaviour when the focused window is not managed\n\nUsage: komorebic.exe unmanaged-window-operation-behaviour <OPERATION_BEHAVIOUR>\n\nArguments:\n  <OPERATION_BEHAVIOUR>\n          Possible values:\n          - op:    Process komorebic commands on temporarily unmanaged/floated windows\n          - no-op: Ignore komorebic commands on temporarily unmanaged/floated windows\n\nOptions:\n  -h, --help\n          Print help (see a summary with '-h')\n\n```\n"
  },
  {
    "path": "docs/cli/unstack-all.md",
    "content": "# unstack-all\n\n```\nUnstack all windows in the focused container\n\nUsage: komorebic.exe unstack-all\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/unstack.md",
    "content": "# unstack\n\n```\nUnstack the focused window\n\nUsage: komorebic.exe unstack\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/unsubscribe-pipe.md",
    "content": "# unsubscribe-pipe\n\n```\nUnsubscribe from komorebi events\n\nUsage: komorebic.exe unsubscribe-pipe <NAMED_PIPE>\n\nArguments:\n  <NAMED_PIPE>\n          Name of the pipe to stop sending event notifications to (without \"\\\\.\\pipe\\\" prepended)\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/unsubscribe-socket.md",
    "content": "# unsubscribe-socket\n\n```\nUnsubscribe from komorebi events\n\nUsage: komorebic.exe unsubscribe-socket <SOCKET>\n\nArguments:\n  <SOCKET>\n          Name of the socket to stop sending event notifications to\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/visible-windows.md",
    "content": "# visible-windows\n\n```\nShow a JSON representation of visible windows\n\nUsage: komorebic.exe visible-windows\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/watch-configuration.md",
    "content": "# watch-configuration\n\n```\nEnable or disable watching of legacy komorebi.ahk or komorebi.ps1 configurations (if they exist)\n\nUsage: komorebic.exe watch-configuration <BOOLEAN_STATE>\n\nArguments:\n  <BOOLEAN_STATE>\n          [possible values: enable, disable]\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/whkdrc.md",
    "content": "# whkdrc\n\n```\nShow the path to whkdrc\n\nUsage: komorebic.exe whkdrc\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/window-hiding-behaviour.md",
    "content": "# window-hiding-behaviour\n\n```\nSet the window behaviour when switching workspaces / cycling stacks\n\nUsage: komorebic.exe window-hiding-behaviour <HIDING_BEHAVIOUR>\n\nArguments:\n  <HIDING_BEHAVIOUR>\n          Possible values:\n          - hide:     END OF LIFE FEATURE: Use the SW_HIDE flag to hide windows when switching workspaces (has issues with Electron apps)\n          - minimize: Use the SW_MINIMIZE flag to hide windows when switching workspaces (has issues with frequent workspace switching)\n          - cloak:    Use the undocumented SetCloak Win32 function to hide windows when switching workspaces\n\nOptions:\n  -h, --help\n          Print help (see a summary with '-h')\n\n```\n"
  },
  {
    "path": "docs/cli/workspace-layout-rule.md",
    "content": "# workspace-layout-rule\n\n```\nAdd a dynamic layout rule for the specified workspace\n\nUsage: komorebic.exe workspace-layout-rule <MONITOR> <WORKSPACE> <AT_CONTAINER_COUNT> <LAYOUT>\n\nArguments:\n  <MONITOR>\n          Monitor index (zero-indexed)\n\n  <WORKSPACE>\n          Workspace index on the specified monitor (zero-indexed)\n\n  <AT_CONTAINER_COUNT>\n          The number of window containers on-screen required to trigger this layout rule\n\n  <LAYOUT>\n          [possible values: bsp, columns, rows, vertical-stack, horizontal-stack, ultrawide-vertical-stack, grid, right-main-vertical-stack, scrolling]\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/workspace-layout.md",
    "content": "# workspace-layout\n\n```\nSet the layout for the specified workspace\n\nUsage: komorebic.exe workspace-layout <MONITOR> <WORKSPACE> <VALUE>\n\nArguments:\n  <MONITOR>\n          Monitor index (zero-indexed)\n\n  <WORKSPACE>\n          Workspace index on the specified monitor (zero-indexed)\n\n  <VALUE>\n          [possible values: bsp, columns, rows, vertical-stack, horizontal-stack, ultrawide-vertical-stack, grid, right-main-vertical-stack, scrolling]\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/workspace-name.md",
    "content": "# workspace-name\n\n```\nSet the workspace name for the specified workspace\n\nUsage: komorebic.exe workspace-name <MONITOR> <WORKSPACE> <VALUE>\n\nArguments:\n  <MONITOR>\n          Monitor index (zero-indexed)\n\n  <WORKSPACE>\n          Workspace index on the specified monitor (zero-indexed)\n\n  <VALUE>\n          Name of the workspace as a String\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/workspace-padding.md",
    "content": "# workspace-padding\n\n```\nSet the workspace padding for the specified workspace\n\nUsage: komorebic.exe workspace-padding <MONITOR> <WORKSPACE> <SIZE>\n\nArguments:\n  <MONITOR>\n          Monitor index (zero-indexed)\n\n  <WORKSPACE>\n          Workspace index on the specified monitor (zero-indexed)\n\n  <SIZE>\n          Pixels to pad with as an integer\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/workspace-rule.md",
    "content": "# workspace-rule\n\n```\nAdd a rule to associate an application with a workspace\n\nUsage: komorebic.exe workspace-rule <IDENTIFIER> <ID> <MONITOR> <WORKSPACE>\n\nArguments:\n  <IDENTIFIER>\n          [possible values: exe, class, title, path]\n\n  <ID>\n          Identifier as a string\n\n  <MONITOR>\n          Monitor index (zero-indexed)\n\n  <WORKSPACE>\n          Workspace index on the specified monitor (zero-indexed)\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/workspace-tiling.md",
    "content": "# workspace-tiling\n\n```\nEnable or disable window tiling for the specified workspace\n\nUsage: komorebic.exe workspace-tiling <MONITOR> <WORKSPACE> <VALUE>\n\nArguments:\n  <MONITOR>\n          Monitor index (zero-indexed)\n\n  <WORKSPACE>\n          Workspace index on the specified monitor (zero-indexed)\n\n  <VALUE>\n          [possible values: enable, disable]\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/cli/workspace-work-area-offset.md",
    "content": "# workspace-work-area-offset\n\n```\nSet offsets for a workspace to exclude parts of the work area from tiling\n\nUsage: komorebic.exe workspace-work-area-offset <MONITOR> <WORKSPACE> <LEFT> <TOP> <RIGHT> <BOTTOM>\n\nArguments:\n  <MONITOR>\n          Monitor index (zero-indexed)\n\n  <WORKSPACE>\n          Workspace index (zero-indexed)\n\n  <LEFT>\n          Size of the left work area offset (set right to left * 2 to maintain right padding)\n\n  <TOP>\n          Size of the top work area offset (set bottom to the same value to maintain bottom padding)\n\n  <RIGHT>\n          Size of the right work area offset\n\n  <BOTTOM>\n          Size of the bottom work area offset\n\nOptions:\n  -h, --help\n          Print help\n\n```\n"
  },
  {
    "path": "docs/common-workflows/animations.md",
    "content": "# Animations\n\nIf you would like to add window movement animations, ensure the following options are\ndefined in the `komorebi.json` configuration file.\n\n```json\n{\n  \"animation\": {\n    \"enabled\": true,\n    \"duration\": 250,\n    \"fps\": 60,\n    \"style\": \"EaseOutSine\"\n  }\n}\n```\n\nWindow movement animations only apply to actions taking place within the same monitor\nworkspace.\n\nYou can optionally set a custom duration in ms with `animation.duration` (default: `250`),\na custom style with `animation.style` (default: `Linear`), and a custom FPS value with\n`animation.fps` (default: `60`).\n\nIt is important to note that higher `fps` and a longer `duration` settings will result\nin increased CPU usage.\n\nThis feature is not considered stable, and you may encounter visual artifacts\nfrom time to time."
  },
  {
    "path": "docs/common-workflows/autohotkey.md",
    "content": "# AutoHotkey\n\nIf you would like to use Autohotkey, please make sure you have AutoHotKey v2\ninstalled.\n\nGenerally, users who opt for AHK will have specific needs that can only be\naddressed by the advanced functionality of AHK, and so they are assumed to be\nable to craft their own configuration files.\n\nIf you would like to try out AHK, here is a simple sample configuration which\nlargely matches the `whkdrc` sample configuration.\n\n```autohotkey\n{% include \"./komorebi.ahk.txt\" %}\n```\n\nBy default, the `komorebi.ahk` file should be located in the `$Env:USERPROFILE`\ndirectory, however, if `$Env:KOMOREBI_CONFIG_HOME` is set, it should be located\nthere.\n\nOnce the file is in place, you can stop komorebi and whkd by running `komorebic stop --whkd`,\nand then start komorebi with Autohotkey by running `komorebic start --ahk`.\n"
  },
  {
    "path": "docs/common-workflows/autostart.md",
    "content": "# Autostart\n\nIf you would like to autostart `komorebi`, you can use the `komorebic enable-autostart` command to generate a shortcut\nin the `shell:startup` folder.\n\n```\nGenerates the komorebi.lnk shortcut in shell:startup to autostart komorebi\n\nUsage: komorebic.exe enable-autostart [OPTIONS]\n\nOptions:\n  -c, --config <CONFIG>\n          Path to a static configuration JSON file\n\n  -f, --ffm\n          Enable komorebi's custom focus-follows-mouse implementation\n\n      --whkd\n          Enable autostart of whkd\n\n      --ahk\n          Enable autostart of ahk\n\n      --bar\n          Enable autostart of komorebi-bar\n\n  -h, --help\n          Print help\n```\n"
  },
  {
    "path": "docs/common-workflows/borders.md",
    "content": "# Borders\n\nIf you would like to add a visual border around both the currently focused window\nand unfocused windows ensure the following options are defined in the `komorebi.json`\nconfiguration file.\n\n```json\n{\n  \"border\": true,\n  \"border_width\": 8,\n  \"border_offset\": -1,\n  \"border_style\": \"System\",\n  \"border_colours\": {\n    \"single\": \"#42a5f5\",\n    \"stack\": \"#00a542\",\n    \"monocle\": \"#ff3399\",\n    \"unfocused\": \"#808080\"\n  }\n}\n```\n\nIt is important to note that borders will only apply to windows managed by `komorebi`.\n\nThis feature is not considered stable, and you may encounter visual artifacts\nfrom time to time.\n\n[![Watch the tutorial\nvideo](https://img.youtube.com/vi/7_9D22t7KK4/hqdefault.jpg)](https://www.youtube.com/watch?v=7_9D22t7KK4)\n"
  },
  {
    "path": "docs/common-workflows/dynamic-layout-switching.md",
    "content": "# Dynamic Layout Switching\n\nWith `komorebi` it is possible to define rules to automatically change the\nlayout on a specified workspace when a threshold of window containers is met.\n\n```json\n{\n  \"monitors\": [\n    {\n      \"workspaces\": [\n        {\n          \"name\": \"personal\",\n          \"layout_rules\": {\n            \"1\": \"BSP\"\n          }\n          \"custom_layout_rules\": {\n            \"5\": \"C:/Users/LGUG2Z/my-custom-layout.json\"\n          }\n        },\n      ]\n    }\n  ]\n}\n```\n\nIn this example, when there are one or more window containers visible on the\nscreen, the BSP layout is used, and when there are five or more window\ncontainers visible, a custom layout is used.\n\nHowever, if you add workspace layout rules, you will not be able to manually\nchange the layout of a workspace until all layout rules for that workspace have\nbeen cleared.\n\n```powershell\n# for example, to clear rules from monitor 0, workspace 0\nkomorebic clear-workspace-layout-rules 0 0\n```\n"
  },
  {
    "path": "docs/common-workflows/floating-applications.md",
    "content": "# Floating Windows\n\nSometimes you will want a specific application to be managed as a floating window.\nYou can add rules to enforce this behaviour in the `komorebi.json` configuration file.\n\n```json\n{\n  \"floating_applications\": [\n    {\n      \"kind\": \"Title\",\n      \"id\": \"Media Player\",\n      \"matching_strategy\": \"Equals\"\n    }\n  ]\n}\n```\n"
  },
  {
    "path": "docs/common-workflows/force-manage-windows.md",
    "content": "# Force Manage Windows\n\n❗️**NOTE**: A significant number of force-manage window rules for the most\ncommon applications are [already generated for\nyou](https://github.com/LGUG2Z/komorebi-application-specific-configuration)\n\nIn some rare cases, a window may not automatically be registered to be managed\nby `komorebi`. You can add rules to enforce this behaviour in the\n`komorebi.json` configuration file.\n\n```json\n{\n  \"manage_rules\": [\n    {\n      \"kind\": \"Title\",\n      \"id\": \"Media Player\",\n      \"matching_strategy\": \"Equals\"\n    }\n  ]\n}\n```\n"
  },
  {
    "path": "docs/common-workflows/ignore-windows.md",
    "content": "# Ignore Windows\n\n❗️**NOTE**: A significant number of ignored window rules for the most common\napplications are [already generated for\nyou](https://github.com/LGUG2Z/komorebi-application-specific-configuration)\n\nSometimes you will want a specific application to never be tiled, and instead\nbe completely ignored. You can add rules to enforce this behaviour in the\n`komorebi.json` configuration file.\n\n```json\n{\n  \"ignore_rules\": [\n    {\n      \"kind\": \"Title\",\n      \"id\": \"Media Player\",\n      \"matching_strategy\": \"Equals\"\n    }\n  ]\n}\n```\n"
  },
  {
    "path": "docs/common-workflows/komorebi-config-home.md",
    "content": "# `KOMOREBI_CONFIG_HOME`\n\nIf you do not want to keep _komorebi_-related files in your `$Env:USERPROFILE`\ndirectory, you can specify a custom directory by setting the\n`$Env:KOMOREBI_CONFIG_HOME` environment variable.\n\nFor example, to use the `~/.config/komorebi` directory:\n\n```powershell\n# Run this command to make sure that the directory has been created\nmkdir -p ~/.config/komorebi\n\n# Run this command to open up your PowerShell profile configuration in Notepad\nnotepad $PROFILE\n\n# Add this line (with your login user!) to the bottom of your PowerShell profile configuration\n$Env:KOMOREBI_CONFIG_HOME = 'C:\\Users\\LGUG2Z\\.config\\komorebi'\n\n# Save the changes and then reload the PowerShell profile\n. $PROFILE\n```\n\nIf you already have configuration files that you wish to keep, move them to the\n`~/.config/komorebi` directory.\n\nThe next time you run `komorebic start`, any files created by or loaded by\n_komorebi_ will be placed or expected to exist in this folder.\n\nAfter setting `$Env:KOMOREBI_CONFIG_HOME`, make sure to update the path in komorebi.json:\n\n```json\n{\n  \"app_specific_configuration_path\": \"$Env:KOMOREBI_CONFIG_HOME/applications.json\"\n}\n```\n\nThis ensures that komorebi can locate all configuration files correctly.\n\n[![Watch the tutorial\nvideo](https://img.youtube.com/vi/C_KWUqQ6kko/hqdefault.jpg)](https://www.youtube.com/watch?v=C_KWUqQ6kko)\n"
  },
  {
    "path": "docs/common-workflows/layout-ratios.md",
    "content": "# Layout Ratios\n\nWith `komorebi` you can customize the split ratios for various layouts using\n`column_ratios` and `row_ratios` in the `layout_options` configuration.\n\n## Before and After\n\nBSP layout example:\n\n**Before** (default 50/50 splits):\n\n![Before layout ratios](../assets/layout-ratios_before.png)\n\n**After** (with `column_ratios: [0.7]` and `row_ratios: [0.6]`):\n\n![After layout ratios](../assets/layout-ratios_after.png)\n\n## Configuration\n\n```json\n{\n  \"monitors\": [\n    {\n      \"workspaces\": [\n        {\n          \"name\": \"main\",\n          \"layout_options\": {\n            \"column_ratios\": [0.3, 0.4],\n            \"row_ratios\": [0.4, 0.3]\n          }\n        }\n      ]\n    }\n  ]\n}\n```\n\nYou can specify up to 5 ratio values (defined by `MAX_RATIOS` constant). Each value should be between 0.1 and 0.9\n(defined by `MIN_RATIO` and `MAX_RATIO` constants). Values outside this range are automatically clamped.\nColumns or rows without a specified ratio will share the remaining space equally.\n\n## Usage by Layout\n\n| Layout | `column_ratios` | `row_ratios` |\n|--------|-----------------|--------------|\n| **Columns** | Width of each column | - |\n| **Rows** | - | Height of each row |\n| **Grid** | Width of each column (rows are equal height) | - |\n| **BSP** | `[0]` as horizontal split ratio | `[0]` as vertical split ratio |\n| **VerticalStack** | `[0]` as primary column width | Stack row heights |\n| **RightMainVerticalStack** | `[0]` as primary column width | Stack row heights |\n| **HorizontalStack** | Stack column widths | `[0]` as primary row height |\n| **UltrawideVerticalStack** | `[0]` center, `[1]` left column | Tertiary stack row heights |\n\n## Examples\n\n### Columns Layout with Custom Widths\n\nCreate 3 columns with 30%, 40%, and 30% widths:\n\n```json\n{\n  \"layout_options\": {\n    \"column_ratios\": [0.3, 0.4]\n  }\n}\n```\n\nNote: The third column automatically gets the remaining 30%.\n\n### Rows Layout with Custom Heights\n\nCreate 3 rows with 20%, 50%, and 30% heights:\n\n```json\n{\n  \"layout_options\": {\n    \"row_ratios\": [0.2, 0.5]\n  }\n}\n```\n\nNote: The third row automatically gets the remaining 30%.\n\n### Grid Layout with Custom Column Widths\n\nGrid with custom column widths (rows within each column are always equal height):\n\n```json\n{\n  \"layout_options\": {\n    \"column_ratios\": [0.4, 0.6]\n  }\n}\n```\n\nNote: The Grid layout only supports `column_ratios`. Rows within each column are always\ndivided equally because the number of rows per column varies dynamically based on window count.\n\n### VerticalStack with Custom Ratios\n\nPrimary column takes 60% width, and the stack rows are split 30%/70%:\n\n```json\n{\n  \"layout_options\": {\n    \"column_ratios\": [0.6],\n    \"row_ratios\": [0.3]\n  }\n}\n```\n\nNote: The second row automatically gets the remaining 70%.\n\n### HorizontalStack with Custom Ratios\n\nPrimary row takes 70% height, and the stack columns are split 40%/60%:\n\n```json\n{\n  \"layout_options\": {\n    \"row_ratios\": [0.7],\n    \"column_ratios\": [0.4]\n  }\n}\n```\n\nNote: The second column automatically gets the remaining 60%.\n\n### UltrawideVerticalStack with Custom Ratios\n\nCenter column at 50%, left column at 25% (remaining 25% goes to tertiary stack),\nwith tertiary rows split 40%/60%:\n\n```json\n{\n  \"layout_options\": {\n    \"column_ratios\": [0.5, 0.25],\n    \"row_ratios\": [0.4]\n  }\n}\n```\n\nNote: The second row automatically gets the remaining 60%.\n\n### BSP Layout with Custom Split Ratios\n\nUse separate ratios for horizontal (left/right) and vertical (top/bottom) splits:\n\n```json\n{\n  \"layout_options\": {\n    \"column_ratios\": [0.6],\n    \"row_ratios\": [0.3]\n  }\n}\n```\n\n- `column_ratios[0]`: Controls all horizontal splits (left window gets 60%, right gets 40%)\n- `row_ratios[0]`: Controls all vertical splits (top window gets 30%, bottom gets 70%)\n\nNote: BSP only uses the first value (`[0]`) from each ratio array. This single ratio is applied\nconsistently to all splits of that type throughout the layout. Additional values in the arrays are ignored.\n\n## Notes\n\n- Ratios are clamped between 0.1 and 0.9 (prevents zero-sized windows and ensures space for other windows)\n- Default ratio is 0.5 (50%) when not specified, except for UltrawideVerticalStack secondary column which defaults to 0.25 (25%)\n- Ratios are applied **progressively** - a ratio is only used when there are more windows to place after the current one\n- The **last window always takes the remaining space**, regardless of defined ratios\n- **Ratios that would sum to 100% or more are automatically truncated** at config load time to ensure there's always space for additional windows\n- Unspecified ratios default to sharing the remaining space equally\n- You only need to specify the ratios you want to customize; trailing values can be omitted\n\n## Progressive Ratio Behavior\n\nRatios are applied progressively as windows are added. For example, with `row_ratios: [0.3, 0.5]` in a VerticalStack:\n\n| Windows in Stack | Row Heights |\n|------------------|-------------|\n| 1 | 100% |\n| 2 | 30%, 70% (remainder) |\n| 3 | 30%, 50%, 20% (remainder) |\n| 4 | 30%, 50%, 10%, 10% (remainder split equally) |\n| 5 | 30%, 50%, 6.67%, 6.67%, 6.67% |\n\n## Automatic Ratio Truncation\n\nWhen ratios sum to 100% (or more), they are automatically truncated at config load time.\n\nFor example, if you configure `column_ratios: [0.4, 0.3, 0.3]` (sums to 100%), the last ratio (0.3) is automatically removed, resulting in effectively `[0.4, 0.3]`. This ensures there's always remaining space for the last window.\n\n| Configured Ratios | Effective Ratios | Reason |\n|-------------------|------------------|--------|\n| `[0.3, 0.4]` | `[0.3, 0.4]` | Sum is 0.7, below 1.0 |\n| `[0.4, 0.3, 0.3]` | `[0.4, 0.3]` | Sum would be 1.0, last ratio truncated |\n| `[0.5, 0.5]` | `[0.5]` | Sum would be 1.0, last ratio truncated |\n| `[0.6, 0.5]` | `[0.6]` | Sum would exceed 1.0, last ratio truncated |\n\nThis ensures the layout always fills 100% of the available space and new windows are never placed outside the visible area.\n"
  },
  {
    "path": "docs/common-workflows/mouse-follows-focus.md",
    "content": "# Mouse Follows Focus\n\nBy default, the mouse will move to the center of the window when the focus is\nchanged in a given direction. This behaviour is know as 'mouse follows focus'.\nThis behaviour can be disabled in the `komorebi.json` configuration file.\n\n```json\n{\n  \"mouse_follows_focus\": false,\n}\n```\n\n<!-- TODO: Record a new video -->\n\n[![Watch the tutorial video](https://img.youtube.com/vi/LBoyXQiNINc/hqdefault.jpg)](https://www.youtube.com/watch?v=LBoyXQiNINc)\n"
  },
  {
    "path": "docs/common-workflows/multi-monitor-setup.md",
    "content": "# Multi-Monitor Setup\n\nYou can set up komorebi to work with multiple monitors. To do so, first you start by setting up multiple monitor\nconfigurations on your `komorebi.json` config file.\n\nIf you've used the [`komorebic quickstart`](../cli/quickstart.md) command you'll already have a `komorebi.json` config\nfile with one monitor config setup. Open this file and look for the `\"monitors\":` line, you should find something like\nthis:\n\n```json\n{\n  \"monitors\": [\n    {\n      \"workspaces\": [\n        {\n          \"name\": \"I\",\n          \"layout\": \"BSP\"\n        },\n        {\n          \"name\": \"II\",\n          \"layout\": \"VerticalStack\"\n        },\n        {\n          \"name\": \"III\",\n          \"layout\": \"HorizontalStack\"\n        },\n        {\n          \"name\": \"IV\",\n          \"layout\": \"UltrawideVerticalStack\"\n        },\n        {\n          \"name\": \"V\",\n          \"layout\": \"Rows\"\n        },\n        {\n          \"name\": \"VI\",\n          \"layout\": \"Grid\"\n        },\n        {\n          \"name\": \"VII\",\n          \"layout\": \"RightMainVerticalStack\"\n        }\n      ]\n    }\n  ]\n}\n```\n\nFor this example we will remove some workspaces to simplify the config so it is easier to look at, but feel free to\nset up as many workspaces per monitor as you'd like. Here is the same configuration with only 3 workspaces.\n\n```json\n{\n  \"monitors\": [\n    {\n      \"workspaces\": [\n        {\n          \"name\": \"I\",\n          \"layout\": \"BSP\"\n        },\n        {\n          \"name\": \"II\",\n          \"layout\": \"VerticalStack\"\n        },\n        {\n          \"name\": \"III\",\n          \"layout\": \"HorizontalStack\"\n        }\n      ]\n    }\n  ]\n}\n```\n\nLet's add another monitor:\n\n```json\n{\n  \"monitors\": [\n    // monitor 1, index 0\n    {\n      \"workspaces\": [\n        {\n          \"name\": \"I\",\n          \"layout\": \"BSP\"\n        },\n        {\n          \"name\": \"II\",\n          \"layout\": \"VerticalStack\"\n        },\n        {\n          \"name\": \"III\",\n          \"layout\": \"HorizontalStack\"\n        }\n      ]\n    },\n    // monitor 2, index 1\n    {\n      \"workspaces\": [\n        {\n          \"name\": \"1\",\n          \"layout\": \"BSP\"\n        },\n        {\n          \"name\": \"2\",\n          \"layout\": \"VerticalStack\"\n        },\n        {\n          \"name\": \"3\",\n          \"layout\": \"HorizontalStack\"\n        }\n      ]\n    }\n  ]\n}\n```\n\nNow have two monitor configurations. We have the first monitor configuration, which is index 0 (*usually\non programming languages the first item of a list starts with index 0*), this configuration has 3 workspaces with names\n\"I\", \"II\" and \"III\". Then the 2nd monitor configuration, which is index 1, also has 3 workspaces with names \"1\", \"2\",\nand \"3\" (you should always give unique names to your workspaces).\n\nNow if you start komorebi with two monitors connected, the main monitor will use the configuration with index 0 and the\nsecondary monitor will use the configuration with index 1.\n\n---\n\nLet's say you have more monitors, or you want to make sure that a certain configuration is always applied to a certain\nmonitor. For this you will want to use the `display_index_preferences`.\n\nOpen up a terminal and type the following command: [ `komorebic monitor-info`](../cli/monitor-information.md). This\ncommand will give you the information about your connected monitors, you want to look up the `serial_number_id`. You\nshould get something like this:\n\n```\n❯ komorebic monitor-info\n[\n  {\n    \"id\": 6620935,\n    \"name\": \"DISPLAY1\",\n    \"device\": \"BOE0A1C\",\n    \"device_id\": \"BOE0A1C-5&a2bea0b&0&UID512\",\n    \"serial_number_id\": \"0\",\n    \"size\": {\n      \"left\": 0,\n      \"top\": 0,\n      \"right\": 1920,\n      \"bottom\": 1080\n    }\n  },\n  {\n    \"id\": 181932057,\n    \"name\": \"DISPLAY2\",\n    \"device\": \"VSC8C31\",\n    \"device_id\": \"VSC8C31-5&18560b1f&0&UID4356\",\n    \"serial_number_id\": \"UEP174021562\",\n    \"size\": {\n      \"left\": 0,\n      \"top\": -1080,\n      \"right\": 1920,\n      \"bottom\": 1080\n    }\n  }\n]\n```\n\nIn this case the setup is a laptop with a secondary monitor connected. You'll need to figure out which monitor is which,\nusually the display name's number should be similar to the numbers you can find on\n`Windows Settings -> System -> Display`.\n\nIf you have trouble with this step you can always jump on Discord and ask for help (create a `Support` thread).\n\nOnce you know which monitor is which, you want to look up their `serial_number_id` to use that on\n`display_index_preferences`, you can also use the `device_id`, it accepts both however there have been reported cases\nwhere the `device_id` changes after a restart while the `serial_number_id` doesn't.\n\nSo with the example above, we want the laptop to always use the configuration index 0 and the other monitor to use\nconfiguration index 1, so we map the configuration index number to the monitor `serial_number_id`/`device_id` like this:\n\n```json\n{\n  \"display_index_preferences\": {\n    \"0\": \"0\",\n    \"1\": \"UEP174021562\"\n  }\n}\n```\n\nAgain you could also have used the `device_id` like this:\n\n```json\n{\n  \"display_index_preferences\": {\n    \"0\": \"BOE0A1C-5&a2bea0b&0&UID512\",\n    \"1\": \"VSC8C31-5&18560b1f&0&UID4356\"\n  }\n}\n```\n\nYou should add this `display_index_preferences` option to your `komorebi.json` file. If you find that something is\nnot working as expected you can try to use the command `komorebic check`.\n\n> [!IMPORTANT]\n>\n> **When using multiple monitors it is recommended to always set the `display_index_preferences`. If you don't you might\nget some undefined behaviour.**\n\n---\n\nIf you would like to run multiple instances of `komorebi-bar` to target different monitors, it is possible to do so\nusing the `bar_configurations` array in your `komorebi.json` configuration file. You can refer to the\n[multiple-bar-instances](multiple-bar-instances.md) documentation.\n\nIn this case it is important to use `display_index_preferences`, because if you don't, and you have 3 or more monitors,\ndisconnecting and reconnecting monitors may result in the bars for the monitors getting shifted around.\n\nConsider this setup with 3 monitors (A, B and C):\n\n```json\n// HOME_MONITOR_1_BAR.json\n{\n  \"monitor_index\": 0\n  //...\n}\n```\n\n```json\n// HOME_MONITOR_2_BAR.json\n{\n  \"monitor_index\": 1\n  //...\n}\n```\n\n```json\n// WORK_MONITOR_1_BAR.json\n{\n  \"monitor_index\": 2\n  //...\n}\n```\n\n```json\n{\n  \"display_index_preferences\": {\n    \"0\": \"MONITOR_1_ID\",\n    \"1\": \"MONITOR_2_ID\",\n    \"2\": \"MONITOR_3_ID\"\n  },\n  \"bar_configurations\": [\n    // this bar uses \"monitor_index\": 0,\n    \"path/to/bar_config_1.json\",\n    // this bar uses \"monitor_index\": 1,\n    \"path/to/bar_config_2.json\",\n    // this bar uses \"monitor_index\": 2,\n    \"path/to/bar_config_3.json\"\n  ]\n}\n```\n\nKomorebi uses an internal map to keep track of monitor to config indices, this map is called `monitor_usr_idx_map` it is\nan internal variable to komorebi that you don't need to do anything with, but you can see it with the [\n`komorebic state`](../cli/state.md) command (in case you need to debug something).\n\nAt first, komorebi will load all monitors and set the internal index map (`monitor_usr_idx_map`) as:\n\n```json\n{\n  // This is monitor A\n  \"0\": 0,\n  // This is monitor B\n  \"1\": 1,\n  // This is monitor C\n  \"2\": 2\n}\n```\n\nWhich kind of seems unnecessary, but imagine that then you disconnect monitor B (or it goes to sleep). Then komorebi\nwill only have 2 monitors with index 0 and 1, so the above map will be updated to this:\n\n```jsonc\n[\n  \"0\": 0, // This is monitor A\n  \"2\": 1, // This is now monitor C, because monitor B disconnected\n]\n```\n\nSo now the bar intended to be for monitor B, which was looking for index \"1\" on that map, doesn't see it and knows it\nshould be disabled. And the bar for monitor C looks at that map and knows that it's index \"2\" now maps to index 1 so it\nuses that index internally to get all the correct values about the monitor.\n\nIf you didn't have the `display_index_preferences` set, then when you disconnected monitor B, komorebi wouldn't know\nhow to map the indices and would use default behaviour which would result in a map like this:\n\n```json\n{\n  // This is monitor A \n  \"0\": 0,\n  // This is monitor C, because monitor B disconnected. However the bars will think it is monitor B because it has index \"1\" \n  \"1\": 1\n}\n```\n\n# Multiple monitors on different machines\n\nYou can use the same `komorebi.json` to configure two different setups and then synchronize your config across machines.\nHowever, if you do this it is important to be aware of a few things.\n\nFirstly, using `display_index_preferences` is required in this case.\n\nYou will need to get the `serial_number_id` or `device_id` of all the monitors of all your setups. With that information\nyou would then set your config like this:\n\n```json\n{\n  \"display_index_preferences\": {\n    \"0\": \"HOME_MONITOR_1_ID\",\n    \"1\": \"HOME_MONITOR_2_ID\",\n    \"2\": \"WORK_MONITOR_1_ID\",\n    \"3\": \"WORK_MONITOR_2_ID\"\n  },\n  \"monitors\": [\n    // HOME_MONITOR_1\n    {\n      \"workspaces\": [\n        // ...\n      ]\n    },\n    // HOME_MONITOR_2\n    {\n      \"workspaces\": [\n        // ...\n      ]\n    },\n    // WORK_MONITOR_1\n    {\n      \"workspaces\": [\n        // ...\n      ]\n    },\n    // WORK_MONITOR_2\n    {\n      \"workspaces\": [\n        // ...\n      ]\n    }\n  ]\n}\n```\n\n> [!NOTE]\n>\n> *You can't use the same config on two different monitors, you have to make a duplicated config for each monitor!*\n\nThen on the bar configs you need to set the bar's monitor index like this:\n\n```json\n// HOME_MONITOR_1_BAR.json\n{\n  \"monitor_index\": 0\n  //...\n}\n```\n\n```json\n// HOME_MONITOR_2_BAR.json\n{\n  \"monitor_index\": 1\n  //...\n}\n```\n\n```json\n// WORK_MONITOR_1_BAR.json\n{\n  \"monitor_index\": 2\n  //...\n}\n```\n\n```json\n// WORK_MONITOR_2_BAR.json\n{\n  \"monitor_index\": 3\n  //...\n}\n```\n\nAlthough you will only ever have 2 monitors connected at any one time, and they'll always have index 0 and 1, the\nabove config will still work on both physical configurations.\n\nThis is because komorebi will apply the appropriate config to the loaded monitors and will create a map of the user\nindex (the index defined in the user config) to the actual monitor index, and the bar will use that map to know if it\nshould be enabled, and where it should be drawn.\n\n# Windows Display Settings\n\nIn `Settings > System > Display > Multiple Displays`:\n\n- Disable \"Remember windows locations on monitor connection\"\n- Enable \"Minimize windows when a monitor is disconnected\"\n\n### Things to keep in mind\n\n* If you are using a laptop connected to one monitor at work and a different one at home, the work monitor and the home\n  monitor are considered different monitors by komorebi\n* When you disconnect from work, komorebi will keep the work monitor cached\n* You can still use a laptop alone without any monitor and if you need a window that was on the other monitor you can\n  press the taskbar icon or use `alt + tab` to bring it to focus and that window will now be part of the laptop monitor\n* If you then reconnect the work monitor, the cached version will be applied with all its windows (except any window(s)\n  you might have moved to another monitor)\n* If however, instead of reconnecting the work monitor, you connect the home monitor, then the work monitor will still\n  remain cached, and komorebi will load the home monitor from the cache (if it exists)\n* Sometimes when you disconnect/reconnect a monitor the event might be missed by komorebi, meaning that Windows will\n  show you both monitors but komorebi won't know about the existence of one of them\n* If you notice this type of weird behaviour, always run the [\n  `komorebic monitor-info`](../cli/monitor-information.md)\n  command and validate if one of the monitors is missing\n* To fix this you can try disconnecting and reconnecting the monitor again, or restarting komorebi\n"
  },
  {
    "path": "docs/common-workflows/multiple-bar-instances.md",
    "content": "# Multiple Bar Instances\n\nIf you would like to run multiple instances of `komorebi-bar` to target different monitors, it is possible to do so\nby maintaining multiple `komorebi.bar.json` configuration files and specifying their paths in the `bar_configurations`\narray in your `komorebi.json` configuration file.\n\n```json\n{\n  \"bar_configurations\": [\n    \"C:/Users/LGUG2Z/komorebi.bar.monitor1.json\",\n    \"C:/Users/LGUG2Z/komorebi.bar.monitor2.json\"\n  ]\n}\n```\n\nYou may also use `$Env:USERPROFILE` or `$Env:KOMOREBI_CONFIG_HOME` when specifying the paths.\n\nThe main difference between different `komorebi.bar.json` files will be the value of `monitor.index` which is used to\ntarget the monitor for each instance of `komorebi-bar`."
  },
  {
    "path": "docs/common-workflows/remove-gaps.md",
    "content": "# Remove Gaps\n\nIf you would like to remove all gaps by default, both between windows\nthemselves, and between the monitor edges and the windows, you can set the\nfollowing configuration options to `0` and `-1` in the `komorebi.json`\nconfiguration file.\n\n```json\n{\n  \"default_workspace_padding\": 0,\n  \"default_container_padding\": -1,\n}\n```\n\n[![Watch the tutorial video](https://img.youtube.com/vi/6QYLao953XE/hqdefault.jpg)](https://www.youtube.com/watch?v=6QYLao953XE)\n"
  },
  {
    "path": "docs/common-workflows/stackbar.md",
    "content": "# Stackbar\n\nIf you would like to add a visual stackbar to show which windows are in a container\nstack ensure the following options are defined in the `komorebi.json` configuration\nfile.\n\n```json\n{\n  \"stackbar\": {\n    \"height\": 40,\n    \"mode\": \"OnStack\",\n    \"tabs\": {\n      \"width\": 300,\n      \"focused_text\": \"#00a542\",\n      \"unfocused_text\": \"#b3b3b3\",\n      \"background\": \"#141414\"\n    }\n  }\n}\n```\n\nThis feature is not considered stable, and you may encounter visual artifacts\nfrom time to time."
  },
  {
    "path": "docs/common-workflows/tray-and-multi-window-applications.md",
    "content": "# Tray and Multi-Window Applications\n\n❗️**NOTE**: A significant number of tray and multi-window application rules for\nthe most common applications are [already generated for\nyou](https://github.com/LGUG2Z/komorebi/#generating-common-application-specific-configurations)\n\nIf you are experiencing behaviour where closing a window leaves a blank tile,\nbut minimizing the same window does not, you have probably enabled a\n'close/minimize to tray' option for that application. You can tell `komorebi`\nto handle this application appropriately by identifying it via the executable\nname or the window class.\n\n```json\n{\n  \"tray_and_multi_window_applications\": [\n    {\n      \"kind\": \"Class\",\n      \"id\": \"SDL_app\",\n      \"matching_strategy\": \"Equals\"\n    }\n  ]\n}\n```\n"
  },
  {
    "path": "docs/design.md",
    "content": "---\nhide:\n  - toc\n---\n\n## Description\n\n_komorebi_ only responds to [WinEvents](https://docs.microsoft.com/en-us/windows/win32/winauto/event-constants) and the\nmessages it receives on a dedicated socket.\n\n_komorebic_ is a CLI that writes messages on _komorebi_'s socket.\n\n_komorebi_ doesn't handle any keyboard or mouse inputs; a third party program (e.g.\n[whkd](https://github.com/LGUG2Z/whkd)) is needed in order to translate keyboard and mouse events to _komorebic_ commands.\n\nThis architecture, popularised by [_bspwm_](https://github.com/baskerville/bspwm) on Linux and\n[_yabai_](https://github.com/koekeishiya/yabai) on macOS, is outlined as follows:\n\n\n```\n          PROCESS                SOCKET\nwhkd/ahk  -------->  komorebic  <------>  komorebi\n```\n\n## Data Model\n\n_komorebi_ holds a list of physical monitors.\n\nA monitor is just a rectangle of the available work area which contains one or more virtual workspaces.\n\nA workspace holds a list of containers.\n\nA container is just a rectangle where one or more application windows can be displayed.\n\nThis means that:\n\n- Every monitor has its own collection of virtual workspaces\n- Workspaces only know about containers and their dimensions, not about individual application windows\n- Every application window must belong to a container, even if that container only contains one application window\n- Many application windows can be stacked and cycled through in the same container within a workspace\n"
  },
  {
    "path": "docs/example-configurations.md",
    "content": "`komorebi`, and tiling window managers in general, are very complex pieces of\nsoftware.\n\nIn an attempt to reduce some of the initial configuration burden for users who\nare looking to try out the software for the first time, example configurations\nare provided and updated whenever appropriate.\n\n## Downloading example configurations\n\nRun the following command to download example configuration files for\n`komorebi` and `whkd`. Pay attention to the output of the command to see where\nthe example files have been downloaded. For most new users this will be in the\n`$Env:USERPROFILE` directory.\n\n```powershell\nkomorebic quickstart\n```\n\n## Corporate Devices Enrolled in MDM\n\nIf you are using `komorebi` on a corporate device enrolled in mobile device\nmanagement, you will receive a pop-up when you run `komorebic start` reminding\nyou that the [Komorebi License](https://github.com/LGUG2Z/komorebi-license) does\nnot permit any kind of commercial use.\n\nYou can remove this pop-up by running `komorebic license <email>` with the email\nassociated with your Individual Commercial Use License. A single HTTP request\nwill be sent with the given email address to verify license validity.\n\n## Starting komorebi\n\nWith the example configurations downloaded, you can now start `komorebi`,\n`komorebi-bar` and `whkd`.\n\n```powershell\nkomorebic start --whkd --bar\n```\n\nIf you don't want to use the komorebi status bar, you can remove the `--bar` option\nfrom the above command.\n\n## komorebi.json\n\nThe example window manager configuration sets some sane defaults and provides\nseven preconfigured workspaces on the primary monitor each with a different\nlayout.\n\n```json\n{% include \"./komorebi.example.json\" %}\n```\n\n### Application-specific configuration\n\nThere is a [community-maintained\nrepository](https://github.com/LGUG2Z/komorebi-application-specific-configuration)\nof \"apps behaving badly\" that do not conform to Windows application development\nguidelines and behave erratically when used with `komorebi` without additional\nconfiguration.\n\nYou can always download the latest version of these configurations by running\n`komorebic fetch-asc`. The output of this command will also provide a line that\nyou can paste into `komorebi.json` to ensure that the window manager looks for\nthe file in the correction location.\n\nWhen installing and running `komorebi` for the first time, the `komorebic\nquickstart` command will usually download this file to the `$Env:USERPROFILE`\ndirectory.\n\n### Padding\n\nWhile you can set the workspace padding (the space between the outer edges of\nthe windows and the bezel of your monitor) and the container padding (the space\nbetween each of the tiled windows) for each workspace independently, you can\nalso set a default for both of these values that will apply to all workspaces\nusing `default_workspace_padding` and `default_container_padding`.\n\n### Active window border\n\nYou may have seen videos and screenshots of people using `komorebi` with a\nthick, colourful active window border. You can also enable this by setting\n`border` to `true`. However, please be warned that this feature\nis a crude hack trying to compensate for the insistence of Microsoft Windows\ndesign teams to make custom borders with widths that are actually visible to\nthe user a thing of the past and removing this capability from the Win32 API.\n\nI know it's buggy, and I know that most of the it sucks, but this is something\nyou should be bring up with the billion dollar company and not with me, the\nsolo developer.\n\n### Border colours\n\nIf you choose to use the active window border, you can set different colours to\ngive you visual queues when you are focused on a single window, a stack of\nwindows, or a window that is in monocle mode.\n\nThe example colours given are blue single, green for stack and pink for\nmonocle.\n\n### Layouts\n\n#### BSP\n\n```\n+-------+-----+\n|       |     |\n|       +--+--+\n|       |  |--|\n+-------+--+--+\n```\n\n#### Vertical Stack\n\n```\n+-------+-----+\n|       |     |\n|       +-----+\n|       |     |\n+-------+-----+\n```\n\n#### RightMainVerticalStack\n\n```\n+-----+-------+\n|     |       |\n+-----+       |\n|     |       |\n+-----+-------+\n```\n\n#### Horizontal Stack\n\n```\n+------+------+\n|             |\n|------+------+\n|      |      |\n+------+------+\n```\n\n#### Columns\n\n```\n+--+--+--+--+\n|  |  |  |  |\n|  |  |  |  |\n|  |  |  |  |\n+--+--+--+--+\n```\n\n#### Rows\n\nIf you have a vertical monitor, I recommend using this layout.\n\n```\n+-----------+\n|-----------|\n|-----------|\n|-----------|\n+-----------+\n```\n\n#### Ultrawide Vertical Stack\n\nIf you have an ultrawide monitor, I recommend using this layout.\n\n```\n+-----+-----------+-----+\n|     |           |     |\n|     |           +-----+\n|     |           |     |\n|     |           +-----+\n|     |           |     |\n+-----+-----------+-----+\n```\n\n### Grid\n\nIf you like the `grid` layout in [LeftWM](https://github.com/leftwm/leftwm-layouts) this is almost exactly the same!\n\nThe `grid` layout does not support resizing windows tiles.\n\n```\n+-----+-----+   +---+---+---+   +---+---+---+   +---+---+---+\n|     |     |   |   |   |   |   |   |   |   |   |   |   |   |\n|     |     |   |   |   |   |   |   |   |   |   |   |   +---+\n+-----+-----+   |   +---+---+   +---+---+---+   +---+---|   |\n|     |     |   |   |   |   |   |   |   |   |   |   |   +---+\n|     |     |   |   |   |   |   |   |   |   |   |   |   |   |\n+-----+-----+   +---+---+---+   +---+---+---+   +---+---+---+\n  4 windows       5 windows       6 windows       7 windows\n```\n\n## whkdrc\n\n`whkd` is a fairly basic piece of software with a simple configuration format:\nkey bindings go to the left of the colon, and shell commands go to the right of the\ncolon.\n\nAs of [`v0.2.4`](https://github.com/LGUG2Z/whkd/releases/tag/v0.2.4), `whkd` can override most of Microsoft's\nlimitations on hotkey bindings that include the `win` key. However, you will still need\nto [modify the registry](https://superuser.com/questions/1059511/how-to-disable-winl-in-windows-10) to prevent\n`win + l` from locking the operating system.\n\nYou can toggle an overlay of the current `whkdrc` shortcuts related to `komorebi` at any time when using the example\nconfiguration with `alt + i`.\n\n```\n{% include \"./whkdrc.sample\" %}\n```\n\n### Configuration\n\n`whkd` searches for a `whkdrc` configuration file in the following locations:\n\n* `$Env:WHKD_CONFIG_HOME`\n* `$Env:USERPROFILE/.config`\n\nIt is also possible to change a hotkey behavior depending on which application has focus:\n\n```\nalt + n [\n    # ProcessName as shown by `Get-Process`\n    Firefox       : echo \"hello firefox\"\n\n    # Spaces are fine, no quotes required\n    Google Chrome : echo \"hello chrome\"\n]\n```\n\n### Setting .shell\n\nThere is one special directive at the top of the file, `.shell` which can be\nset to either `powershell`, `pwsh` or `cmd`. Which one you use will depend on\nwhich shell you use in your terminal.\n\n* `powershell` - set this if you are using the version of PowerShell that comes\n  installed with Windows 10+ (the executable file for this is `powershell.exe`)\n\n* `pwsh` - set this if you are using PowerShell 7+, which you have installed yourself either through the Windows Store\n  or WinGet (the executable file for this is `pwsh.exe`)\n\n* `cmd` - set this if you don't want to use PowerShell at all and instead you\n  want to call commands through the shell used by the old-school Command\n  Prompt (the executable file for this is `cmd.exe`)\n\n### Key codes\n\nKey codes for alphanumeric and arrow keys are just what you would expect. For\npunctuation and other keys, please refer to the [Virtual Key\nCodes](https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes)\nreference.\n\nIf you want to use one of those key codes, put them into lower case and remove\nthe `VK_` prefix. For example, the keycode `VK_OEM_PLUS` becomes `oem_plus` in\nthe sample configuration above.\n\n## komorebi.bar.json\n\nThe example status bar configuration sets some sane defaults and provides\na number of pre-configured widgets on the primary monitor.\n\n```json\n{% include \"./komorebi.bar.example.json\" %}\n```\n\n### Themes\n\nThemes can be set in either `komorebi.json` or `komorebi.bar.json`. If set\nin `komorebi.json`, the theme will be applied to both komorebi's borders and\nstackbars as well as the status bar.\n\nIf set in `komorebi.bar.json`, the theme will only be applied to the status bar.\n\nAll [Catppuccin palette variants](https://catppuccin.com/)\nand [most Base16 palette variants](https://tinted-theming.github.io/tinted-gallery/)\nare available as themes.\n"
  },
  {
    "path": "docs/index.md",
    "content": "![screenshot](https://user-images.githubusercontent.com/13164844/184027064-f5a6cec2-2865-4d65-a549-a1f1da589abf.png)\n\n## Overview\n\n`komorebi` is a tiling window manager that works as an extension to Microsoft's\n[Desktop Window\nManager](https://docs.microsoft.com/en-us/windows/win32/dwm/dwm-overview) in\nWindows 10 and above.\n\n`komorebi` allows you to control application windows, virtual workspaces and\ndisplay monitors with a CLI which can be used with third-party software such as\n[AutoHotKey](https://github.com/Lexikos/AutoHotkey_L) to set user-defined\nkeyboard shortcuts.\n\n`komorebi` aims to make _as few modifications as possible_ to the operating\nsystem and desktop environment by default. Users are free to make such\nmodifications in their own configuration files for `komorebi`, but these will\nalways remain opt-in and off-by-default.\n\n## Community\n\nThere is a [Discord server](https://discord.gg/mGkn66PHkx) available for\n`komorebi`-related discussion, help, troubleshooting etc.\n\nThere is a [YouTube\nchannel](https://www.youtube.com/channel/UCeai3-do-9O4MNy9_xjO6mg) where I post\n`komorebi` development videos, feature previews and release overviews. Subscribing\nto the channel (which is monetized as part of the YouTube Partner Program) and\nwatching videos is a really simple and passive way to contribute financially to\nthe development and maintenance of `komorebi`.\n\nThere is an [Awesome List](https://github.com/LGUG2Z/awesome-komorebi) which\nshowcases the many awesome projects that exist in the `komorebi` ecosystem.\n\n## Licensing for Personal Use\n\n`komorebi` is licensed under the [Komorebi 2.0.0 license](https://github.com/LGUG2Z/komorebi-license), which is a fork\nof the [PolyForm Strict 1.0.0 license](https://polyformproject.org/licenses/strict/1.0.0). On a high level this means\nthat you are free to do whatever you want with `komorebi` for personal use other than redistribution, or distribution of\nnew works (i.e. hard-forks) based on the software.\n\nAnyone is free to make their own fork of `komorebi` with changes intended either for personal use or for integration\nback upstream via pull requests.\n\nThe [Komorebi 2.0.0 License](https://github.com/LGUG2Z/komorebi-license) does not permit any kind of commercial use (\ni.e. using `komorebi` at work).\n\n## Sponsorship for Personal Use\n\n`komorebi` is a free and educational source project, and one that encourages you\nto make charitable donations if you find the software to be useful and have the\nfinancial means.\n\nI encourage you to make a charitable donation to the [Palestine Children's\nRelief Fund](https://pcrf1.app.neoncrm.com/forms/gaza-recovery) or to contribute\nto a [Gaza Funds campaign](https://gazafunds.com) before you consider sponsoring\nme on GitHub.\n\n[GitHub Sponsors is enabled for this\nproject](https://github.com/sponsors/LGUG2Z). Sponsors can claim custom roles on\nthe Discord server, get shout-outs at the end of _komorebi_-related videos on\nYouTube, and gain the ability to submit feature requests on the issue tracker.\n\nIf you would like to tip or sponsor the project but are unable to use GitHub\nSponsors, you may also sponsor through [Ko-fi](https://ko-fi.com/lgug2z), or\nmake an anonymous Bitcoin donation to `bc1qv73wzspc77k46uty4vp85x8sdp24mphvm58f6q`.\n\n## Licensing for Commercial Use\n\nA dedicated Individual Commercial Use License is available for those who want to\nuse `komorebi` at work.\n\nThe Individual Commerical Use License adds “Commercial Use” as a “Permitted Use”\nfor the licensed individual only, for the duration of a valid paid license\nsubscription only. All provisions and restrictions enumerated in the [Komorebi\nLicense](https://github.com/LGUG2Z/komorebi-license) continue to apply.\n\nMore information, pricing and purchase links for Individual Commercial Use\nLicenses [can be found here](https://lgug2z.com/software/komorebi).\n"
  },
  {
    "path": "docs/installation.md",
    "content": "# Getting started\n\n`komorebi` is a tiling window manager for Windows that is comprised of two\nmain binaries, `komorebi.exe`, which contains the window manager itself,\nand `komorebic.exe`, which is the main way to send commands to the tiling\nwindow manager.\n\nIt is important to note that neither `komorebi.exe` nor `komorebic.exe` handle\nkey bindings, because `komorebi` is a tiling window manager and not a hotkey\ndaemon.\n\nThis getting started guide suggests the installation of\n[`whkd`](https://github.com/LGUG2Z/whkd) to allow you to bind `komorebic.exe`\ncommands to hotkeys to allow you to communicate with the tiling window manager\nusing keyboard shortcuts.\n\nHowever, `whkd` is a very simple hotkey daemon, and notably, does not include\nworkarounds for Microsoft's restrictions on hotkey combinations that can use\nthe `Windows` key.\n\nIf using hotkey combinations with the `Windows` key is important to you, I\nsuggest that once you are familiar with the main `komorebic.exe` commands used\nto manipulate the window manager, you use\n[AutoHotKey](https://www.autohotkey.com/) to handle your key bindings.\n\n`komorebi` also includes `komorebi-bar.exe`, a simple and reliable status bar which\nis deeply integrated with the tiling window manager, and can be customized with\nvarious widgets and themes.\n\n## Installation\n\n`komorebi` is available pre-built to install via\n[Scoop](https://scoop.sh/#/apps?q=komorebi) and\n[WinGet](https://winget.run/pkg/LGUG2Z/komorebi), and you may also build\nit from [source](https://github.com/LGUG2Z/komorebi) if you would prefer.\n\n- [Scoop](#scoop)\n- [WinGet](#winget)\n- [Building from source](#building-from-source)\n- [Offline](#offline)\n\n## Long path support\n\nIt is highly recommended that you enable support for long paths in Windows by\nrunning the following command in an Administrator Terminal before installing\n`komorebi`.\n\n```powershell\nSet-ItemProperty 'HKLM:\\SYSTEM\\CurrentControlSet\\Control\\FileSystem' -Name 'LongPathsEnabled' -Value 1\n```\n\n## Disabling unnecessary system animations\n\nIt is highly recommended that you enable the \"Turn off all unnecessary animations (when possible)\" option in\n\"Control Panel > Ease of Access > Ease of Access Centre / Make the computer easier to see\" for the best performance with\nkomorebi.\n\n## Scoop\n\nMake sure you have installed [`scoop`](https://scoop.sh) and verified that\ninstalled binaries are available in your `$PATH` before proceeding.\n\nIssues with `komorebi` and related commands not being recognized in the\nterminal ultimately come down to the `$PATH` environment variable not being\ncorrectly configured by your package manager and **should not** be raised as\nbugs or issues either on the `komorebi` GitHub repository or Discord server.\n\n### Install komorebi and whkd\n\nFirst add the extras bucket\n\n```powershell\nscoop bucket add extras\n```\n\nThen install the `komorebi` and `whkd` packages using `scoop install`\n\n```powershell\nscoop install komorebi whkd\n```\n\nOnce komorebi is installed, proceed to get the [example\nconfigurations](example-configurations.md).\n\n## WinGet\n\nMake sure you have installed the latest version of\n[`winget`](https://learn.microsoft.com/en-us/windows/package-manager/winget/)\nand verified that installed binaries are available in your `$PATH` before\nproceeding.\n\nIssues with `komorebi` and related commands not being recognized in the\nterminal ultimately come down to the `$PATH` environment variable not being\ncorrectly configured by your package manager and **should not** be raised as\nbugs or issues either on the `komorebi` GitHub repository or Discord server.\n\n### Install komorebi and whkd\n\nInstall the `komorebi` and `whkd` packages using `winget install`\n\n```powershell\nwinget install LGUG2Z.komorebi\nwinget install LGUG2Z.whkd\n```\n\nOnce komorebi is installed, proceed to get the [example\nconfigurations](example-configurations.md).\n\n## Building from source\n\nMake sure you have installed [`rustup`](https://rustup.rs), a stable `rust`\ncompiler toolchain, and the Visual Studio [Visual Studio\nprerequisites](https://rust-lang.github.io/rustup/installation/windows-msvc.html).\n\nClone the git repository, enter the directory, and build the following binaries:\n\n```powershell\ncargo +stable install --path komorebi --locked\ncargo +stable install --path komorebic --locked\ncargo +stable install --path komorebic-no-console --locked\ncargo +stable install --path komorebi-gui --locked\ncargo +stable install --path komorebi-bar --locked\ncargo +stable install --path komorebi-shortcuts --locked\n```\n\nIf the binaries have been built and added to your `$PATH` correctly, you should\nsee some output when running `komorebi --help` and `komorebic --help`\n\n### Offline\n\nDownload the latest [komorebi](https://github.com/LGUG2Z/komorebi/releases)\nand [whkd](https://github.com/LGUG2Z/whkd/releases) MSI installers on an internet-connected computer, then copy them to\nan offline machine to install.\n\nOnce installed, proceed to get the [example configurations](example-configurations.md) (none of the commands for\nfirst-time set up and running komorebi require an internet connection).\n\n## Upgrades\n\nBefore upgrading, make sure to run `komorebic stop --whkd --bar`. This is to ensure that all the current\nkomorebi-related exe files can be replaced without issue.\n\nThen, depending on whether you installed via `scoop` or `winget`, you can run the appropriate command:\n\n```powershell\n# for winget\nwinget upgrade LGUG2Z.komorebi\n```\n\n```powershell\n# for scoop\nscoop update komorebi\n```\n\nOnce the upgrade is completed you can confirm that you have the latest version by running `komorebic --version`, and\nthen start it with `komorebic start --whkd --bar`.\n\n## Uninstallation\n\nBefore uninstalling, first run `komorebic stop --whkd --bar` to make sure that\nthe `komorebi`, `komorebi-bar` and `whkd` processes have been stopped.\n\nThen, depending on whether you installed with Scoop or WinGet, run `scoop\nuninstall komorebi whkd` or `winget uninstall LGUG2Z.komorebi LGUG2Z.whkd`.\n\nFinally, you can run the following commands in a PowerShell prompt to clean up\nfiles created by the `quickstart` command and any other runtime files:\n\n```powershell\nrm $Env:USERPROFILE\\komorebi.json\nrm $Env:USERPROFILE\\applications.json\nrm $Env:USERPROFILE\\.config\\whkdrc\nrm -r -Force $Env:LOCALAPPDATA\\komorebi\n```\n"
  },
  {
    "path": "docs/komorebi.ahk.txt",
    "content": "#Requires AutoHotkey v2.0.2\n#SingleInstance Force\n\nKomorebic(cmd) {\n    RunWait(format(\"komorebic.exe {}\", cmd), , \"Hide\")\n}\n\n!q::Komorebic(\"close\")\n!m::Komorebic(\"minimize\")\n\n; Focus windows\n!h::Komorebic(\"focus left\")\n!j::Komorebic(\"focus down\")\n!k::Komorebic(\"focus up\")\n!l::Komorebic(\"focus right\")\n\n!+[::Komorebic(\"cycle-focus previous\")\n!+]::Komorebic(\"cycle-focus next\")\n\n; Move windows\n!+h::Komorebic(\"move left\")\n!+j::Komorebic(\"move down\")\n!+k::Komorebic(\"move up\")\n!+l::Komorebic(\"move right\")\n\n; Stack windows\n!Left::Komorebic(\"stack left\")\n!Down::Komorebic(\"stack down\")\n!Up::Komorebic(\"stack up\")\n!Right::Komorebic(\"stack right\")\n!;::Komorebic(\"unstack\")\n![::Komorebic(\"cycle-stack previous\")\n!]::Komorebic(\"cycle-stack next\")\n\n; Resize\n!=::Komorebic(\"resize-axis horizontal increase\")\n!-::Komorebic(\"resize-axis horizontal decrease\")\n!+=::Komorebic(\"resize-axis vertical increase\")\n!+_::Komorebic(\"resize-axis vertical decrease\")\n\n; Manipulate windows\n!t::Komorebic(\"toggle-float\")\n!f::Komorebic(\"toggle-monocle\")\n\n; Window manager options\n!+r::Komorebic(\"retile\")\n!p::Komorebic(\"toggle-pause\")\n\n; Layouts\n!x::Komorebic(\"flip-layout horizontal\")\n!y::Komorebic(\"flip-layout vertical\")\n\n; Workspaces\n!1::Komorebic(\"focus-workspace 0\")\n!2::Komorebic(\"focus-workspace 1\")\n!3::Komorebic(\"focus-workspace 2\")\n!4::Komorebic(\"focus-workspace 3\")\n!5::Komorebic(\"focus-workspace 4\")\n!6::Komorebic(\"focus-workspace 5\")\n!7::Komorebic(\"focus-workspace 6\")\n!8::Komorebic(\"focus-workspace 7\")\n\n; Move windows across workspaces\n!+1::Komorebic(\"move-to-workspace 0\")\n!+2::Komorebic(\"move-to-workspace 1\")\n!+3::Komorebic(\"move-to-workspace 2\")\n!+4::Komorebic(\"move-to-workspace 3\")\n!+5::Komorebic(\"move-to-workspace 4\")\n!+6::Komorebic(\"move-to-workspace 5\")\n!+7::Komorebic(\"move-to-workspace 6\")\n!+8::Komorebic(\"move-to-workspace 7\")\n"
  },
  {
    "path": "docs/komorebi.bar.example.json",
    "content": "{\n  \"$schema\": \"https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.41/schema.bar.json\",\n  \"font_family\": \"JetBrains Mono\",\n  \"theme\": {\n    \"palette\": \"Base16\",\n    \"name\": \"Ashes\",\n    \"accent\": \"Base0D\"\n  },\n  \"left_widgets\": [\n    {\n      \"Komorebi\": {\n        \"workspaces\": {\n          \"enable\": true,\n          \"hide_empty_workspaces\": false\n        },\n        \"layout\": {\n          \"enable\": true\n        },\n        \"focused_window\": {\n          \"enable\": true,\n          \"show_icon\": true\n        }\n      }\n    }\n  ],\n  \"right_widgets\": [\n    {\n      \"Update\": {\n        \"enable\": true\n      }\n    },\n    {\n      \"Media\": {\n        \"enable\": false\n      }\n    },\n    {\n      \"Storage\": {\n        \"enable\": false\n      }\n    },\n    {\n      \"Memory\": {\n        \"enable\": false\n      }\n    },\n    {\n      \"Network\": {\n        \"enable\": false,\n        \"show_activity\": true,\n        \"show_total_activity\": true\n      }\n    },\n    {\n      \"Date\": {\n        \"enable\": true,\n        \"format\": \"DayDateMonthYear\"\n      }\n    },\n    {\n      \"Time\": {\n        \"enable\": true,\n        \"format\": \"TwentyFourHour\"\n      }\n    },\n    {\n      \"Battery\": {\n        \"enable\": true\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "docs/komorebi.example.json",
    "content": "{\n  \"$schema\": \"https://raw.githubusercontent.com/LGUG2Z/komorebi/v0.1.41/schema.json\",\n  \"app_specific_configuration_path\": \"$Env:USERPROFILE/applications.json\",\n  \"window_hiding_behaviour\": \"Cloak\",\n  \"cross_monitor_move_behaviour\": \"Insert\",\n  \"default_workspace_padding\": 20,\n  \"default_container_padding\": 20,\n  \"border\": true,\n  \"border_width\": 8,\n  \"border_offset\": -1,\n  \"theme\": {\n    \"palette\": \"Base16\",\n    \"name\": \"Ashes\",\n    \"unfocused_border\": \"Base03\",\n    \"bar_accent\": \"Base0D\"\n  },\n  \"monitors\": [\n    {\n      \"workspaces\": [\n        {\n          \"name\": \"I\",\n          \"layout\": \"BSP\"\n        },\n        {\n          \"name\": \"II\",\n          \"layout\": \"VerticalStack\"\n        },\n        {\n          \"name\": \"III\",\n          \"layout\": \"HorizontalStack\"\n        },\n        {\n          \"name\": \"IV\",\n          \"layout\": \"UltrawideVerticalStack\"\n        },\n        {\n          \"name\": \"V\",\n          \"layout\": \"Rows\"\n        },\n        {\n          \"name\": \"VI\",\n          \"layout\": \"Grid\"\n        },\n        {\n          \"name\": \"VII\",\n          \"layout\": \"RightMainVerticalStack\"\n        }\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": "docs/troubleshooting.md",
    "content": "# Troubleshooting\n\n## Phantom Tiles\n\nSometimes you may experience an application which leaves \"ghost tiles\" on a workspace, where there is space reserved for\na window but no window visible.\n\nYou can ignore these windows by following these steps:\n\n* Run `komorebic visible-windows` to find details about the invisible window\n* Using that information, [create a rule to ignore that window](common-workflows/ignore-windows.md)\n\n## AutoHotKey executable not found\n\nIf you try to start komorebi with AHK using `komorebic start --ahk`, and you have\nnot installed AHK using `scoop`, you'll probably receive an error:\n\n```text\nError: could not find autohotkey, please make sure it is installed before using the --ahk flag\n```\n\nDepending on how AHK is installed the executable on your system may have a\ndifferent name. In order to account for this, you may set the `KOMOREBI_AHK_EXE`\nenvironment variable in your\n[PowerShell profile](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_profiles?view=powershell-7.4)\nto match the name of the executable as it is found on your system.\n\nAfter setting `KOMOREBI_AHK_EXE` make sure to either reload your PowerShell\nprofile or open a new terminal tab.\n\n## Komorebi is unresponsive when the display wakes from sleep\n\nThis can happen in rare cases when your monitor state is not preserved after it\nwakes from sleep.\n\n### Problem\n\nYour hotkeys in _whkd_ work, but it feels as if _komorebi_ knows nothing about\nthe previous state (you can't control previous windows, although newly launched ones\ncan be manipulated as normal).\n\n### Solution\n\nSome monitors, such as the Samsung G8/G9 (LED, Neo, OLED) have an _adaptive\nsync_ or _variable refresh rate_ setting within the actual monitor OSD that can\ndisrupt how the device is persisted in the _komorebi_ state following suspension.\n\nTo fix this, please try to disable _Adaptive Sync_ or any other _VRR_ branded\nalias by referring to the manufacturer's documentation.\n\n!!! warning\n\n    Disabling VRR within Windows (e.g. _Nvidia Control Panel_) may work and can indeed\n    change the configuration you see within your monitor's OSD, but some monitors\n    will re-enable the setting regardless following suspension.\n\n### Reproducing\n\nEnsure _komorebi_ is in an operational state by executing `komorebic start` as\nnormal.\n\nIf _komorebi_ is already unresponsive, then please restart _komorebi_ first by\nrunning `komorebic stop` and `komorebic start`.\n\n1. **`komorebic state`**\n\n   ```json\n   {\n     \"monitors\": {\n       \"elements\": [\n         {\n           \"id\": 65537,\n           \"name\": \"DISPLAY1\",\n           \"device\": \"SAM71AA\",\n           \"device_id\": \"SAM71AA-5&a1a3e88&0&UID24834\",\n           \"size\": {\n             \"left\": 0,\n             \"top\": 0,\n             \"right\": 5120,\n             \"bottom\": 1440\n           }\n         }\n       ]\n     }\n   }\n   ```\n\n   This appears to be fine -- _komorebi_ is aware of the device and associated\n   window handles.\n\n2. **Let your display go to sleep.**\n\n   Simply turning the monitor off is not enough to reproduce the problem; you must\n   let Windows turn off the display itself.\n\n   To avoid waiting an eternity:\n\n    - _Control Panel_ -> _Hardware and Sound_ -> _Power Options_ -> _Edit Plan\n      Settings_\n\n      _Turn off the display: 1 minute_\n\n   Allow a minute for the display to reset, then once it actually shuts off\n   allow for any additional time as prompted by your monitor for the cycle to\n   complete.\n\n3. **Wake your display again** by pressing any key.\n\n   _komorebi_ should now be unresponsive.\n\n4. **`komorebic state`**\n\n   Don't stop _komorebi_ just yet.\n\n   Since it's unresponsive, you can open another shell instead to execute the above command.\n\n   ```json\n   {\n     \"monitors\": {\n       \"elements\": [\n         {\n           \"id\": 65537,\n           \"name\": \"DISPLAY1\",\n           \"device\": null,\n           \"device_id\": null\n         }\n       ]\n     }\n   }\n   ```\n\n   We can see the _komorebi_ state is no longer associated with the previous\n   device: `null`, suggesting an issue when the display resumes from a suspended\n   state.\n\n## Komorebi Bar does not render transparency on Nvidia GPUs\n\nUsers with Nvidia GPUs may have issues with transparency on the Komorebi Bar.\n\nTo solve this the user can do the following:\n\n- Open the Nvidia Control Panel\n- On the left menu tree, under \"3D Settings\", select \"Manage 3D Settings\"\n- Select the \"Program Settings\" tab\n- Press the \"Add\" button and select \"komorebi-bar\"\n- Under \"3. Specify the settings for this program:\", find the feature labelled, \"OpenGL GDI compatibility\"\n- Change the setting to \"Prefer compatibility\"\n- At the bottom of the window select \"Apply\"\n- Restart the Komorebi Bar with \"komorebic stop --bar; komorebic start --bar\"\n\nThis should resolve the issue and your Komorebi Bar should render with the proper transparency.\n"
  },
  {
    "path": "docs/usage/focusing-windows.md",
    "content": "# Focusing Windows\n\nWindows can be focused in a direction (left, down, up, right) using the [`komorebic focus`](../cli/focus.md) command.\n\n```\n# example showing how you might bind this command\n\nalt + h                 : komorebic focus left\nalt + j                 : komorebic focus down\nalt + k                 : komorebic focus up\nalt + l                 : komorebic focus right\n```\n\nWindows can be focused in a cycle direction (previous, next) using the [`komorebic cycle-focus`](../cli/cycle-focus.md)\ncommand.\n\n```\n# example showing you might bind this command\n\nalt + shift + oem_4     : komorebic cycle-focus previous # oem_4 is [\nalt + shift + oem_6     : komorebic cycle-focus next # oem_6 is ]\n```\n\nIt is possible to attempt to focus the first window, on any workspace, matching an exe using the [\n`komorebic eager-focus`](../cli/eager-focus.md) command.\n\n```\n# example showing how you might bind this command\n\nwin + 1                 : komorebic eager-focus firefox.exe\n```\n\nThe window at the largest tile can be focused using the [`komorebic promote-focus`](../cli/promote-focus.md) command.\n\n```\n# example showing how you might bind this command\n\nalt + return            : komorebic promote-focus\n```\n\nThe behaviour when attempting to call `komorebic focus` when at the left or right edge of a monitor is determined by\nthe [`cross_boundary_behaviour`](https://komorebi.lgug2z.com/schema#cross_boundary_behaviour) configuration option.\n\nWhen set to `Workspace`, the next workspace on the same monitor will be focused.\n\nWhen set to `Monitor`, the focused workspace on the next monitor in the given direction will be focused."
  },
  {
    "path": "docs/usage/focusing-workspaces.md",
    "content": "# Focusing Workspaces\n\nWorkspaces on the focused monitor can be focused by their index using the [\n`komorebic focus-workspace`](../cli/focus-workspace.md)  command.\n\nIf this command is called with an index for a workspace which does not exist, that workspace, and all workspace indexes\nrequired to get to that workspace, will be created.\n\n```\n# example showing how you might bind this command\n\nalt + 1                 : komorebic focus-workspace 0\nalt + 2                 : komorebic focus-workspace 1\nalt + 3                 : komorebic focus-workspace 2\n```\n\nWorkspaces on the focused monitor can be focused in a cycle direction (previous, next) using the [\n`komorebic cycle-workspace`](../cli/cycle-workspace.md) command.\n\n```\n# example showing how you might bind this command\n\nalt + shift + oem_4     : komorebic cycle-workspace previous # oem_4 is [\nalt + shift + oem_6     : komorebic cycle-workspace next # oem_6 is ]\n```\n\nWorkspaces on other monitors can be focused by both the monitor index and the workspace index using the [\n`komorebic focus-monitor-workspace`](../cli/focus-monitor-workspace.md) command.\n\n```\n# example showing how you might bind this command\n\nalt + 1                 : komorebic focus-monitor-workspace 0 0\nalt + 2                 : komorebic focus-monitor-workspace 0 1 \nalt + 3                 : komorebic focus-monitor-workspace 1 0\n```\n\nWorkspaces on any monitor can be focused by their name (given that all workspace names across all monitors are unique)\nusing the [`komorebic focus-named-workspace`](../cli/focus-named-workspace.md)  command.\n\n```\n# example showing how you might bind this command\n\nalt + c                 : komorebic focus-named-workspace coding\n```\n\nWorkspaces on all monitors can be set to the same index (emulating single workspaces which span across all monitors)\nusing the [`komorebic focus-workspaces`](../cli/focus-workspaces.md) command.\n\n```\n# example showing how you might bind this command\n\nalt + 1                 : komorebic focus-workspaces 0\nalt + 2                 : komorebic focus-workspaces 1\nalt + 3                 : komorebic focus-workspaces 2\n```\n\nThe last focused workspace on the focused monitor can be re-focused using the [\n`komorebic focus-last-workspace`](../cli/focus-last-workspace.md) command.\n"
  },
  {
    "path": "docs/usage/moving-windows-across-workspaces.md",
    "content": "# Moving Windows Across Workspaces\n\nWindows can be moved to another workspace on the focused monitor using the [\n`komorebic move-to-workspace`](../cli/move-to-workspace.md) command. This command will also move your focus to the\ntarget workspace.\n\n```\n# example showing how you might bind this command\n\nalt + shift + 1         : komorebic move-to-workspace 0\nalt + shift + 2         : komorebic move-to-workspace 1\nalt + shift + 3         : komorebic move-to-workspace 2\n```\n\nWindows can be sent to another workspace on the focused monitor using the [\n`komorebic send-to-workspace`](../cli/send-to-workspace.md) command. This command will keep your focus on the origin\nworkspace.\n\n```\n# example showing how you might bind this command\n\nalt + shift + 1         : komorebic send-to-workspace 0\nalt + shift + 2         : komorebic send-to-workspace 1\nalt + shift + 3         : komorebic send-to-workspace 2\n```\n\nWindows can be moved to another workspace on the focused monitor in a cycle direction (previous, next) using the [\n`komorebic cycle-move-to-workspace`](../cli/cycle-move-to-workspace.md) command. This command will also move your focus\nto the target workspace.\n\n```\n# example showing how you might bind this command\n\nalt + shift + oem_4     : komorebic cycle-move-to-workspace previous # oem_4 is [\nalt + shift + oem_6     : komorebic cycle-move-to-workspace next # oem_6 is ]\n```\n\nWindows can be sent to another workspace on the focused monitor in a cycle direction (previous, next) using the [\n`komorebic cycle-move-to-workspace`](../cli/cycle-move-to-workspace.md) command. This command will keep your focus on\nthe origin workspace.\n\n```\n# example showing how you might bind this command\n\nalt + shift + oem_4     : komorebic cycle-send-to-workspace previous # oem_4 is [\nalt + shift + oem_6     : komorebic cycle-send-to-workspace next # oem_6 is ]\n```\n\nWindows can be moved or sent to the focused workspace on a another monitor using the [\n`komorebic move-to-monitor`](../cli/move-to-monitor.md) and [`komorebic send-to-monitor`](../cli/send-to-monitor.md)\ncommands.\n\nWindows can be moved or sent to the focused workspace on a monitor in a cycle direction (previous, next) using the [\n`komorebic cycle-move-to-monitor`](../cli/cycle-move-to-monitor.md) and [\n`komorebic cycle-send-to-monitor`](../cli/cycle-send-to-monitor.md) commands.\n\nWindows can be moved or sent to a named workspace on any monitor (given that all workspace names across all monitors are\nunique) using the [`komorebic move-to-named-workspace`](../cli/move-to-named-workspace.md) and [\n`komorebic send-to-named-workspace`](../cli/send-to-named-workspace.md) commands\n"
  },
  {
    "path": "docs/usage/moving-windows.md",
    "content": "# Moving Windows\n\nWindows can be moved in a direction (left, down, up, right) using the [`komorebic move`](../cli/move.md) command.\n\n```\n# example showing how you might bind this command\n\nalt + shift + h         : komorebic move left\nalt + shift + j         : komorebic move down\nalt + shift + k         : komorebic move up\nalt + shift + l         : komorebic move right\n```\n\nWindows can be moved in a cycle direction (previous, next) using the [`komorebic cycle-move`](../cli/cycle-move.md)\ncommand.\n\n```\n# example showing how you might bind this command\n\nalt + shift + oem_4     : komorebic cycle-move previous # oem_4 is [\nalt + shift + oem_6     : komorebic cycle-move next # oem_6 is ]\n```\n\nThe focused window can be moved to the largest tile using the [`komorebic promote`](../cli/promote.md) command.\n\n```\n# example showing how you might bind this command\n\nalt + shift + return    : komorebic promote\n```\n\nThe behaviour when attempting to call `komorebic move` when at the left or right edge of a monitor is determined by\nthe [`cross_boundary_behaviour`](https://komorebi.lgug2z.com/schema#cross_boundary_behaviour) configuration option.\n\nWhen set to `Workspace`, the focused window will be moved to the next workspace on the focused monitor in the given\ndirection\n\nWhen set to `Monitor`, the focused window will be moved to the focused workspace on the next monitor in the given\ndirection.\n\nThe behaviour when calling `komorebic move` with `cross_boundary_behaviour` set to `Monitor` can be further refined with\nthe [`cross_monitor_move_behaviour`](https://komorebi.lgug2z.com/schema#cross_monitor_move_behaviour) configuration\noption.\n\nWhen set to `Swap`, the focused window will be swapped with the window at the corresponding edge of the adjacent monitor\n\nWhen set to `Insert`, the focused window will be inserted into the focused workspace on the adjacent monitor.\n\nWhen set to `NoOp`, the focused window will not be moved across a monitor boundary, though focusing across monitor\nboundaries will continue to function."
  },
  {
    "path": "docs/usage/stacking-windows.md",
    "content": "# Stacking Windows\n\nWindows can be stacked in a direction (left, down, up, right) using the [`komorebic stack`](../cli/stack.md) command.\n\n```\n# example showing how you might bind this command\n\nalt + left              : komorebic stack left\nalt + down              : komorebic stack down\nalt + up                : komorebic stack up\nalt + right             : komorebic stack right\n```\n\nWindows can be popped from a stack using the [`komorebic unstack`](../cli/unstack.md) command.\n\n```\n# example showing how you might bind this command\n\nalt + oem_1             : komorebic unstack # oem_1 is ;\n```\n\nWindows in a stack can be focused in a cycle direction (previous, next) using the [\n`komorebic cycle-stack`](../cli/cycle-stack.md) command.\n\n```\n# example showing how you might bind this command\n\nalt + oem_4             : komorebic cycle-stack previous # oem_4 is [\nalt + oem_6             : komorebic cycle-stack next # oem_6 is ]\n```\n\nWindows in a stack can have their positions in the stack moved in a cycle direction (previous, next) using the [\n`komorebic cycle-stack-index`](../cli/cycle-stack-index.md) command.\n\n```\n# example showing how you might bind this command\n\nalt + shift + oem_4     : komorebic cycle-stack-index previous # oem_4 is [\nalt + shift + oem_6     : komorebic cycle-stack-index next # oem_6 is ]\n```\n\nWindows in a stack can be focused by their index in the stack using the [\n`komorebic focus-stack-window`](../cli/focus-stack-window.md) command.\n\nAll windows on the focused workspace can be combined into a single stack using the [\n`komorebic stack-all`](../cli/stack-all.md) command.\n\nAll windows in a focused stack can be popped using the [`komorebic unstack-all`](../cli/unstack-all.md) command.\n\nIt is possible to tell the window manager to stack the next opened window on top of the currently focused window by\nusing the [\n`komorebic toggle-workspace-window-container-behaviour`](../cli/toggle-workspace-window-container-behaviour.md) command.\n"
  },
  {
    "path": "docs/whkdrc.sample",
    "content": ".shell powershell\n\n# Reload whkd configuration\n# alt + o                 : taskkill /f /im whkd.exe && start /b whkd # if shell is cmd\nalt + o                 : taskkill /f /im whkd.exe; Start-Process whkd -WindowStyle hidden # if shell is pwsh / powershell\nalt + shift + o         : komorebic reload-configuration\n\nalt + i                 : komorebic toggle-shortcuts\n\n# App shortcuts - these require shell to be pwsh / powershell\n# The apps will be focused if open, or launched if not open\n# alt + f                 : if ($wshell.AppActivate('Firefox') -eq $False) { start firefox }\n# alt + b                 : if ($wshell.AppActivate('Chrome') -eq $False) { start chrome }\n\nalt + q                 : komorebic close\nalt + m                 : komorebic minimize\n\n# Focus windows\nalt + h                 : komorebic focus left\nalt + j                 : komorebic focus down\nalt + k                 : komorebic focus up\nalt + l                 : komorebic focus right\nalt + shift + oem_4     : komorebic cycle-focus previous # oem_4 is [\nalt + shift + oem_6     : komorebic cycle-focus next # oem_6 is ]\n\n# Move windows\nalt + shift + h         : komorebic move left\nalt + shift + j         : komorebic move down\nalt + shift + k         : komorebic move up\nalt + shift + l         : komorebic move right\nalt + shift + return    : komorebic promote\n\n# Stack windows\nalt + left              : komorebic stack left\nalt + down              : komorebic stack down\nalt + up                : komorebic stack up\nalt + right             : komorebic stack right\nalt + oem_1             : komorebic unstack # oem_1 is ;\nalt + oem_4             : komorebic cycle-stack previous # oem_4 is [\nalt + oem_6             : komorebic cycle-stack next # oem_6 is ]\n\n# Resize\nalt + oem_plus          : komorebic resize-axis horizontal increase\nalt + oem_minus         : komorebic resize-axis horizontal decrease\nalt + shift + oem_plus  : komorebic resize-axis vertical increase\nalt + shift + oem_minus : komorebic resize-axis vertical decrease\n\n# Manipulate windows\nalt + t                 : komorebic toggle-float\nalt + shift + f         : komorebic toggle-monocle\n\n# Window manager options\nalt + shift + r         : komorebic retile\nalt + p                 : komorebic toggle-pause\n\n# Layouts\nalt + x                 : komorebic flip-layout horizontal\nalt + y                 : komorebic flip-layout vertical\n\n# Workspaces\nalt + 1                 : komorebic focus-workspace 0\nalt + 2                 : komorebic focus-workspace 1\nalt + 3                 : komorebic focus-workspace 2\nalt + 4                 : komorebic focus-workspace 3\nalt + 5                 : komorebic focus-workspace 4\nalt + 6                 : komorebic focus-workspace 5\nalt + 7                 : komorebic focus-workspace 6\nalt + 8                 : komorebic focus-workspace 7\n\n# Move windows across workspaces\nalt + shift + 1         : komorebic move-to-workspace 0\nalt + shift + 2         : komorebic move-to-workspace 1\nalt + shift + 3         : komorebic move-to-workspace 2\nalt + shift + 4         : komorebic move-to-workspace 3\nalt + shift + 5         : komorebic move-to-workspace 4\nalt + shift + 6         : komorebic move-to-workspace 5\nalt + shift + 7         : komorebic move-to-workspace 6\nalt + shift + 8         : komorebic move-to-workspace 7\n"
  },
  {
    "path": "flake.nix",
    "content": "{\n  description = \"komorebi for Windows\";\n\n  inputs = {\n    nixpkgs.url = \"github:NixOS/nixpkgs/nixpkgs-unstable\";\n    flake-parts.url = \"github:hercules-ci/flake-parts\";\n    crane.url = \"github:ipetkov/crane\";\n    rust-overlay.url = \"github:oxalica/rust-overlay\";\n    rust-overlay.inputs.nixpkgs.follows = \"nixpkgs\";\n    treefmt-nix.url = \"github:numtide/treefmt-nix\";\n    treefmt-nix.inputs.nixpkgs.follows = \"nixpkgs\";\n    git-hooks-nix.url = \"github:cachix/git-hooks.nix\";\n    git-hooks-nix.inputs.nixpkgs.follows = \"nixpkgs\";\n  };\n\n  outputs =\n    inputs@{\n      self,\n      nixpkgs,\n      flake-parts,\n      crane,\n      rust-overlay,\n      ...\n    }:\n    let\n      windowsSdkVersion = \"10.0.26100\";\n      windowsCrtVersion = \"14.44.17.14\";\n\n      mkWindowsSdk =\n        pkgs:\n        pkgs.stdenvNoCC.mkDerivation {\n          name = \"windows-sdk-${windowsSdkVersion}-crt-${windowsCrtVersion}\";\n\n          nativeBuildInputs = [ pkgs.xwin ];\n\n          outputHashAlgo = \"sha256\";\n          outputHashMode = \"recursive\";\n          outputHash = \"sha256-6cLS5q1BDRpLPScfmmKpTTEHUzsgKTKD1+mKvGX9Deo=\";\n\n          buildCommand = ''\n            export HOME=$(mktemp -d)\n            xwin --accept-license \\\n              --sdk-version ${windowsSdkVersion} \\\n              --crt-version ${windowsCrtVersion} \\\n              splat --output $out\n          '';\n        };\n\n      mkMsvcEnv =\n        { pkgs, windowsSdk }:\n        let\n          clangVersion = pkgs.lib.versions.major pkgs.llvmPackages.clang.version;\n        in\n        {\n          # linker for the windows target\n          CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER = \"lld-link\";\n\n          # c/c++ compiler\n          CC_x86_64_pc_windows_msvc = \"clang-cl\";\n          CXX_x86_64_pc_windows_msvc = \"clang-cl\";\n          AR_x86_64_pc_windows_msvc = \"llvm-lib\";\n\n          # IMPORTANT: libclang include path MUST come first to avoid header conflicts\n          CFLAGS_x86_64_pc_windows_msvc = builtins.concatStringsSep \" \" [\n            \"--target=x86_64-pc-windows-msvc\"\n            \"-Wno-unused-command-line-argument\"\n            \"-fuse-ld=lld-link\"\n            \"/imsvc${pkgs.llvmPackages.libclang.lib}/lib/clang/${clangVersion}/include\"\n            \"/imsvc${windowsSdk}/crt/include\"\n            \"/imsvc${windowsSdk}/sdk/include/ucrt\"\n            \"/imsvc${windowsSdk}/sdk/include/um\"\n            \"/imsvc${windowsSdk}/sdk/include/shared\"\n          ];\n\n          CXXFLAGS_x86_64_pc_windows_msvc = builtins.concatStringsSep \" \" [\n            \"--target=x86_64-pc-windows-msvc\"\n            \"-Wno-unused-command-line-argument\"\n            \"-fuse-ld=lld-link\"\n            \"/imsvc${pkgs.llvmPackages.libclang.lib}/lib/clang/${clangVersion}/include\"\n            \"/imsvc${windowsSdk}/crt/include\"\n            \"/imsvc${windowsSdk}/sdk/include/ucrt\"\n            \"/imsvc${windowsSdk}/sdk/include/um\"\n            \"/imsvc${windowsSdk}/sdk/include/shared\"\n          ];\n\n          # target-specific rust flags with linker flavor and library search paths\n          CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_RUSTFLAGS = builtins.concatStringsSep \" \" [\n            \"-Clinker-flavor=lld-link\"\n            \"-Lnative=${windowsSdk}/crt/lib/x86_64\"\n            \"-Lnative=${windowsSdk}/sdk/lib/um/x86_64\"\n            \"-Lnative=${windowsSdk}/sdk/lib/ucrt/x86_64\"\n          ];\n\n          # cargo target\n          CARGO_BUILD_TARGET = \"x86_64-pc-windows-msvc\";\n        };\n\n      mkKomorebiPackages =\n        { pkgs, windowsSdk }:\n        let\n          # toolchain with windows msvc target\n          toolchain = (pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml).override {\n            targets = [ \"x86_64-pc-windows-msvc\" ];\n          };\n          craneLib = (crane.mkLib pkgs).overrideToolchain toolchain;\n          version = \"0.1.0\";\n\n          msvcEnv = mkMsvcEnv { inherit pkgs windowsSdk; };\n\n          src = pkgs.lib.cleanSourceWith {\n            src = ./.;\n            filter =\n              path: type:\n              (craneLib.filterCargoSources path type)\n              || (pkgs.lib.hasInfix \"/docs/\" path)\n              || (builtins.match \".*/docs/.*\" path != null);\n          };\n\n          commonArgs = {\n            inherit src version;\n            strictDeps = true;\n            COMMIT_HASH = self.rev or (pkgs.lib.removeSuffix \"-dirty\" self.dirtyRev);\n\n            # build inputs for cross-compilation\n            nativeBuildInputs = [\n              pkgs.llvmPackages.clang-unwrapped\n              pkgs.llvmPackages.lld\n              pkgs.llvmPackages.llvm\n            ];\n\n            # cross-compilation environment\n            inherit (msvcEnv)\n              CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER\n              CC_x86_64_pc_windows_msvc\n              CXX_x86_64_pc_windows_msvc\n              AR_x86_64_pc_windows_msvc\n              CFLAGS_x86_64_pc_windows_msvc\n              CXXFLAGS_x86_64_pc_windows_msvc\n              CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_RUSTFLAGS\n              CARGO_BUILD_TARGET\n              ;\n          };\n\n          cargoArtifacts = craneLib.buildDepsOnly commonArgs;\n\n          individualCrateArgs = commonArgs // {\n            inherit cargoArtifacts;\n            doCheck = false;\n            doDoc = false;\n          };\n\n          fullBuild = craneLib.buildPackage (\n            individualCrateArgs\n            // {\n              pname = \"komorebi-workspace\";\n            }\n          );\n\n          extractBinary =\n            binaryName:\n            pkgs.runCommand \"komorebi-${binaryName}\"\n              {\n                meta = fullBuild.meta // { };\n              }\n              ''\n                mkdir -p $out/bin\n                cp ${fullBuild}/bin/${binaryName}.exe $out/bin/\n              '';\n        in\n        {\n          inherit\n            craneLib\n            src\n            individualCrateArgs\n            fullBuild\n            msvcEnv\n            ;\n          komorebi = extractBinary \"komorebi\";\n          komorebic = extractBinary \"komorebic\";\n          komorebic-no-console = extractBinary \"komorebic-no-console\";\n          komorebi-bar = extractBinary \"komorebi-bar\";\n          komorebi-gui = extractBinary \"komorebi-gui\";\n          komorebi-shortcuts = extractBinary \"komorebi-shortcuts\";\n        };\n\n      mkPkgs =\n        system:\n        import nixpkgs {\n          inherit system;\n          overlays = [ (import rust-overlay) ];\n        };\n    in\n    flake-parts.lib.mkFlake { inherit inputs; } {\n      systems = [\n        \"aarch64-darwin\"\n        \"x86_64-linux\"\n        \"aarch64-linux\"\n      ];\n\n      imports = [\n        inputs.treefmt-nix.flakeModule\n        inputs.git-hooks-nix.flakeModule\n      ];\n\n      perSystem =\n        { config, system, ... }:\n        let\n          pkgs = mkPkgs system;\n          windowsSdk = mkWindowsSdk pkgs;\n          build = mkKomorebiPackages { inherit pkgs windowsSdk; };\n\n          # toolchain with windows target and nightly rustfmt\n          rustToolchain = (pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml).override {\n            targets = [ \"x86_64-pc-windows-msvc\" ];\n          };\n          nightlyRustfmt = pkgs.rust-bin.nightly.latest.rustfmt;\n          rustToolchainWithNightlyRustfmt = pkgs.symlinkJoin {\n            name = \"rust-toolchain-with-nightly-rustfmt\";\n            paths = [\n              nightlyRustfmt\n              rustToolchain\n            ];\n          };\n          nightlyToolchain = pkgs.rust-bin.nightly.latest.default.override {\n            targets = [ \"x86_64-pc-windows-msvc\" ];\n          };\n          cargo-udeps = pkgs.writeShellScriptBin \"cargo-udeps\" ''\n            export PATH=\"${nightlyToolchain}/bin:$PATH\"\n            exec ${pkgs.cargo-udeps}/bin/cargo-udeps \"$@\"\n          '';\n        in\n        {\n          treefmt = {\n            projectRootFile = \"flake.nix\";\n            programs = {\n              deadnix.enable = true;\n              just.enable = true;\n              nixfmt.enable = true;\n              taplo.enable = true;\n              rustfmt = {\n                enable = true;\n                package = pkgs.rust-bin.nightly.latest.rustfmt;\n              };\n            };\n          };\n\n          checks = {\n            komorebi-workspace-clippy = build.craneLib.cargoClippy (\n              build.individualCrateArgs\n              // {\n                cargoClippyExtraArgs = \"--all-targets -- -D warnings\";\n              }\n            );\n\n            komorebi-workspace-fmt = build.craneLib.cargoFmt {\n              inherit (build) src;\n            };\n\n            komorebi-workspace-toml-fmt = build.craneLib.taploFmt {\n              src = pkgs.lib.sources.sourceFilesBySuffices build.src [ \".toml\" ];\n            };\n\n            komorebi-workspace-deny = build.craneLib.cargoDeny {\n              inherit (build) src;\n            };\n\n            komorebi-workspace-nextest = build.craneLib.cargoNextest build.individualCrateArgs;\n          };\n\n          packages = {\n            inherit (build)\n              komorebi\n              komorebic\n              komorebic-no-console\n              komorebi-bar\n              komorebi-gui\n              komorebi-shortcuts\n              ;\n            inherit windowsSdk;\n            komorebi-full = build.fullBuild;\n            default = build.fullBuild;\n          };\n\n          apps = {\n            komorebi = {\n              type = \"app\";\n              program = \"${build.komorebi}/bin/komorebi.exe\";\n            };\n            komorebic = {\n              type = \"app\";\n              program = \"${build.komorebic}/bin/komorebic.exe\";\n            };\n            komorebic-no-console = {\n              type = \"app\";\n              program = \"${build.komorebic-no-console}/bin/komorebic-no-console.exe\";\n            };\n            komorebi-bar = {\n              type = \"app\";\n              program = \"${build.komorebi-bar}/bin/komorebi-bar.exe\";\n            };\n            komorebi-gui = {\n              type = \"app\";\n              program = \"${build.komorebi-gui}/bin/komorebi-gui.exe\";\n            };\n            komorebi-shortcuts = {\n              type = \"app\";\n              program = \"${build.komorebi-shortcuts}/bin/komorebi-shortcuts.exe\";\n            };\n            default = {\n              type = \"app\";\n              program = \"${build.fullBuild}/bin/komorebi.exe\";\n            };\n          };\n\n          devShells.default = pkgs.mkShell {\n            name = \"komorebi\";\n\n            RUST_BACKTRACE = \"full\";\n\n            # cross-compilation environment\n            inherit (build.msvcEnv)\n              CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER\n              CC_x86_64_pc_windows_msvc\n              CXX_x86_64_pc_windows_msvc\n              AR_x86_64_pc_windows_msvc\n              CFLAGS_x86_64_pc_windows_msvc\n              CXXFLAGS_x86_64_pc_windows_msvc\n              CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_RUSTFLAGS\n              CARGO_BUILD_TARGET\n              ;\n\n            packages = [\n              rustToolchainWithNightlyRustfmt\n              cargo-udeps\n\n              # cross-compilation tooling\n              pkgs.llvmPackages.clang-unwrapped # provides clang-cl\n              pkgs.llvmPackages.lld # provides lld-link\n              pkgs.llvmPackages.llvm # provides llvm-lib\n\n              pkgs.cargo-deny\n              pkgs.cargo-nextest\n              pkgs.cargo-outdated\n              pkgs.jq\n              pkgs.just\n              pkgs.prettier\n            ];\n          };\n\n          pre-commit = {\n            check.enable = true;\n            settings.hooks.treefmt = {\n              enable = true;\n              package = config.treefmt.build.wrapper;\n              pass_filenames = false;\n            };\n          };\n        };\n    };\n}\n"
  },
  {
    "path": "justfile",
    "content": "set windows-shell := [\"pwsh.exe\", \"-NoLogo\", \"-Command\"]\n\nexport RUST_BACKTRACE := \"full\"\n\nclean:\n    cargo clean\n\nfmt:\n    cargo +nightly fmt\n    cargo +stable clippy\n    prettier --write .github/ISSUE_TEMPLATE/bug_report.yml\n    prettier --write .github/ISSUE_TEMPLATE/config.yml\n    prettier --write .github/ISSUE_TEMPLATE/feature_request.yml\n    prettier --write .github/dependabot.yml\n    prettier --write .github/FUNDING.yml\n    prettier --write .github/workflows/windows.yaml\n\nfix:\n    cargo clippy --fix --allow-dirty\n\ninstall-targets *targets:\n    \"{{ targets }}\" -split ' ' | ForEach-Object { just install-target $_ }\n\ninstall-target target:\n    cargo +stable install --path {{ target }} --locked --no-default-features\n\ninstall-targets-with-jsonschema *targets:\n    \"{{ targets }}\" -split ' ' | ForEach-Object { just install-target-with-jsonschema $_ }\n\ninstall-target-with-jsonschema target:\n    cargo +stable install --path {{ target }} --locked\n\ninstall:\n    just install-targets komorebic komorebic-no-console komorebi komorebi-bar komorebi-gui komorebi-shortcuts\n\ninstall-with-jsonschema:\n    just install-targets-with-jsonschema komorebic komorebic-no-console komorebi komorebi-bar komorebi-gui komorebi-shortcuts\n\nbuild-targets *targets:\n    \"{{ targets }}\" -split ' ' | ForEach-Object { just build-target $_ }\n\nbuild-target target:\n    cargo +stable build --package {{ target }} --locked --release --no-default-features\n\nbuild:\n    just build-targets komorebic komorebic-no-console komorebi komorebi-bar komorebi-gui komorebi-shortcuts\n\ncopy-target target:\n    cp .\\target\\release\\{{ target }}.exe $Env:USERPROFILE\\.cargo\\bin\n\ncopy-targets *targets:\n    \"{{ targets }}\" -split ' ' | ForEach-Object { just copy-target $_ }\n\nwpm target:\n    just build-target {{ target }} && wpmctl stop {{ target }}; just copy-target {{ target }} && wpmctl start {{ target }}\n\ncopy:\n    just copy-targets komorebic komorebic-no-console komorebi komorebi-bar komorebi-gui komorebi-shortcuts\n\nrun target:\n    cargo +stable run --bin {{ target }} --locked --no-default-features\n\nwarn target $RUST_LOG=\"warn\":\n    just run {{ target }}\n\ninfo target $RUST_LOG=\"info\":\n    just run {{ target }}\n\ndebug target $RUST_LOG=\"debug\":\n    just run {{ target }}\n\ntrace target $RUST_LOG=\"trace\":\n    just run {{ target }}\n\ndeadlock $RUST_LOG=\"trace\":\n    cargo +stable run --bin komorebi --locked --no-default-features --features deadlock_detection\n\ndocgen starlight:\n    rm {{ starlight }}/src/data/cli/windows/*.md\n    cargo run --package komorebic -- docgen --output {{ starlight }}/src/data/cli/windows\n    schemars-docgen ./schema.json --output {{ starlight }}/src/content/docs/reference/komorebi-windows.mdx --title \"komorebi.json (Windows)\" --description \"komorebi for Windows configuration schema reference\"\n    schemars-docgen ./schema.bar.json --output {{ starlight }}/src/content/docs/reference/bar-windows.mdx --title \"komorebi.bar.json (Windows)\" --description \"komorebi-bar for Windows configuration schema reference\"\n\njsonschema:\n    cargo run --package komorebic -- static-config-schema > schema.json\n    cargo run --package komorebic -- application-specific-configuration-schema > schema.asc.json\n    cargo run --package komorebi-bar -- --schema > schema.bar.json\n\nversion := `cargo metadata --format-version 1 --no-deps | jq -r '.packages[] | select(.name == \"komorebi\") | .version'`\n\nschemapub:\n    rm -Force komorebi-schema\n    mkdir -Force komorebi-schema\n    cp schema.json komorebi-schema/komorebi.{{ version }}.schema.json\n    cp schema.bar.json komorebi-schema/komorebi.bar.{{ version }}.schema.json\n    npx wrangler pages deploy --project-name komorebi --branch main .\\komorebi-schema\n\ndepcheck:\n    cargo outdated --depth 2\n    cargo +nightly udeps --quiet\n\ndeps:\n    cargo update\n    just depgen\n\ndepgen:\n    cargo deny check\n    cargo deny list --format json | jq 'del(.unlicensed)' > dependencies.json\n\nprocdump:\n    cargo build --bin komorebi\n    .\\procdump.exe -ma -e -x . .\\target\\debug\\komorebi.exe\n"
  },
  {
    "path": "komorebi/Cargo.toml",
    "content": "[package]\nname = \"komorebi\"\nversion = \"0.1.41\"\ndescription = \"A tiling window manager for Windows\"\nrepository = \"https://github.com/LGUG2Z/komorebi\"\nedition = \"2024\"\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\nkomorebi-layouts = { path = \"../komorebi-layouts\", features = [\"win32\"] }\nkomorebi-themes = { path = \"../komorebi-themes\" }\n\nbase64 = \"0.22\"\nbitflags = { version = \"2\", features = [\"serde\"] }\nclap = { workspace = true }\nchrono = { workspace = true }\ncolor-eyre = { workspace = true }\ncrossbeam-channel = { workspace = true }\ncrossbeam-utils = { workspace = true }\nctrlc = { version = \"3\", features = [\"termination\"] }\ndirs = { workspace = true }\ned25519-dalek = \"2\"\nhotwatch = { workspace = true }\nlazy_static = { workspace = true }\nmiow = \"0.6\"\nnanoid = \"0.4\"\nnet2 = \"0.2\"\nos_info = \"3.10\"\nparking_lot = { workspace = true }\npaste = { workspace = true }\npowershell_script = \"1.0\"\nregex = \"1\"\nreqwest = { version = \"0.12\", features = [\"blocking\"] }\nschemars = { workspace = true, optional = true }\nserde = { workspace = true }\nserde_json = { workspace = true, features = [\"preserve_order\"] }\nserde_yaml = { workspace = true }\nshadow-rs = { workspace = true }\nstrum = { workspace = true }\nsysinfo = { workspace = true }\ntracing = { workspace = true }\ntracing-appender = { workspace = true }\ntracing-subscriber = { workspace = true }\nuds_windows = { workspace = true }\nwhich = { workspace = true }\nwin32-display-data = { workspace = true }\nwindows = { workspace = true }\nwindows-core = { workspace = true }\nwindows-numerics = { workspace = true }\nwindows-implement = { workspace = true }\nwindows-interface = { workspace = true }\nwinput = \"0.2\"\nwinreg = \"0.55\"\nserde_with = { version = \"3.12\", features = [\"schemars_1\"] }\n\n[build-dependencies]\nshadow-rs = { workspace = true }\n\n[dev-dependencies]\nreqwest = { version = \"0.12\", features = [\"blocking\"] }\nuuid = { version = \"1\", features = [\"v4\"] }\n\n[features]\ndefault = [\"schemars\"]\ndeadlock_detection = [\"parking_lot/deadlock_detection\"]\nschemars = [\"dep:schemars\", \"komorebi-layouts/schemars\"]\n"
  },
  {
    "path": "komorebi/build.rs",
    "content": "use shadow_rs::ShadowBuilder;\n\nfn main() {\n    ShadowBuilder::builder().build().unwrap();\n}\n"
  },
  {
    "path": "komorebi/src/animation/animation_manager.rs",
    "content": "use std::collections::HashMap;\nuse std::collections::hash_map::Entry;\n\nuse super::prefix::AnimationPrefix;\n\n#[derive(Debug, Clone, Copy)]\nstruct AnimationState {\n    pub in_progress: bool,\n    pub cancel_idx_counter: usize,\n    pub pending_cancel_count: usize,\n}\n\n#[derive(Debug)]\npub struct AnimationManager {\n    animations: HashMap<String, AnimationState>,\n}\n\nimpl Default for AnimationManager {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl AnimationManager {\n    pub fn new() -> Self {\n        Self {\n            animations: HashMap::new(),\n        }\n    }\n\n    pub fn is_cancelled(&self, animation_key: &str) -> bool {\n        if let Some(animation_state) = self.animations.get(animation_key) {\n            animation_state.pending_cancel_count > 0\n        } else {\n            false\n        }\n    }\n\n    pub fn in_progress(&self, animation_key: &str) -> bool {\n        if let Some(animation_state) = self.animations.get(animation_key) {\n            animation_state.in_progress\n        } else {\n            false\n        }\n    }\n\n    pub fn init_cancel(&mut self, animation_key: &str) -> usize {\n        if let Some(animation_state) = self.animations.get_mut(animation_key) {\n            animation_state.pending_cancel_count += 1;\n            animation_state.cancel_idx_counter += 1;\n\n            // return cancel idx\n            animation_state.cancel_idx_counter\n        } else {\n            0\n        }\n    }\n\n    pub fn latest_cancel_idx(&mut self, animation_key: &str) -> usize {\n        if let Some(animation_state) = self.animations.get_mut(animation_key) {\n            animation_state.cancel_idx_counter\n        } else {\n            0\n        }\n    }\n\n    pub fn end_cancel(&mut self, animation_key: &str) {\n        if let Some(animation_state) = self.animations.get_mut(animation_key) {\n            animation_state.pending_cancel_count -= 1;\n        }\n    }\n\n    pub fn cancel(&mut self, animation_key: &str) {\n        if let Some(animation_state) = self.animations.get_mut(animation_key) {\n            animation_state.in_progress = false;\n        }\n    }\n\n    pub fn start(&mut self, animation_key: &str) {\n        if let Entry::Vacant(e) = self.animations.entry(animation_key.to_string()) {\n            e.insert(AnimationState {\n                in_progress: true,\n                cancel_idx_counter: 0,\n                pending_cancel_count: 0,\n            });\n\n            return;\n        }\n\n        if let Some(animation_state) = self.animations.get_mut(animation_key) {\n            animation_state.in_progress = true;\n        }\n    }\n\n    pub fn end(&mut self, animation_key: &str) {\n        if let Some(animation_state) = self.animations.get_mut(animation_key) {\n            animation_state.in_progress = false;\n\n            if animation_state.pending_cancel_count == 0 {\n                self.animations.remove(animation_key);\n            }\n        }\n    }\n\n    pub fn count_in_progress(&self, animation_key_prefix: AnimationPrefix) -> usize {\n        self.animations\n            .keys()\n            .filter(|key| key.starts_with(animation_key_prefix.to_string().as_str()))\n            .count()\n    }\n\n    pub fn count(&self) -> usize {\n        self.animations.len()\n    }\n}\n"
  },
  {
    "path": "komorebi/src/animation/engine.rs",
    "content": "use color_eyre::eyre;\n\nuse serde::Deserialize;\nuse serde::Serialize;\nuse std::sync::atomic::Ordering;\nuse std::time::Duration;\nuse std::time::Instant;\n\nuse super::ANIMATION_DURATION_GLOBAL;\nuse super::ANIMATION_FPS;\nuse super::ANIMATION_MANAGER;\nuse super::RenderDispatcher;\n\n#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\npub struct AnimationEngine;\n\nimpl AnimationEngine {\n    pub fn wait_for_all_animations() {\n        let max_duration = Duration::from_secs(20);\n        let spent_duration = Instant::now();\n\n        while ANIMATION_MANAGER.lock().count() > 0 {\n            if spent_duration.elapsed() >= max_duration {\n                break;\n            }\n\n            std::thread::sleep(Duration::from_millis(\n                ANIMATION_DURATION_GLOBAL.load(Ordering::SeqCst),\n            ));\n        }\n    }\n\n    /// Returns true if the animation needs to continue\n    pub fn cancel(animation_key: &str) -> bool {\n        // should be more than 0\n        let cancel_idx = ANIMATION_MANAGER.lock().init_cancel(animation_key);\n        let max_duration = Duration::from_secs(5);\n        let spent_duration = Instant::now();\n\n        while ANIMATION_MANAGER.lock().in_progress(animation_key) {\n            if spent_duration.elapsed() >= max_duration {\n                ANIMATION_MANAGER.lock().end(animation_key);\n            }\n\n            std::thread::sleep(Duration::from_millis(250 / 2));\n        }\n\n        let latest_cancel_idx = ANIMATION_MANAGER.lock().latest_cancel_idx(animation_key);\n\n        ANIMATION_MANAGER.lock().end_cancel(animation_key);\n\n        latest_cancel_idx == cancel_idx\n    }\n\n    #[allow(clippy::cast_precision_loss)]\n    pub fn animate(\n        render_dispatcher: impl RenderDispatcher + Send + 'static,\n        duration: Duration,\n    ) -> eyre::Result<()> {\n        std::thread::spawn(move || {\n            let animation_key = render_dispatcher.get_animation_key();\n            if ANIMATION_MANAGER.lock().in_progress(animation_key.as_str()) {\n                let should_animate = Self::cancel(animation_key.as_str());\n\n                if !should_animate {\n                    return Ok(());\n                }\n            }\n\n            render_dispatcher.pre_render()?;\n\n            ANIMATION_MANAGER.lock().start(animation_key.as_str());\n\n            let target_frame_time =\n                Duration::from_millis(1000 / ANIMATION_FPS.load(Ordering::Relaxed));\n            let mut progress = 0.0;\n            let animation_start = Instant::now();\n\n            // start animation\n            while progress < 1.0 {\n                // check if animation is cancelled\n                if ANIMATION_MANAGER\n                    .lock()\n                    .is_cancelled(animation_key.as_str())\n                {\n                    // cancel animation\n                    ANIMATION_MANAGER.lock().cancel(animation_key.as_str());\n                    return Ok(());\n                }\n\n                let frame_start = Instant::now();\n                // calculate progress\n                progress =\n                    animation_start.elapsed().as_millis() as f64 / duration.as_millis() as f64;\n                render_dispatcher.render(progress).ok();\n\n                // sleep until next frame\n                let frame_time_elapsed = frame_start.elapsed();\n\n                if frame_time_elapsed < target_frame_time {\n                    std::thread::sleep(target_frame_time - frame_time_elapsed);\n                }\n            }\n\n            ANIMATION_MANAGER.lock().end(animation_key.as_str());\n\n            // limit progress to 1.0 if animation took longer\n            if progress != 1.0 {\n                progress = 1.0;\n\n                // process animation for 1.0 to set target position\n                render_dispatcher.render(progress).ok();\n            }\n\n            render_dispatcher.post_render()\n        });\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "komorebi/src/animation/lerp.rs",
    "content": "use crate::AnimationStyle;\nuse crate::core::Rect;\n\nuse super::style::apply_ease_func;\n\npub trait Lerp<T = Self> {\n    fn lerp(self, end: T, time: f64, style: AnimationStyle) -> T;\n}\n\nimpl Lerp for i32 {\n    #[allow(clippy::cast_possible_truncation)]\n    fn lerp(self, end: i32, time: f64, style: AnimationStyle) -> i32 {\n        let time = apply_ease_func(time, style);\n\n        f64::from(end - self).mul_add(time, f64::from(self)).round() as i32\n    }\n}\n\nimpl Lerp for f64 {\n    fn lerp(self, end: f64, time: f64, style: AnimationStyle) -> f64 {\n        let time = apply_ease_func(time, style);\n\n        (end - self).mul_add(time, self)\n    }\n}\n\nimpl Lerp for u8 {\n    fn lerp(self, end: u8, time: f64, style: AnimationStyle) -> u8 {\n        (self as f64).lerp(end as f64, time, style) as u8\n    }\n}\n\nimpl Lerp for Rect {\n    fn lerp(self, end: Rect, time: f64, style: AnimationStyle) -> Rect {\n        Rect {\n            left: self.left.lerp(end.left, time, style),\n            top: self.top.lerp(end.top, time, style),\n            right: self.right.lerp(end.right, time, style),\n            bottom: self.bottom.lerp(end.bottom, time, style),\n        }\n    }\n}\n"
  },
  {
    "path": "komorebi/src/animation/mod.rs",
    "content": "use crate::animation::animation_manager::AnimationManager;\nuse crate::core::animation::AnimationStyle;\n\nuse lazy_static::lazy_static;\nuse prefix::AnimationPrefix;\nuse std::collections::HashMap;\nuse std::sync::Arc;\nuse std::sync::atomic::AtomicBool;\nuse std::sync::atomic::AtomicU64;\n\nuse parking_lot::Mutex;\n\npub use engine::AnimationEngine;\npub mod animation_manager;\npub mod engine;\npub mod lerp;\npub mod prefix;\npub mod render_dispatcher;\npub use render_dispatcher::RenderDispatcher;\npub mod style;\n\nuse serde::Deserialize;\nuse serde::Serialize;\n\n#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n#[serde(untagged)]\n/// Animation configuration\n///\n/// This can be either global:\n/// ```json\n/// {\n///     \"enabled\": true,\n///     \"style\": \"EaseInSine\",\n///     \"fps\": 60,\n///     \"duration\": 250\n/// }\n/// ```\n///\n/// Or scoped by an animation kind prefix:\n/// ```json\n/// {\n///     \"movement\": {\n///         \"enabled\": true,\n///         \"style\": \"EaseInSine\",\n///         \"fps\": 60,\n///         \"duration\": 250\n///     }\n/// }\n/// ```\npub enum PerAnimationPrefixConfig<T> {\n    /// Animation configuration prefixed for a specific animation kind\n    Prefix(HashMap<AnimationPrefix, T>),\n    /// Animation configuration for all animation kinds\n    Global(T),\n}\n\npub const DEFAULT_ANIMATION_ENABLED: bool = false;\npub const DEFAULT_ANIMATION_STYLE: AnimationStyle = AnimationStyle::Linear;\npub const DEFAULT_ANIMATION_DURATION: u64 = 250;\npub const DEFAULT_ANIMATION_FPS: u64 = 60;\n\nlazy_static! {\n    pub static ref ANIMATION_MANAGER: Arc<Mutex<AnimationManager>> =\n        Arc::new(Mutex::new(AnimationManager::new()));\n    pub static ref ANIMATION_STYLE_GLOBAL: Arc<Mutex<AnimationStyle>> =\n        Arc::new(Mutex::new(DEFAULT_ANIMATION_STYLE));\n    pub static ref ANIMATION_ENABLED_GLOBAL: Arc<AtomicBool> =\n        Arc::new(AtomicBool::new(DEFAULT_ANIMATION_ENABLED));\n    pub static ref ANIMATION_DURATION_GLOBAL: Arc<AtomicU64> =\n        Arc::new(AtomicU64::new(DEFAULT_ANIMATION_DURATION));\n    pub static ref ANIMATION_STYLE_PER_ANIMATION: Arc<Mutex<HashMap<AnimationPrefix, AnimationStyle>>> =\n        Arc::new(Mutex::new(HashMap::new()));\n    pub static ref ANIMATION_ENABLED_PER_ANIMATION: Arc<Mutex<HashMap<AnimationPrefix, bool>>> =\n        Arc::new(Mutex::new(HashMap::new()));\n    pub static ref ANIMATION_DURATION_PER_ANIMATION: Arc<Mutex<HashMap<AnimationPrefix, u64>>> =\n        Arc::new(Mutex::new(HashMap::new()));\n}\n\npub static ANIMATION_FPS: AtomicU64 = AtomicU64::new(DEFAULT_ANIMATION_FPS);\n"
  },
  {
    "path": "komorebi/src/animation/prefix.rs",
    "content": "use clap::ValueEnum;\n\nuse serde::Deserialize;\nuse serde::Serialize;\nuse strum::Display;\nuse strum::EnumString;\n\n#[derive(\n    Copy, Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize, Display, EnumString, ValueEnum,\n)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n#[strum(serialize_all = \"snake_case\")]\n#[serde(rename_all = \"snake_case\")]\npub enum AnimationPrefix {\n    Movement,\n    Transparency,\n}\n\npub fn new_animation_key(prefix: AnimationPrefix, key: String) -> String {\n    format!(\"{prefix}:{key}\")\n}\n"
  },
  {
    "path": "komorebi/src/animation/render_dispatcher.rs",
    "content": "use color_eyre::eyre;\n\npub trait RenderDispatcher {\n    fn get_animation_key(&self) -> String;\n    fn pre_render(&self) -> eyre::Result<()>;\n    fn render(&self, delta: f64) -> eyre::Result<()>;\n    fn post_render(&self) -> eyre::Result<()>;\n}\n"
  },
  {
    "path": "komorebi/src/animation/style.rs",
    "content": "use crate::core::AnimationStyle;\n\nuse std::f64::consts::PI;\n\npub trait Ease {\n    fn evaluate(t: f64) -> f64;\n}\n\npub struct Linear;\n\nimpl Ease for Linear {\n    fn evaluate(t: f64) -> f64 {\n        t\n    }\n}\n\npub struct EaseInSine;\n\nimpl Ease for EaseInSine {\n    fn evaluate(t: f64) -> f64 {\n        1.0 - f64::cos((t * PI) / 2.0)\n    }\n}\n\npub struct EaseOutSine;\n\nimpl Ease for EaseOutSine {\n    fn evaluate(t: f64) -> f64 {\n        f64::sin((t * PI) / 2.0)\n    }\n}\n\npub struct EaseInOutSine;\n\nimpl Ease for EaseInOutSine {\n    fn evaluate(t: f64) -> f64 {\n        -(f64::cos(PI * t) - 1.0) / 2.0\n    }\n}\n\npub struct EaseInQuad;\n\nimpl Ease for EaseInQuad {\n    fn evaluate(t: f64) -> f64 {\n        t * t\n    }\n}\n\npub struct EaseOutQuad;\n\nimpl Ease for EaseOutQuad {\n    fn evaluate(t: f64) -> f64 {\n        (1.0 - t).mul_add(-1.0 - t, 1.0)\n    }\n}\n\npub struct EaseInOutQuad;\n\nimpl Ease for EaseInOutQuad {\n    fn evaluate(t: f64) -> f64 {\n        if t < 0.5 {\n            2.0 * t * t\n        } else {\n            1.0 - (-2.0f64).mul_add(t, 2.0).powi(2) / 2.0\n        }\n    }\n}\n\npub struct EaseInCubic;\n\nimpl Ease for EaseInCubic {\n    fn evaluate(t: f64) -> f64 {\n        t * t * t\n    }\n}\n\npub struct EaseOutCubic;\n\nimpl Ease for EaseOutCubic {\n    fn evaluate(t: f64) -> f64 {\n        1.0 - (1.0 - t).powi(3)\n    }\n}\n\npub struct EaseInOutCubic;\n\nimpl Ease for EaseInOutCubic {\n    fn evaluate(t: f64) -> f64 {\n        if t < 0.5 {\n            4.0 * t * t * t\n        } else {\n            1.0 - (-2.0f64).mul_add(t, 2.0).powi(3) / 2.0\n        }\n    }\n}\n\npub struct EaseInQuart;\n\nimpl Ease for EaseInQuart {\n    fn evaluate(t: f64) -> f64 {\n        t * t * t * t\n    }\n}\n\npub struct EaseOutQuart;\n\nimpl Ease for EaseOutQuart {\n    fn evaluate(t: f64) -> f64 {\n        1.0 - (1.0 - t).powi(4)\n    }\n}\n\npub struct EaseInOutQuart;\n\nimpl Ease for EaseInOutQuart {\n    fn evaluate(t: f64) -> f64 {\n        if t < 0.5 {\n            8.0 * t * t * t * t\n        } else {\n            1.0 - (-2.0f64).mul_add(t, 2.0).powi(4) / 2.0\n        }\n    }\n}\n\npub struct EaseInQuint;\n\nimpl Ease for EaseInQuint {\n    fn evaluate(t: f64) -> f64 {\n        t * t * t * t * t\n    }\n}\n\npub struct EaseOutQuint;\n\nimpl Ease for EaseOutQuint {\n    fn evaluate(t: f64) -> f64 {\n        1.0 - (1.0 - t).powi(5)\n    }\n}\n\npub struct EaseInOutQuint;\n\nimpl Ease for EaseInOutQuint {\n    fn evaluate(t: f64) -> f64 {\n        if t < 0.5 {\n            16.0 * t * t * t * t\n        } else {\n            1.0 - (-2.0f64).mul_add(t, 2.0).powi(5) / 2.0\n        }\n    }\n}\n\npub struct EaseInExpo;\n\nimpl Ease for EaseInExpo {\n    fn evaluate(t: f64) -> f64 {\n        if t == 0.0 {\n            return t;\n        }\n\n        10.0f64.mul_add(t, -10.0).exp2()\n    }\n}\n\npub struct EaseOutExpo;\n\nimpl Ease for EaseOutExpo {\n    fn evaluate(t: f64) -> f64 {\n        if (t - 1.0).abs() < f64::EPSILON {\n            return t;\n        }\n\n        1.0 - (-10.0 * t).exp2()\n    }\n}\n\npub struct EaseInOutExpo;\n\nimpl Ease for EaseInOutExpo {\n    fn evaluate(t: f64) -> f64 {\n        if t == 0.0 || (t - 1.0).abs() < f64::EPSILON {\n            return t;\n        }\n\n        if t < 0.5 {\n            20.0f64.mul_add(t, -10.0).exp2() / 2.0\n        } else {\n            (2.0 - (-20.0f64).mul_add(t, 10.0).exp2()) / 2.0\n        }\n    }\n}\n\npub struct EaseInCirc;\n\nimpl Ease for EaseInCirc {\n    fn evaluate(t: f64) -> f64 {\n        1.0 - f64::sqrt(t.mul_add(-t, 1.0))\n    }\n}\n\npub struct EaseOutCirc;\n\nimpl Ease for EaseOutCirc {\n    fn evaluate(t: f64) -> f64 {\n        f64::sqrt((t - 1.0).mul_add(-(t - 1.0), 1.0))\n    }\n}\n\npub struct EaseInOutCirc;\n\nimpl Ease for EaseInOutCirc {\n    fn evaluate(t: f64) -> f64 {\n        if t < 0.5 {\n            (1.0 - f64::sqrt((2.0 * t).mul_add(-(2.0 * t), 1.0))) / 2.0\n        } else {\n            (f64::sqrt(\n                (-2.0f64)\n                    .mul_add(t, 2.0)\n                    .mul_add(-(-2.0f64).mul_add(t, 2.0), 1.0),\n            ) + 1.0)\n                / 2.0\n        }\n    }\n}\n\npub struct EaseInBack;\n\nimpl Ease for EaseInBack {\n    fn evaluate(t: f64) -> f64 {\n        let c1 = 1.70158;\n        let c3 = c1 + 1.0;\n\n        (c3 * t * t).mul_add(t, -c1 * t * t)\n    }\n}\n\npub struct EaseOutBack;\n\nimpl Ease for EaseOutBack {\n    fn evaluate(t: f64) -> f64 {\n        let c1: f64 = 1.70158;\n        let c3: f64 = c1 + 1.0;\n\n        c1.mul_add((t - 1.0).powi(2), c3.mul_add((t - 1.0).powi(3), 1.0))\n    }\n}\n\npub struct EaseInOutBack;\n\nimpl Ease for EaseInOutBack {\n    fn evaluate(t: f64) -> f64 {\n        let c1: f64 = 1.70158;\n        let c2: f64 = c1 * 1.525;\n\n        if t < 0.5 {\n            ((2.0 * t).powi(2) * ((c2 + 1.0) * 2.0).mul_add(t, -c2)) / 2.0\n        } else {\n            ((2.0f64.mul_add(t, -2.0))\n                .powi(2)\n                .mul_add((c2 + 1.0).mul_add(t.mul_add(2.0, -2.0), c2), 2.0))\n                / 2.0\n        }\n    }\n}\n\npub struct EaseInElastic;\n\nimpl Ease for EaseInElastic {\n    fn evaluate(t: f64) -> f64 {\n        if (t - 1.0).abs() < f64::EPSILON || t == 0.0 {\n            return t;\n        }\n\n        let c4 = (2.0 * PI) / 3.0;\n\n        -(10.0f64.mul_add(t, -10.0).exp2()) * f64::sin(t.mul_add(10.0, -10.75) * c4)\n    }\n}\n\npub struct EaseOutElastic;\n\nimpl Ease for EaseOutElastic {\n    fn evaluate(t: f64) -> f64 {\n        if (t - 1.0).abs() < f64::EPSILON || t == 0.0 {\n            return t;\n        }\n\n        let c4 = (2.0 * PI) / 3.0;\n\n        (-10.0 * t)\n            .exp2()\n            .mul_add(f64::sin(t.mul_add(10.0, -0.75) * c4), 1.0)\n    }\n}\n\npub struct EaseInOutElastic;\n\nimpl Ease for EaseInOutElastic {\n    fn evaluate(t: f64) -> f64 {\n        if (t - 1.0).abs() < f64::EPSILON || t == 0.0 {\n            return t;\n        }\n\n        let c5 = (2.0 * PI) / 4.5;\n\n        if t < 0.5 {\n            -(20.0f64.mul_add(t, -10.0).exp2() * f64::sin(20.0f64.mul_add(t, -11.125) * c5)) / 2.0\n        } else {\n            ((-20.0f64).mul_add(t, 10.0).exp2() * f64::sin(20.0f64.mul_add(t, -11.125) * c5)) / 2.0\n                + 1.0\n        }\n    }\n}\n\npub struct EaseInBounce;\n\nimpl Ease for EaseInBounce {\n    fn evaluate(t: f64) -> f64 {\n        1.0 - EaseOutBounce::evaluate(1.0 - t)\n    }\n}\n\npub struct EaseOutBounce;\n\nimpl Ease for EaseOutBounce {\n    fn evaluate(t: f64) -> f64 {\n        let mut time = t;\n        let n1 = 7.5625;\n        let d1 = 2.75;\n\n        if t < 1.0 / d1 {\n            n1 * time * time\n        } else if time < 2.0 / d1 {\n            time -= 1.5 / d1;\n            (n1 * time).mul_add(time, 0.75)\n        } else if time < 2.5 / d1 {\n            time -= 2.25 / d1;\n            (n1 * time).mul_add(time, 0.9375)\n        } else {\n            time -= 2.625 / d1;\n            (n1 * time).mul_add(time, 0.984_375)\n        }\n    }\n}\n\npub struct EaseInOutBounce;\n\nimpl Ease for EaseInOutBounce {\n    fn evaluate(t: f64) -> f64 {\n        if t < 0.5 {\n            (1.0 - EaseOutBounce::evaluate(2.0f64.mul_add(-t, 1.0))) / 2.0\n        } else {\n            (1.0 + EaseOutBounce::evaluate(2.0f64.mul_add(t, -1.0))) / 2.0\n        }\n    }\n}\n\npub struct CubicBezier {\n    pub x1: f64,\n    pub y1: f64,\n    pub x2: f64,\n    pub y2: f64,\n}\n\nimpl CubicBezier {\n    fn x(&self, s: f64) -> f64 {\n        3.0 * self.x1 * s * (1.0 - s).powi(2) + 3.0 * self.x2 * s.powi(2) * (1.0 - s) + s.powi(3)\n    }\n\n    fn y(&self, s: f64) -> f64 {\n        3.0 * self.y1 * s * (1.0 - s).powi(2) + 3.0 * self.y2 * s.powi(2) * (1.0 - s) + s.powi(3)\n    }\n\n    fn dx_ds(&self, s: f64) -> f64 {\n        3.0 * self.x1 * (1.0 - s) * (1.0 - 3.0 * s)\n            + 3.0 * self.x2 * (2.0 * s - 3.0 * s.powi(2))\n            + 3.0 * s.powi(2)\n    }\n\n    fn find_s(&self, t: f64) -> f64 {\n        if t <= 0.0 {\n            return 0.0;\n        }\n\n        if t >= 1.0 {\n            return 1.0;\n        }\n\n        let mut s = t;\n\n        for _ in 0..8 {\n            let x_val = self.x(s);\n            let dx_val = self.dx_ds(s);\n            if dx_val.abs() < 1e-6 {\n                break;\n            }\n            let delta = (x_val - t) / dx_val;\n            s = (s - delta).clamp(0.0, 1.0);\n            if delta.abs() < 1e-6 {\n                break;\n            }\n        }\n\n        s\n    }\n\n    fn evaluate(&self, t: f64) -> f64 {\n        let s = self.find_s(t.clamp(0.0, 1.0));\n        self.y(s)\n    }\n}\n\npub fn apply_ease_func(t: f64, style: AnimationStyle) -> f64 {\n    match style {\n        AnimationStyle::Linear => Linear::evaluate(t),\n        AnimationStyle::EaseInSine => EaseInSine::evaluate(t),\n        AnimationStyle::EaseOutSine => EaseOutSine::evaluate(t),\n        AnimationStyle::EaseInOutSine => EaseInOutSine::evaluate(t),\n        AnimationStyle::EaseInQuad => EaseInQuad::evaluate(t),\n        AnimationStyle::EaseOutQuad => EaseOutQuad::evaluate(t),\n        AnimationStyle::EaseInOutQuad => EaseInOutQuad::evaluate(t),\n        AnimationStyle::EaseInCubic => EaseInCubic::evaluate(t),\n        AnimationStyle::EaseOutCubic => EaseOutCubic::evaluate(t),\n        AnimationStyle::EaseInOutCubic => EaseInOutCubic::evaluate(t),\n        AnimationStyle::EaseInQuart => EaseInQuart::evaluate(t),\n        AnimationStyle::EaseOutQuart => EaseOutQuart::evaluate(t),\n        AnimationStyle::EaseInOutQuart => EaseInOutQuart::evaluate(t),\n        AnimationStyle::EaseInQuint => EaseInQuint::evaluate(t),\n        AnimationStyle::EaseOutQuint => EaseOutQuint::evaluate(t),\n        AnimationStyle::EaseInOutQuint => EaseInOutQuint::evaluate(t),\n        AnimationStyle::EaseInExpo => EaseInExpo::evaluate(t),\n        AnimationStyle::EaseOutExpo => EaseOutExpo::evaluate(t),\n        AnimationStyle::EaseInOutExpo => EaseInOutExpo::evaluate(t),\n        AnimationStyle::EaseInCirc => EaseInCirc::evaluate(t),\n        AnimationStyle::EaseOutCirc => EaseOutCirc::evaluate(t),\n        AnimationStyle::EaseInOutCirc => EaseInOutCirc::evaluate(t),\n        AnimationStyle::EaseInBack => EaseInBack::evaluate(t),\n        AnimationStyle::EaseOutBack => EaseOutBack::evaluate(t),\n        AnimationStyle::EaseInOutBack => EaseInOutBack::evaluate(t),\n        AnimationStyle::EaseInElastic => EaseInElastic::evaluate(t),\n        AnimationStyle::EaseOutElastic => EaseOutElastic::evaluate(t),\n        AnimationStyle::EaseInOutElastic => EaseInOutElastic::evaluate(t),\n        AnimationStyle::EaseInBounce => EaseInBounce::evaluate(t),\n        AnimationStyle::EaseOutBounce => EaseOutBounce::evaluate(t),\n        AnimationStyle::EaseInOutBounce => EaseInOutBounce::evaluate(t),\n        AnimationStyle::CubicBezier(x1, y1, x2, y2) => CubicBezier { x1, y1, x2, y2 }.evaluate(t),\n    }\n}\n"
  },
  {
    "path": "komorebi/src/border_manager/border.rs",
    "content": "use crate::WINDOWS_11;\nuse crate::WindowsApi;\nuse crate::border_manager::BORDER_OFFSET;\nuse crate::border_manager::BORDER_WIDTH;\nuse crate::border_manager::RenderTarget;\nuse crate::border_manager::STYLE;\nuse crate::border_manager::WindowKind;\nuse crate::border_manager::window_kind_colour;\nuse crate::core::BorderStyle;\nuse crate::core::Rect;\nuse crate::windows_api;\nuse std::collections::HashMap;\nuse std::ops::Deref;\nuse std::sync::Arc;\nuse std::sync::LazyLock;\nuse std::sync::atomic::AtomicBool;\nuse std::sync::atomic::Ordering;\nuse std::sync::mpsc;\nuse windows::Win32::Foundation::FALSE;\nuse windows::Win32::Foundation::HWND;\nuse windows::Win32::Foundation::LPARAM;\nuse windows::Win32::Foundation::LRESULT;\nuse windows::Win32::Foundation::TRUE;\nuse windows::Win32::Foundation::WPARAM;\nuse windows::Win32::Graphics::Direct2D::Common::D2D_RECT_F;\nuse windows::Win32::Graphics::Direct2D::Common::D2D_SIZE_U;\nuse windows::Win32::Graphics::Direct2D::Common::D2D1_ALPHA_MODE_PREMULTIPLIED;\nuse windows::Win32::Graphics::Direct2D::Common::D2D1_COLOR_F;\nuse windows::Win32::Graphics::Direct2D::Common::D2D1_PIXEL_FORMAT;\nuse windows::Win32::Graphics::Direct2D::D2D1_ANTIALIAS_MODE_PER_PRIMITIVE;\nuse windows::Win32::Graphics::Direct2D::D2D1_BRUSH_PROPERTIES;\nuse windows::Win32::Graphics::Direct2D::D2D1_FACTORY_TYPE_MULTI_THREADED;\nuse windows::Win32::Graphics::Direct2D::D2D1_HWND_RENDER_TARGET_PROPERTIES;\nuse windows::Win32::Graphics::Direct2D::D2D1_PRESENT_OPTIONS_IMMEDIATELY;\nuse windows::Win32::Graphics::Direct2D::D2D1_RENDER_TARGET_PROPERTIES;\nuse windows::Win32::Graphics::Direct2D::D2D1_RENDER_TARGET_TYPE_DEFAULT;\nuse windows::Win32::Graphics::Direct2D::D2D1_ROUNDED_RECT;\nuse windows::Win32::Graphics::Direct2D::D2D1CreateFactory;\nuse windows::Win32::Graphics::Direct2D::ID2D1Factory;\nuse windows::Win32::Graphics::Direct2D::ID2D1SolidColorBrush;\nuse windows::Win32::Graphics::Dwm::DWM_BB_BLURREGION;\nuse windows::Win32::Graphics::Dwm::DWM_BB_ENABLE;\nuse windows::Win32::Graphics::Dwm::DWM_BLURBEHIND;\nuse windows::Win32::Graphics::Dwm::DwmEnableBlurBehindWindow;\nuse windows::Win32::Graphics::Dxgi::Common::DXGI_FORMAT_UNKNOWN;\nuse windows::Win32::Graphics::Gdi::CreateRectRgn;\nuse windows::Win32::Graphics::Gdi::InvalidateRect;\nuse windows::Win32::Graphics::Gdi::ValidateRect;\nuse windows::Win32::UI::WindowsAndMessaging::CREATESTRUCTW;\nuse windows::Win32::UI::WindowsAndMessaging::DefWindowProcW;\nuse windows::Win32::UI::WindowsAndMessaging::DispatchMessageW;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DESTROY;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_LOCATIONCHANGE;\nuse windows::Win32::UI::WindowsAndMessaging::GWLP_USERDATA;\nuse windows::Win32::UI::WindowsAndMessaging::GetMessageW;\nuse windows::Win32::UI::WindowsAndMessaging::GetSystemMetrics;\nuse windows::Win32::UI::WindowsAndMessaging::GetWindowLongPtrW;\nuse windows::Win32::UI::WindowsAndMessaging::IDC_ARROW;\nuse windows::Win32::UI::WindowsAndMessaging::LoadCursorW;\nuse windows::Win32::UI::WindowsAndMessaging::MSG;\nuse windows::Win32::UI::WindowsAndMessaging::PostQuitMessage;\nuse windows::Win32::UI::WindowsAndMessaging::SM_CXVIRTUALSCREEN;\nuse windows::Win32::UI::WindowsAndMessaging::SetCursor;\nuse windows::Win32::UI::WindowsAndMessaging::SetWindowLongPtrW;\nuse windows::Win32::UI::WindowsAndMessaging::TranslateMessage;\nuse windows::Win32::UI::WindowsAndMessaging::WM_CREATE;\nuse windows::Win32::UI::WindowsAndMessaging::WM_DESTROY;\nuse windows::Win32::UI::WindowsAndMessaging::WM_PAINT;\nuse windows::Win32::UI::WindowsAndMessaging::WM_SETCURSOR;\nuse windows::Win32::UI::WindowsAndMessaging::WNDCLASSW;\nuse windows_core::BOOL;\nuse windows_core::PCWSTR;\nuse windows_numerics::Matrix3x2;\n\npub struct RenderFactory(ID2D1Factory);\nunsafe impl Sync for RenderFactory {}\nunsafe impl Send for RenderFactory {}\n\nimpl Deref for RenderFactory {\n    type Target = ID2D1Factory;\n\n    fn deref(&self) -> &Self::Target {\n        &self.0\n    }\n}\n\n#[allow(clippy::expect_used)]\nstatic RENDER_FACTORY: LazyLock<RenderFactory> = unsafe {\n    LazyLock::new(|| {\n        RenderFactory(\n            D2D1CreateFactory::<ID2D1Factory>(D2D1_FACTORY_TYPE_MULTI_THREADED, None)\n                .expect(\"creating RENDER_FACTORY failed\"),\n        )\n    })\n};\n\nstatic BRUSH_PROPERTIES: LazyLock<D2D1_BRUSH_PROPERTIES> =\n    LazyLock::new(|| D2D1_BRUSH_PROPERTIES {\n        opacity: 1.0,\n        transform: Matrix3x2::identity(),\n    });\n\npub extern \"system\" fn border_hwnds(hwnd: HWND, lparam: LPARAM) -> BOOL {\n    let hwnds = unsafe { &mut *(lparam.0 as *mut Vec<isize>) };\n    let hwnd = hwnd.0 as isize;\n\n    if let Ok(class) = WindowsApi::real_window_class_w(hwnd)\n        && class.starts_with(\"komoborder\")\n    {\n        hwnds.push(hwnd);\n    }\n\n    true.into()\n}\n\n#[derive(Debug, Clone)]\npub struct Border {\n    pub hwnd: isize,\n    pub id: String,\n    pub monitor_idx: Option<usize>,\n    pub render_target: Option<RenderTarget>,\n    pub tracking_hwnd: isize,\n    pub window_rect: Rect,\n    pub window_kind: WindowKind,\n    pub style: BorderStyle,\n    pub width: i32,\n    pub offset: i32,\n    pub brush_properties: D2D1_BRUSH_PROPERTIES,\n    pub rounded_rect: D2D1_ROUNDED_RECT,\n    pub brushes: HashMap<WindowKind, ID2D1SolidColorBrush>,\n    pub is_destroying: Arc<AtomicBool>,\n}\n\nimpl From<isize> for Border {\n    fn from(value: isize) -> Self {\n        Self {\n            hwnd: value,\n            id: String::new(),\n            monitor_idx: None,\n            render_target: None,\n            tracking_hwnd: 0,\n            window_rect: Rect::default(),\n            window_kind: WindowKind::Unfocused,\n            style: STYLE.load(),\n            width: BORDER_WIDTH.load(Ordering::Relaxed),\n            offset: BORDER_OFFSET.load(Ordering::Relaxed),\n            brush_properties: D2D1_BRUSH_PROPERTIES::default(),\n            rounded_rect: D2D1_ROUNDED_RECT::default(),\n            brushes: HashMap::new(),\n            is_destroying: Arc::new(AtomicBool::new(false)),\n        }\n    }\n}\n\nimpl Border {\n    pub const fn hwnd(&self) -> HWND {\n        HWND(windows_api::as_ptr!(self.hwnd))\n    }\n\n    pub fn create(\n        id: &str,\n        tracking_hwnd: isize,\n        monitor_idx: usize,\n    ) -> color_eyre::Result<Box<Self>> {\n        let name: Vec<u16> = format!(\"komoborder-{id}\\0\").encode_utf16().collect();\n        let class_name = PCWSTR(name.as_ptr());\n\n        let h_module = WindowsApi::module_handle_w()?;\n\n        let window_class = WNDCLASSW {\n            hInstance: h_module.into(),\n            lpszClassName: class_name,\n            lpfnWndProc: Some(Self::callback),\n            hbrBackground: WindowsApi::create_solid_brush(0),\n            ..Default::default()\n        };\n\n        let _ = WindowsApi::register_class_w(&window_class);\n\n        let (border_sender, border_receiver) = mpsc::channel();\n\n        let instance = h_module.0 as isize;\n        let container_id = id.to_owned();\n        std::thread::spawn(move || -> color_eyre::Result<()> {\n            let mut border = Self {\n                hwnd: 0,\n                id: container_id,\n                monitor_idx: Some(monitor_idx),\n                render_target: None,\n                tracking_hwnd,\n                window_rect: WindowsApi::window_rect(tracking_hwnd).unwrap_or_default(),\n                window_kind: WindowKind::Unfocused,\n                style: STYLE.load(),\n                width: BORDER_WIDTH.load(Ordering::Relaxed),\n                offset: BORDER_OFFSET.load(Ordering::Relaxed),\n                brush_properties: Default::default(),\n                rounded_rect: Default::default(),\n                brushes: HashMap::new(),\n                is_destroying: Arc::new(AtomicBool::new(false)),\n            };\n\n            let border_pointer = &raw mut border;\n            let hwnd =\n                WindowsApi::create_border_window(PCWSTR(name.as_ptr()), instance, border_pointer)?;\n\n            let boxed = unsafe {\n                (*border_pointer).hwnd = hwnd;\n                Box::from_raw(border_pointer)\n            };\n            border_sender.send(boxed)?;\n\n            let mut msg: MSG = MSG::default();\n\n            loop {\n                unsafe {\n                    if !GetMessageW(&mut msg, None, 0, 0).as_bool() {\n                        tracing::debug!(\"border window event processing thread shutdown\");\n                        break;\n                    };\n                    // TODO: error handling\n                    let _ = TranslateMessage(&msg);\n                    DispatchMessageW(&msg);\n                }\n            }\n\n            Ok(())\n        });\n\n        let mut border = border_receiver.recv()?;\n\n        // I have literally no idea, apparently this is to get rid of the black pixels\n        // around the edges of rounded corners? @lukeyou05 borrowed this from PowerToys\n        unsafe {\n            let pos: i32 = -GetSystemMetrics(SM_CXVIRTUALSCREEN) - 8;\n            let hrgn = CreateRectRgn(pos, 0, pos + 1, 1);\n            let mut bh: DWM_BLURBEHIND = Default::default();\n            if !hrgn.is_invalid() {\n                bh = DWM_BLURBEHIND {\n                    dwFlags: DWM_BB_ENABLE | DWM_BB_BLURREGION,\n                    fEnable: TRUE,\n                    hRgnBlur: hrgn,\n                    fTransitionOnMaximized: FALSE,\n                };\n            }\n\n            let _ = DwmEnableBlurBehindWindow(border.hwnd(), &bh);\n        }\n\n        border.update_brushes()?;\n\n        Ok(border)\n    }\n\n    pub fn update_brushes(&mut self) -> color_eyre::Result<()> {\n        let hwnd_render_target_properties = D2D1_HWND_RENDER_TARGET_PROPERTIES {\n            hwnd: HWND(windows_api::as_ptr!(self.hwnd)),\n            pixelSize: Default::default(),\n            presentOptions: D2D1_PRESENT_OPTIONS_IMMEDIATELY,\n        };\n\n        let render_target_properties = D2D1_RENDER_TARGET_PROPERTIES {\n            r#type: D2D1_RENDER_TARGET_TYPE_DEFAULT,\n            pixelFormat: D2D1_PIXEL_FORMAT {\n                format: DXGI_FORMAT_UNKNOWN,\n                alphaMode: D2D1_ALPHA_MODE_PREMULTIPLIED,\n            },\n            dpiX: 96.0,\n            dpiY: 96.0,\n            ..Default::default()\n        };\n\n        match unsafe {\n            RENDER_FACTORY\n                .CreateHwndRenderTarget(&render_target_properties, &hwnd_render_target_properties)\n        } {\n            Ok(render_target) => unsafe {\n                self.brush_properties = *BRUSH_PROPERTIES.deref();\n                for window_kind in [\n                    WindowKind::Single,\n                    WindowKind::Stack,\n                    WindowKind::Monocle,\n                    WindowKind::Unfocused,\n                    WindowKind::Floating,\n                    WindowKind::UnfocusedLocked,\n                ] {\n                    let color = window_kind_colour(window_kind);\n                    let color = D2D1_COLOR_F {\n                        r: ((color & 0xFF) as f32) / 255.0,\n                        g: (((color >> 8) & 0xFF) as f32) / 255.0,\n                        b: (((color >> 16) & 0xFF) as f32) / 255.0,\n                        a: 1.0,\n                    };\n\n                    if let Ok(brush) =\n                        render_target.CreateSolidColorBrush(&color, Some(&self.brush_properties))\n                    {\n                        self.brushes.insert(window_kind, brush);\n                    }\n                }\n\n                render_target.SetAntialiasMode(D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);\n\n                self.render_target = Some(RenderTarget(render_target));\n\n                self.rounded_rect = {\n                    let radius = 8.0 + self.width as f32 / 2.0;\n                    D2D1_ROUNDED_RECT {\n                        rect: Default::default(),\n                        radiusX: radius,\n                        radiusY: radius,\n                    }\n                };\n\n                Ok(())\n            },\n            Err(error) => Err(error.into()),\n        }\n    }\n\n    pub fn destroy(&self) -> color_eyre::Result<()> {\n        // signal that we're destroying - prevents new render operations\n        self.is_destroying.store(true, Ordering::Release);\n\n        // small delay to allow in-flight render operations to complete\n        std::thread::sleep(std::time::Duration::from_millis(10));\n\n        // clear user data **BEFORE** closing window\n        // pending messages will see a null pointer and exit early\n        unsafe {\n            SetWindowLongPtrW(self.hwnd(), GWLP_USERDATA, 0);\n        }\n        WindowsApi::close_window(self.hwnd)\n    }\n\n    pub fn set_position(&self, rect: &Rect, reference_hwnd: isize) -> color_eyre::Result<()> {\n        let mut rect = *rect;\n        rect.add_margin(self.width);\n        rect.add_padding(-self.offset);\n\n        WindowsApi::set_border_pos(self.hwnd, &rect, reference_hwnd)?;\n\n        Ok(())\n    }\n\n    // this triggers WM_PAINT in the callback below\n    pub fn invalidate(&self) {\n        let _ = unsafe { InvalidateRect(Option::from(self.hwnd()), None, false) };\n    }\n\n    pub extern \"system\" fn callback(\n        window: HWND,\n        message: u32,\n        wparam: WPARAM,\n        lparam: LPARAM,\n    ) -> LRESULT {\n        unsafe {\n            match message {\n                WM_SETCURSOR => match LoadCursorW(None, IDC_ARROW) {\n                    Ok(cursor) => {\n                        SetCursor(Some(cursor));\n                        LRESULT(0)\n                    }\n                    Err(error) => {\n                        tracing::error!(\"{error}\");\n                        LRESULT(1)\n                    }\n                },\n                WM_CREATE => {\n                    let mut border_pointer: *mut Border =\n                        GetWindowLongPtrW(window, GWLP_USERDATA) as _;\n\n                    if border_pointer.is_null() {\n                        let create_struct: *mut CREATESTRUCTW = lparam.0 as *mut _;\n                        border_pointer = (*create_struct).lpCreateParams as *mut _;\n                        SetWindowLongPtrW(window, GWLP_USERDATA, border_pointer as _);\n                    }\n\n                    LRESULT(0)\n                }\n                EVENT_OBJECT_DESTROY => {\n                    let border_pointer: *mut Border = GetWindowLongPtrW(window, GWLP_USERDATA) as _;\n\n                    if border_pointer.is_null() {\n                        return LRESULT(0);\n                    }\n\n                    // we don't actually want to destroy the window here, just hide it for quicker\n                    // visual feedback to the user; the actual destruction will be handled by the\n                    // core border manager loop\n                    WindowsApi::hide_window(window.0 as isize);\n                    LRESULT(0)\n                }\n                EVENT_OBJECT_LOCATIONCHANGE => {\n                    let border_pointer: *mut Border = GetWindowLongPtrW(window, GWLP_USERDATA) as _;\n\n                    if border_pointer.is_null() {\n                        return LRESULT(0);\n                    }\n\n                    if (*border_pointer).is_destroying.load(Ordering::Acquire) {\n                        return LRESULT(0);\n                    }\n\n                    let reference_hwnd = (*border_pointer).tracking_hwnd;\n\n                    let old_rect = (*border_pointer).window_rect;\n                    let rect = WindowsApi::window_rect(reference_hwnd).unwrap_or_default();\n\n                    (*border_pointer).window_rect = rect;\n\n                    if let Err(error) = (*border_pointer).set_position(&rect, reference_hwnd) {\n                        tracing::error!(\"failed to update border position {error}\");\n                    }\n\n                    if (!rect.is_same_size_as(&old_rect) || !rect.has_same_position_as(&old_rect))\n                        && let Some(render_target) = (*border_pointer).render_target.as_ref()\n                    {\n                        // double-check destruction flag before rendering\n                        if (*border_pointer).is_destroying.load(Ordering::Acquire) {\n                            return LRESULT(0);\n                        }\n\n                        let border_width = (*border_pointer).width;\n                        let border_offset = (*border_pointer).offset;\n\n                        (*border_pointer).rounded_rect.rect = D2D_RECT_F {\n                            left: (border_width / 2 - border_offset) as f32,\n                            top: (border_width / 2 - border_offset) as f32,\n                            right: (rect.right - border_width / 2 + border_offset) as f32,\n                            bottom: (rect.bottom - border_width / 2 + border_offset) as f32,\n                        };\n\n                        let _ = render_target.Resize(&D2D_SIZE_U {\n                            width: rect.right as u32,\n                            height: rect.bottom as u32,\n                        });\n\n                        let window_kind = (*border_pointer).window_kind;\n                        if let Some(brush) = (*border_pointer).brushes.get(&window_kind) {\n                            render_target.BeginDraw();\n                            render_target.Clear(None);\n\n                            // Calculate border radius based on style\n                            let style = match (*border_pointer).style {\n                                BorderStyle::System => {\n                                    if *WINDOWS_11 {\n                                        BorderStyle::Rounded\n                                    } else {\n                                        BorderStyle::Square\n                                    }\n                                }\n                                BorderStyle::Rounded => BorderStyle::Rounded,\n                                BorderStyle::Square => BorderStyle::Square,\n                            };\n\n                            match style {\n                                BorderStyle::Rounded => {\n                                    render_target.DrawRoundedRectangle(\n                                        &(*border_pointer).rounded_rect,\n                                        brush,\n                                        border_width as f32,\n                                        None,\n                                    );\n                                }\n                                BorderStyle::Square => {\n                                    render_target.DrawRectangle(\n                                        &(*border_pointer).rounded_rect.rect,\n                                        brush,\n                                        border_width as f32,\n                                        None,\n                                    );\n                                }\n                                _ => {}\n                            }\n\n                            let _ = render_target.EndDraw(None, None);\n                        }\n                    }\n\n                    LRESULT(0)\n                }\n                WM_PAINT => {\n                    if let Ok(rect) = WindowsApi::window_rect(window.0 as isize) {\n                        let border_pointer: *mut Border =\n                            GetWindowLongPtrW(window, GWLP_USERDATA) as _;\n\n                        if border_pointer.is_null() {\n                            return LRESULT(0);\n                        }\n\n                        if (*border_pointer).is_destroying.load(Ordering::Acquire) {\n                            return LRESULT(0);\n                        }\n\n                        let reference_hwnd = (*border_pointer).tracking_hwnd;\n\n                        // Update position to update the ZOrder\n                        let border_window_rect = (*border_pointer).window_rect;\n\n                        tracing::trace!(\"updating border position\");\n                        if let Err(error) =\n                            (*border_pointer).set_position(&border_window_rect, reference_hwnd)\n                        {\n                            tracing::error!(\"failed to update border position {error}\");\n                        }\n\n                        if let Some(render_target) = (*border_pointer).render_target.as_ref() {\n                            // double-check destruction flag before rendering\n                            if (*border_pointer).is_destroying.load(Ordering::Acquire) {\n                                return LRESULT(0);\n                            }\n\n                            (*border_pointer).width = BORDER_WIDTH.load(Ordering::Relaxed);\n                            (*border_pointer).offset = BORDER_OFFSET.load(Ordering::Relaxed);\n\n                            let border_width = (*border_pointer).width;\n                            let border_offset = (*border_pointer).offset;\n\n                            (*border_pointer).rounded_rect.rect = D2D_RECT_F {\n                                left: (border_width / 2 - border_offset) as f32,\n                                top: (border_width / 2 - border_offset) as f32,\n                                right: (rect.right - border_width / 2 + border_offset) as f32,\n                                bottom: (rect.bottom - border_width / 2 + border_offset) as f32,\n                            };\n\n                            let _ = render_target.Resize(&D2D_SIZE_U {\n                                width: rect.right as u32,\n                                height: rect.bottom as u32,\n                            });\n\n                            // Get window kind and color\n                            let window_kind = (*border_pointer).window_kind;\n                            if let Some(brush) = (*border_pointer).brushes.get(&window_kind) {\n                                render_target.BeginDraw();\n                                render_target.Clear(None);\n\n                                (*border_pointer).style = STYLE.load();\n\n                                // Calculate border radius based on style\n                                let style = match (*border_pointer).style {\n                                    BorderStyle::System => {\n                                        if *WINDOWS_11 {\n                                            BorderStyle::Rounded\n                                        } else {\n                                            BorderStyle::Square\n                                        }\n                                    }\n                                    BorderStyle::Rounded => BorderStyle::Rounded,\n                                    BorderStyle::Square => BorderStyle::Square,\n                                };\n\n                                match style {\n                                    BorderStyle::Rounded => {\n                                        render_target.DrawRoundedRectangle(\n                                            &(*border_pointer).rounded_rect,\n                                            brush,\n                                            border_width as f32,\n                                            None,\n                                        );\n                                    }\n                                    BorderStyle::Square => {\n                                        render_target.DrawRectangle(\n                                            &(*border_pointer).rounded_rect.rect,\n                                            brush,\n                                            border_width as f32,\n                                            None,\n                                        );\n                                    }\n                                    _ => {}\n                                }\n\n                                let _ = render_target.EndDraw(None, None);\n                            }\n                        }\n                    }\n                    let _ = ValidateRect(Option::from(window), None);\n                    LRESULT(0)\n                }\n                WM_DESTROY => {\n                    let border_pointer: *mut Border = GetWindowLongPtrW(window, GWLP_USERDATA) as _;\n                    if !border_pointer.is_null() {\n                        (*border_pointer).render_target = None;\n                        (*border_pointer).brushes.clear();\n                        SetWindowLongPtrW(window, GWLP_USERDATA, 0);\n                    }\n                    PostQuitMessage(0);\n                    LRESULT(0)\n                }\n                _ => DefWindowProcW(window, message, wparam, lparam),\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "komorebi/src/border_manager/mod.rs",
    "content": "#![deny(clippy::unwrap_used, clippy::expect_used)]\n\nmod border;\nuse crate::WindowManager;\nuse crate::WindowsApi;\nuse crate::core::BorderImplementation;\nuse crate::core::BorderStyle;\nuse crate::core::WindowKind;\nuse crate::ring::Ring;\nuse crate::windows_api;\nuse crate::workspace::Workspace;\nuse crate::workspace::WorkspaceLayer;\npub use border::Border;\nuse border::border_hwnds;\nuse crossbeam_channel::Receiver;\nuse crossbeam_channel::Sender;\nuse crossbeam_utils::atomic::AtomicCell;\nuse crossbeam_utils::atomic::AtomicConsume;\nuse komorebi_themes::colour::Colour;\nuse komorebi_themes::colour::Rgb;\nuse lazy_static::lazy_static;\nuse parking_lot::Mutex;\nuse serde::Deserialize;\nuse serde::Serialize;\nuse std::collections::HashMap;\nuse std::collections::hash_map::Entry;\nuse std::ops::Deref;\nuse std::sync::Arc;\nuse std::sync::OnceLock;\nuse std::sync::atomic::AtomicBool;\nuse std::sync::atomic::AtomicI32;\nuse std::sync::atomic::AtomicU32;\nuse std::sync::atomic::Ordering;\nuse strum::Display;\nuse windows::Win32::Foundation::HWND;\nuse windows::Win32::Graphics::Direct2D::ID2D1HwndRenderTarget;\n\npub static BORDER_WIDTH: AtomicI32 = AtomicI32::new(8);\npub static BORDER_OFFSET: AtomicI32 = AtomicI32::new(-1);\n\npub static BORDER_ENABLED: AtomicBool = AtomicBool::new(true);\n\nlazy_static! {\n    pub static ref STYLE: AtomicCell<BorderStyle> = AtomicCell::new(BorderStyle::System);\n    pub static ref IMPLEMENTATION: AtomicCell<BorderImplementation> =\n        AtomicCell::new(BorderImplementation::Komorebi);\n    pub static ref FOCUSED: AtomicU32 =\n        AtomicU32::new(u32::from(Colour::Rgb(Rgb::new(66, 165, 245))));\n    pub static ref UNFOCUSED: AtomicU32 =\n        AtomicU32::new(u32::from(Colour::Rgb(Rgb::new(128, 128, 128))));\n    pub static ref UNFOCUSED_LOCKED: AtomicU32 =\n        AtomicU32::new(u32::from(Colour::Rgb(Rgb::new(158, 8, 8))));\n    pub static ref MONOCLE: AtomicU32 =\n        AtomicU32::new(u32::from(Colour::Rgb(Rgb::new(255, 51, 153))));\n    pub static ref STACK: AtomicU32 = AtomicU32::new(u32::from(Colour::Rgb(Rgb::new(0, 165, 66))));\n    pub static ref FLOATING: AtomicU32 =\n        AtomicU32::new(u32::from(Colour::Rgb(Rgb::new(245, 245, 165))));\n}\n\nlazy_static! {\n    static ref BORDER_STATE: Mutex<HashMap<String, Box<Border>>> = Mutex::new(HashMap::new());\n    static ref WINDOWS_BORDERS: Mutex<HashMap<isize, String>> = Mutex::new(HashMap::new());\n}\n\n#[derive(Debug, Clone)]\npub struct RenderTarget(pub ID2D1HwndRenderTarget);\nunsafe impl Send for RenderTarget {}\n\nimpl Deref for RenderTarget {\n    type Target = ID2D1HwndRenderTarget;\n\n    fn deref(&self) -> &Self::Target {\n        &self.0\n    }\n}\n\npub enum Notification {\n    Update(Option<isize>),\n    ForceUpdate,\n}\n\n#[derive(Debug, Default, Clone, Copy, PartialEq)]\npub struct BorderInfo {\n    pub border_hwnd: isize,\n    pub window_kind: WindowKind,\n}\n\nimpl BorderInfo {\n    pub fn hwnd(&self) -> HWND {\n        HWND(windows_api::as_ptr!(self.border_hwnd))\n    }\n}\n\nstatic CHANNEL: OnceLock<(Sender<Notification>, Receiver<Notification>)> = OnceLock::new();\n\npub fn channel() -> &'static (Sender<Notification>, Receiver<Notification>) {\n    CHANNEL.get_or_init(|| crossbeam_channel::bounded(50))\n}\n\nfn event_tx() -> Sender<Notification> {\n    channel().0.clone()\n}\n\nfn event_rx() -> Receiver<Notification> {\n    channel().1.clone()\n}\n\npub fn window_border(hwnd: isize) -> Option<BorderInfo> {\n    let id = WINDOWS_BORDERS.lock().get(&hwnd)?.clone();\n    BORDER_STATE.lock().get(&id).map(|b| BorderInfo {\n        border_hwnd: b.hwnd,\n        window_kind: b.window_kind,\n    })\n}\n\npub fn send_notification(hwnd: Option<isize>) {\n    if event_tx().try_send(Notification::Update(hwnd)).is_err() {\n        tracing::warn!(\"channel is full; dropping notification\")\n    }\n}\n\npub fn send_force_update() {\n    if event_tx().try_send(Notification::ForceUpdate).is_err() {\n        tracing::warn!(\"channel is full; dropping notification\")\n    }\n}\n\npub fn destroy_all_borders() -> color_eyre::Result<()> {\n    let mut borders = BORDER_STATE.lock();\n    tracing::info!(\n        \"purging known borders: {:?}\",\n        borders.iter().map(|b| b.1.hwnd).collect::<Vec<_>>()\n    );\n\n    for (_, border) in borders.drain() {\n        let _ = destroy_border(border);\n    }\n\n    drop(borders);\n\n    WINDOWS_BORDERS.lock().clear();\n\n    let mut remaining_hwnds = vec![];\n\n    WindowsApi::enum_windows(\n        Some(border_hwnds),\n        &mut remaining_hwnds as *mut Vec<isize> as isize,\n    )?;\n\n    if !remaining_hwnds.is_empty() {\n        tracing::info!(\"purging unknown borders: {:?}\", remaining_hwnds);\n\n        for hwnd in remaining_hwnds {\n            let _ = destroy_border(Box::new(Border::from(hwnd)));\n        }\n    }\n\n    Ok(())\n}\n\nfn window_kind_colour(focus_kind: WindowKind) -> u32 {\n    match focus_kind {\n        WindowKind::Unfocused => UNFOCUSED.load(Ordering::Relaxed),\n        WindowKind::UnfocusedLocked => UNFOCUSED_LOCKED.load(Ordering::Relaxed),\n        WindowKind::Single => FOCUSED.load(Ordering::Relaxed),\n        WindowKind::Stack => STACK.load(Ordering::Relaxed),\n        WindowKind::Monocle => MONOCLE.load(Ordering::Relaxed),\n        WindowKind::Floating => FLOATING.load(Ordering::Relaxed),\n    }\n}\n\npub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) {\n    std::thread::spawn(move || {\n        loop {\n            match handle_notifications(wm.clone()) {\n                Ok(()) => {\n                    tracing::warn!(\"restarting finished thread\");\n                }\n                Err(error) => {\n                    tracing::warn!(\"restarting failed thread: {}\", error);\n                }\n            }\n        }\n    });\n}\n\npub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result<()> {\n    tracing::info!(\"listening\");\n\n    let receiver = event_rx();\n    event_tx().send(Notification::Update(None))?;\n\n    let mut previous_snapshot = Ring::default();\n    let mut previous_pending_move_op = None;\n    let mut previous_is_paused = false;\n    let mut previous_notification: Option<Notification> = None;\n    let mut previous_layer = WorkspaceLayer::default();\n\n    'receiver: for notification in receiver {\n        // Check the wm state every time we receive a notification\n        let state = wm.lock();\n        let is_paused = state.is_paused;\n        let focused_monitor_idx = state.focused_monitor_idx();\n        let focused_workspace_idx =\n            state.monitors.elements()[focused_monitor_idx].focused_workspace_idx();\n        let monitors = state.monitors.clone();\n        let pending_move_op = *state.pending_move_op;\n        let floating_window_hwnds = state.monitors.elements()[focused_monitor_idx].workspaces()\n            [focused_workspace_idx]\n            .floating_windows()\n            .iter()\n            .map(|w| w.hwnd)\n            .collect::<Vec<_>>();\n        let workspace_layer = state.monitors.elements()[focused_monitor_idx].workspaces()\n            [focused_workspace_idx]\n            .layer;\n        let foreground_window = WindowsApi::foreground_window().unwrap_or_default();\n        let layer_changed = previous_layer != workspace_layer;\n        let forced_update = matches!(notification, Notification::ForceUpdate);\n\n        drop(state);\n\n        match IMPLEMENTATION.load() {\n            BorderImplementation::Windows => {\n                'monitors: for (monitor_idx, m) in monitors.elements().iter().enumerate() {\n                    // Only operate on the focused workspace of each monitor\n                    if let Some(ws) = m.focused_workspace() {\n                        // Handle the monocle container separately\n                        if let Some(monocle) = &ws.monocle_container {\n                            let window_kind = if monitor_idx != focused_monitor_idx {\n                                WindowKind::Unfocused\n                            } else {\n                                WindowKind::Monocle\n                            };\n\n                            monocle\n                                .focused_window()\n                                .copied()\n                                .unwrap_or_default()\n                                .set_accent(window_kind_colour(window_kind))?;\n\n                            if ws.layer == WorkspaceLayer::Floating {\n                                for window in ws.floating_windows() {\n                                    let mut window_kind = WindowKind::Unfocused;\n\n                                    if foreground_window == window.hwnd {\n                                        window_kind = WindowKind::Floating;\n                                    }\n\n                                    window.set_accent(window_kind_colour(window_kind))?;\n                                }\n                            }\n                            continue 'monitors;\n                        }\n\n                        for (idx, c) in ws.containers().iter().enumerate() {\n                            let window_kind = if idx != ws.focused_container_idx()\n                                || monitor_idx != focused_monitor_idx\n                            {\n                                if c.locked {\n                                    WindowKind::UnfocusedLocked\n                                } else {\n                                    WindowKind::Unfocused\n                                }\n                            } else if c.windows().len() > 1 {\n                                WindowKind::Stack\n                            } else {\n                                WindowKind::Single\n                            };\n\n                            c.focused_window()\n                                .copied()\n                                .unwrap_or_default()\n                                .set_accent(window_kind_colour(window_kind))?;\n                        }\n\n                        for window in ws.floating_windows() {\n                            let mut window_kind = WindowKind::Unfocused;\n\n                            if foreground_window == window.hwnd {\n                                window_kind = WindowKind::Floating;\n                            }\n\n                            window.set_accent(window_kind_colour(window_kind))?;\n                        }\n                    }\n                }\n            }\n            BorderImplementation::Komorebi => {\n                let should_process_notification = match notification {\n                    Notification::Update(notification_hwnd) => {\n                        let mut should_process_notification = true;\n\n                        if monitors == previous_snapshot\n                        // handle the window dragging edge case\n                        && pending_move_op == previous_pending_move_op\n                        {\n                            should_process_notification = false;\n                        }\n\n                        // handle the pause edge case\n                        if is_paused && !previous_is_paused {\n                            should_process_notification = true;\n                        }\n\n                        // handle the unpause edge case\n                        if previous_is_paused && !is_paused {\n                            should_process_notification = true;\n                        }\n\n                        // handle the retile edge case\n                        if !should_process_notification && BORDER_STATE.lock().is_empty() {\n                            should_process_notification = true;\n                        }\n\n                        // when we switch focus to/from a floating window\n                        let switch_focus_to_from_floating_window =\n                            floating_window_hwnds.iter().any(|fw| {\n                                // if we switch focus to a floating window\n                                fw == &notification_hwnd.unwrap_or_default() ||\n                        // if there is any floating window with a `WindowKind::Floating` border\n                        // that no longer is the foreground window then we need to update that\n                        // border.\n                        (fw != &foreground_window\n                            && window_border(*fw)\n                            .is_some_and(|b| b.window_kind == WindowKind::Floating))\n                            });\n\n                        // when the focused window has an `Unfocused` border kind, usually this happens if\n                        // we focus an admin window and then refocus the previously focused window. For\n                        // komorebi it will have the same state has before, however the previously focused\n                        // window changed its border to unfocused so now we need to update it again.\n                        if !should_process_notification\n                            && window_border(notification_hwnd.unwrap_or_default())\n                                .is_some_and(|b| b.window_kind == WindowKind::Unfocused)\n                        {\n                            should_process_notification = true;\n                        }\n\n                        if !should_process_notification && switch_focus_to_from_floating_window {\n                            should_process_notification = true;\n                        }\n\n                        if !should_process_notification\n                            && let Some(Notification::Update(ref previous)) = previous_notification\n                            && previous.unwrap_or_default() != notification_hwnd.unwrap_or_default()\n                        {\n                            should_process_notification = true;\n                        }\n\n                        should_process_notification\n                    }\n                    Notification::ForceUpdate => true,\n                };\n\n                if !should_process_notification {\n                    tracing::debug!(\"monitor state matches latest snapshot, skipping notification\");\n                    continue 'receiver;\n                }\n\n                let mut borders = BORDER_STATE.lock();\n                let mut windows_borders = WINDOWS_BORDERS.lock();\n\n                // If borders are disabled\n                if !BORDER_ENABLED.load_consume()\n                    // Or if the wm is paused\n                    || is_paused\n                {\n                    // Destroy the borders we know about\n                    for (_, border) in borders.drain() {\n                        destroy_border(border)?;\n                    }\n\n                    windows_borders.clear();\n\n                    previous_is_paused = is_paused;\n                    continue 'receiver;\n                }\n\n                'monitors: for (monitor_idx, m) in monitors.elements().iter().enumerate() {\n                    // Only operate on the focused workspace of each monitor\n                    if let Some(ws) = m.focused_workspace() {\n                        // Workspaces with tiling disabled don't have borders\n                        if !ws.tile {\n                            // Remove all borders on this monitor\n                            remove_borders(\n                                &mut borders,\n                                &mut windows_borders,\n                                monitor_idx,\n                                |_, _| true,\n                            )?;\n\n                            continue 'monitors;\n                        }\n\n                        // Handle the monocle container separately\n                        if let Some(monocle) = &ws.monocle_container {\n                            let mut new_border = false;\n                            let focused_window_hwnd =\n                                monocle.focused_window().map(|w| w.hwnd).unwrap_or_default();\n                            let id = monocle.id.clone();\n                            let border = match borders.entry(id.clone()) {\n                                Entry::Occupied(entry) => entry.into_mut(),\n                                Entry::Vacant(entry) => {\n                                    if let Ok(border) = Border::create(\n                                        &monocle.id,\n                                        focused_window_hwnd,\n                                        monitor_idx,\n                                    ) {\n                                        new_border = true;\n                                        entry.insert(border)\n                                    } else {\n                                        continue 'monitors;\n                                    }\n                                }\n                            };\n\n                            let new_focus_state = if monitor_idx != focused_monitor_idx {\n                                WindowKind::Unfocused\n                            } else {\n                                WindowKind::Monocle\n                            };\n                            border.window_kind = new_focus_state;\n\n                            // Update the borders tracking_hwnd in case it changed and remove the\n                            // old `tracking_hwnd` from `WINDOWS_BORDERS` if needed.\n                            if border.tracking_hwnd != focused_window_hwnd {\n                                if let Some(previous) = windows_borders.get(&border.tracking_hwnd) {\n                                    // Only remove the border from `windows_borders` if it\n                                    // still corresponds to the same border, if doesn't then\n                                    // it means it was already updated by another border for\n                                    // that window and in that case we don't want to remove it.\n                                    if previous == &id {\n                                        windows_borders.remove(&border.tracking_hwnd);\n                                    }\n                                }\n                                border.tracking_hwnd = focused_window_hwnd;\n                                if !WindowsApi::is_window_visible(border.hwnd) {\n                                    WindowsApi::restore_window(border.hwnd);\n                                }\n                            }\n\n                            // Update the border's monitor idx in case it changed\n                            border.monitor_idx = Some(monitor_idx);\n\n                            let rect = WindowsApi::window_rect(focused_window_hwnd)?;\n                            border.window_rect = rect;\n\n                            if new_border {\n                                border.set_position(&rect, focused_window_hwnd)?;\n                            } else if matches!(notification, Notification::ForceUpdate) {\n                                // Update the border brushes if there was a forced update\n                                // notification and this is not a new border (new border's\n                                // already have their brushes updated on creation)\n                                border.update_brushes()?;\n                            }\n\n                            border.invalidate();\n\n                            windows_borders.insert(focused_window_hwnd, id);\n\n                            let border_hwnd = border.hwnd;\n\n                            if ws.layer == WorkspaceLayer::Floating {\n                                handle_floating_borders(\n                                    &mut borders,\n                                    &mut windows_borders,\n                                    ws,\n                                    monitor_idx,\n                                    foreground_window,\n                                    layer_changed,\n                                    forced_update,\n                                )?;\n\n                                // Remove all borders on this monitor except monocle and floating borders\n                                remove_borders(\n                                    &mut borders,\n                                    &mut windows_borders,\n                                    monitor_idx,\n                                    |_, b| {\n                                        border_hwnd != b.hwnd\n                                            && !ws\n                                                .floating_windows()\n                                                .iter()\n                                                .any(|w| w.hwnd == b.tracking_hwnd)\n                                    },\n                                )?;\n                            } else {\n                                // Remove all borders on this monitor except monocle\n                                remove_borders(\n                                    &mut borders,\n                                    &mut windows_borders,\n                                    monitor_idx,\n                                    |_, b| border_hwnd != b.hwnd,\n                                )?;\n                            }\n                            continue 'monitors;\n                        }\n\n                        let foreground_hwnd = WindowsApi::foreground_window().unwrap_or_default();\n                        let foreground_monitor_id =\n                            WindowsApi::monitor_from_window(foreground_hwnd);\n                        let is_maximized =\n                            foreground_monitor_id == m.id && WindowsApi::is_zoomed(foreground_hwnd);\n\n                        if is_maximized {\n                            // Remove all borders on this monitor\n                            remove_borders(\n                                &mut borders,\n                                &mut windows_borders,\n                                monitor_idx,\n                                |_, _| true,\n                            )?;\n\n                            continue 'monitors;\n                        }\n\n                        // Collect focused workspace container and floating windows ID's\n                        let mut container_and_floating_window_ids = ws\n                            .containers()\n                            .iter()\n                            .map(|c| c.id.clone())\n                            .collect::<Vec<_>>();\n\n                        for w in ws.floating_windows() {\n                            container_and_floating_window_ids.push(w.hwnd.to_string());\n                        }\n\n                        // Remove any borders not associated with the focused workspace\n                        remove_borders(\n                            &mut borders,\n                            &mut windows_borders,\n                            monitor_idx,\n                            |id, _| !container_and_floating_window_ids.contains(id),\n                        )?;\n\n                        'containers: for (idx, c) in ws.containers().iter().enumerate() {\n                            let focused_window_hwnd =\n                                c.focused_window().map(|w| w.hwnd).unwrap_or_default();\n                            let id = c.id.clone();\n\n                            // Get the border entry for this container from the map or create one\n                            let mut new_border = false;\n                            let border = match borders.entry(id.clone()) {\n                                Entry::Occupied(entry) => entry.into_mut(),\n                                Entry::Vacant(entry) => {\n                                    if let Ok(border) =\n                                        Border::create(&c.id, focused_window_hwnd, monitor_idx)\n                                    {\n                                        new_border = true;\n                                        entry.insert(border)\n                                    } else {\n                                        continue 'monitors;\n                                    }\n                                }\n                            };\n\n                            let last_focus_state = border.window_kind;\n\n                            let new_focus_state = if idx != ws.focused_container_idx()\n                                || monitor_idx != focused_monitor_idx\n                                || focused_window_hwnd != foreground_window\n                            {\n                                if c.locked {\n                                    WindowKind::UnfocusedLocked\n                                } else {\n                                    WindowKind::Unfocused\n                                }\n                            } else if c.windows().len() > 1 {\n                                WindowKind::Stack\n                            } else {\n                                WindowKind::Single\n                            };\n\n                            border.window_kind = new_focus_state;\n\n                            // Update the borders `tracking_hwnd` in case it changed and remove the\n                            // old `tracking_hwnd` from `WINDOWS_BORDERS` if needed.\n                            if border.tracking_hwnd != focused_window_hwnd {\n                                if let Some(previous) = windows_borders.get(&border.tracking_hwnd) {\n                                    // Only remove the border from `windows_borders` if it\n                                    // still corresponds to the same border, if doesn't then\n                                    // it means it was already updated by another border for\n                                    // that window and in that case we don't want to remove it.\n                                    if previous == &id {\n                                        windows_borders.remove(&border.tracking_hwnd);\n                                    }\n                                }\n                                border.tracking_hwnd = focused_window_hwnd;\n                                if !WindowsApi::is_window_visible(border.hwnd) {\n                                    WindowsApi::restore_window(border.hwnd);\n                                }\n                            }\n\n                            // Update the border's monitor idx in case it changed\n                            border.monitor_idx = Some(monitor_idx);\n\n                            // avoid getting into a thread restart loop if we try to look up\n                            // rect info for a window that has been destroyed by the time\n                            // we get here\n                            let rect = match WindowsApi::window_rect(focused_window_hwnd) {\n                                Ok(rect) => rect,\n                                Err(_) => {\n                                    remove_border(&c.id, &mut borders, &mut windows_borders)?;\n                                    continue 'containers;\n                                }\n                            };\n                            border.window_rect = rect;\n\n                            let should_invalidate = new_border\n                                || (last_focus_state != new_focus_state)\n                                || layer_changed\n                                || forced_update;\n\n                            if should_invalidate {\n                                if forced_update && !new_border {\n                                    // Update the border brushes if there was a forced update\n                                    // notification and this is not a new border (new border's\n                                    // already have their brushes updated on creation)\n                                    border.update_brushes()?;\n                                }\n                                border.set_position(&rect, focused_window_hwnd)?;\n                                border.invalidate();\n                            }\n\n                            windows_borders.insert(focused_window_hwnd, id);\n                        }\n\n                        handle_floating_borders(\n                            &mut borders,\n                            &mut windows_borders,\n                            ws,\n                            monitor_idx,\n                            foreground_window,\n                            layer_changed,\n                            forced_update,\n                        )?;\n                    }\n                }\n            }\n        }\n\n        previous_snapshot = monitors;\n        previous_pending_move_op = pending_move_op;\n        previous_is_paused = is_paused;\n        previous_notification = Some(notification);\n        previous_layer = workspace_layer;\n    }\n\n    Ok(())\n}\n\nfn handle_floating_borders(\n    borders: &mut HashMap<String, Box<Border>>,\n    windows_borders: &mut HashMap<isize, String>,\n    ws: &Workspace,\n    monitor_idx: usize,\n    foreground_window: isize,\n    layer_changed: bool,\n    forced_update: bool,\n) -> color_eyre::Result<()> {\n    for window in ws.floating_windows() {\n        let mut new_border = false;\n        let id = window.hwnd.to_string();\n        let border = match borders.entry(id.clone()) {\n            Entry::Occupied(entry) => entry.into_mut(),\n            Entry::Vacant(entry) => {\n                if let Ok(border) =\n                    Border::create(&window.hwnd.to_string(), window.hwnd, monitor_idx)\n                {\n                    new_border = true;\n                    entry.insert(border)\n                } else {\n                    return Ok(());\n                }\n            }\n        };\n\n        let last_focus_state = border.window_kind;\n\n        let new_focus_state = if foreground_window == window.hwnd {\n            WindowKind::Floating\n        } else {\n            WindowKind::Unfocused\n        };\n\n        border.window_kind = new_focus_state;\n\n        // Update the border's monitor idx in case it changed\n        border.monitor_idx = Some(monitor_idx);\n\n        let rect = WindowsApi::window_rect(window.hwnd)?;\n        border.window_rect = rect;\n\n        let should_invalidate =\n            new_border || (last_focus_state != new_focus_state) || layer_changed || forced_update;\n\n        if should_invalidate {\n            if forced_update && !new_border {\n                // Update the border brushes if there was a forced update\n                // notification and this is not a new border (new border's\n                // already have their brushes updated on creation)\n                border.update_brushes()?;\n            }\n            border.set_position(&rect, window.hwnd)?;\n            border.invalidate();\n        }\n\n        windows_borders.insert(window.hwnd, id);\n    }\n\n    Ok(())\n}\n\n/// Removes all borders from monitor with index `monitor_idx` filtered by\n/// `condition`. This condition is a function that will take a reference to\n/// the container id and the border and returns a bool, if true that border\n/// will be removed.\nfn remove_borders(\n    borders: &mut HashMap<String, Box<Border>>,\n    windows_borders: &mut HashMap<isize, String>,\n    monitor_idx: usize,\n    condition: impl Fn(&String, &Border) -> bool,\n) -> color_eyre::Result<()> {\n    let mut to_remove = vec![];\n    for (id, border) in borders.iter() {\n        // if border is on this monitor\n        if border.monitor_idx.is_some_and(|idx| idx == monitor_idx)\n            // and the condition applies\n            && condition(id, border)\n            // and the border is visible (we don't remove hidden borders)\n            && WindowsApi::is_window_visible(border.hwnd)\n        {\n            // we mark it to be removed\n            to_remove.push(id.clone());\n        }\n    }\n\n    for id in &to_remove {\n        remove_border(id, borders, windows_borders)?;\n    }\n\n    Ok(())\n}\n\n/// Removes the border with `id` and all its related info from all maps\nfn remove_border(\n    id: &str,\n    borders: &mut HashMap<String, Box<Border>>,\n    windows_borders: &mut HashMap<isize, String>,\n) -> color_eyre::Result<()> {\n    if let Some(removed_border) = borders.remove(id) {\n        windows_borders.remove(&removed_border.tracking_hwnd);\n        destroy_border(removed_border)?;\n    }\n\n    Ok(())\n}\n\n/// IMPORTANT: BEWARE when changing this function. We need to make sure that we don't let the\n/// `Box<Border>` be dropped normally. We need to turn the `Box` into the raw pointer and use that\n/// pointer to call the `.destroy()` funtion of the border so it closes the window. This way the\n/// `Box` is consumed and the pointer is dropped like a normal `Copy` number instead of trying to\n/// drop the struct it points to. The actual border is owned by the thread that created the window\n/// and once the window closes that thread gets out of its loop, finishes and properly disposes of\n/// the border.\nfn destroy_border(border: Box<Border>) -> color_eyre::Result<()> {\n    let raw_pointer = Box::into_raw(border);\n    unsafe {\n        // Now safe to destroy window\n        (*raw_pointer).destroy()?;\n    }\n    Ok(())\n}\n\n/// Removes the border around window with `tracking_hwnd` if it exists\npub fn delete_border(tracking_hwnd: isize) {\n    std::thread::spawn(move || {\n        let id = {\n            WINDOWS_BORDERS\n                .lock()\n                .get(&tracking_hwnd)\n                .cloned()\n                .unwrap_or_default()\n        };\n\n        let mut borders = BORDER_STATE.lock();\n        let mut windows_borders = WINDOWS_BORDERS.lock();\n\n        if let Err(error) = remove_border(&id, &mut borders, &mut windows_borders) {\n            tracing::error!(\"Failed to delete border: {}\", error);\n        }\n    });\n}\n\n/// Shows the border around window with `tracking_hwnd` if it exists\npub fn show_border(tracking_hwnd: isize) {\n    std::thread::spawn(move || {\n        if let Some(border_info) = window_border(tracking_hwnd) {\n            WindowsApi::restore_window(border_info.border_hwnd);\n        }\n    });\n}\n\n/// Hides the border around window with `tracking_hwnd` if it exists, unless the border kind is a\n/// `Stack` border.\npub fn hide_border(tracking_hwnd: isize) {\n    std::thread::spawn(move || {\n        if let Some(border_info) = window_border(tracking_hwnd) {\n            WindowsApi::hide_window(border_info.border_hwnd);\n        }\n    });\n}\n\n#[derive(Debug, Copy, Clone, Display, Serialize, Deserialize, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Z Order (https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowpos)\npub enum ZOrder {\n    /// HWND_TOP\n    ///\n    /// Places the window at the top of the Z order.\n    Top,\n    /// HWND_NOTOPMOST\n    ///\n    /// Places the window above all non-topmost windows (that is, behind all topmost windows).\n    /// This flag has no effect if the window is already a non-topmost window.\n    NoTopMost,\n    /// HWND_BOTTOM\n    ///\n    /// Places the window at the bottom of the Z order. If the hWnd parameter identifies a topmost window,\n    /// the window loses its topmost status and is placed at the bottom of all other windows.\n    Bottom,\n    /// HWND_TOPMOST\n    ///\n    /// Places the window above all non-topmost windows.\n    /// The window maintains its topmost position even when it is deactivated.\n    TopMost,\n}\n\nimpl From<ZOrder> for isize {\n    fn from(val: ZOrder) -> Self {\n        match val {\n            ZOrder::Top => 0,\n            ZOrder::NoTopMost => -2,\n            ZOrder::Bottom => 1,\n            ZOrder::TopMost => -1,\n        }\n    }\n}\n"
  },
  {
    "path": "komorebi/src/com/interfaces.rs",
    "content": "// This code is largely taken verbatim from this repository: https://github.com/Ciantic/AltTabAccessor\n// which the author Jari Pennanen (Ciantic) has kindly made available with the MIT license, available\n// in full here: https://github.com/Ciantic/AltTabAccessor/blob/main/LICENSE.txt\n\n#![allow(clippy::use_self)]\n\nuse std::ffi::c_void;\nuse std::ops::Deref;\nuse windows::Win32::Foundation::HWND;\nuse windows::Win32::Foundation::RECT;\nuse windows::Win32::Foundation::SIZE;\nuse windows::Win32::UI::Shell::Common::IObjectArray;\nuse windows::core::GUID;\nuse windows::core::HRESULT;\nuse windows::core::HSTRING;\nuse windows::core::IUnknown;\nuse windows::core::IUnknown_Vtbl;\nuse windows::core::PCWSTR;\nuse windows::core::PWSTR;\nuse windows_core::BOOL;\n\ntype DesktopID = GUID;\n\n// Idea here is that the cloned ComIn instance lifetime is within the original ComIn instance lifetime\n#[repr(transparent)]\npub struct ComIn<'a, T> {\n    data: T,\n    _phantom: std::marker::PhantomData<&'a T>,\n}\n\nimpl<'a, T: Clone> ComIn<'a, T> {\n    pub fn new(t: &'a T) -> Self {\n        Self {\n            data: t.clone(),\n            _phantom: std::marker::PhantomData,\n        }\n    }\n\n    pub const unsafe fn unsafe_new_no_clone(t: T) -> Self {\n        Self {\n            data: t,\n            _phantom: std::marker::PhantomData,\n        }\n    }\n}\n\nimpl<T> Deref for ComIn<'_, T> {\n    type Target = T;\n    fn deref(&self) -> &Self::Target {\n        &self.data\n    }\n}\n\n#[allow(non_upper_case_globals)]\npub const CLSID_ImmersiveShell: GUID = GUID {\n    data1: 0xC2F0_3A33,\n    data2: 0x21F5,\n    data3: 0x47FA,\n    data4: [0xB4, 0xBB, 0x15, 0x63, 0x62, 0xA2, 0xF2, 0x39],\n};\n\n#[allow(clippy::upper_case_acronyms)]\ntype DWORD = u32;\n#[allow(clippy::upper_case_acronyms)]\ntype INT = i32;\n#[allow(clippy::upper_case_acronyms)]\ntype LPVOID = *mut c_void;\n#[allow(clippy::upper_case_acronyms)]\ntype UINT = u32;\n#[allow(clippy::upper_case_acronyms)]\ntype ULONG = u32;\n#[allow(clippy::upper_case_acronyms)]\ntype ULONGLONG = u64;\n\ntype IAsyncCallback = UINT;\ntype IImmersiveMonitor = UINT;\ntype IApplicationViewOperation = UINT;\ntype IApplicationViewPosition = UINT;\ntype IImmersiveApplication = UINT;\ntype IApplicationViewChangeListener = UINT;\n\n#[allow(non_camel_case_types)]\ntype APPLICATION_VIEW_COMPATIBILITY_POLICY = UINT;\n#[allow(non_camel_case_types)]\ntype APPLICATION_VIEW_CLOAK_TYPE = UINT;\n\n#[windows_interface::interface(\"6D5140C1-7436-11CE-8034-00AA006009FA\")]\npub unsafe trait IServiceProvider: IUnknown {\n    pub unsafe fn query_service(\n        &self,\n        guid_service: *const GUID,\n        riid: *const GUID,\n        ppv_object: *mut *mut c_void,\n    ) -> HRESULT;\n}\n\n#[windows_interface::interface(\"372E1D3B-38D3-42E4-A15B-8AB2B178F513\")]\npub unsafe trait IApplicationView: IUnknown {\n    /* IInspecateble */\n    pub unsafe fn get_iids(\n        &self,\n        out_iid_count: *mut ULONG,\n        out_opt_iid_array_ptr: *mut *mut GUID,\n    ) -> HRESULT;\n    pub unsafe fn get_runtime_class_name(&self, out_opt_class_name: *mut HSTRING) -> HRESULT;\n    pub unsafe fn get_trust_level(&self, ptr_trust_level: LPVOID) -> HRESULT;\n\n    /* IApplicationView methods */\n    pub unsafe fn set_focus(&self) -> HRESULT;\n    pub unsafe fn switch_to(&self) -> HRESULT;\n\n    pub unsafe fn try_invoke_back(&self, ptr_async_callback: IAsyncCallback) -> HRESULT;\n    pub unsafe fn get_thumbnail_window(&self, out_hwnd: *mut HWND) -> HRESULT;\n    pub unsafe fn get_monitor(&self, out_monitors: *mut *mut IImmersiveMonitor) -> HRESULT;\n    pub unsafe fn get_visibility(&self, out_int: LPVOID) -> HRESULT;\n    pub unsafe fn set_cloak(\n        &self,\n        application_view_cloak_type: APPLICATION_VIEW_CLOAK_TYPE,\n        unknown: INT,\n    ) -> HRESULT;\n    pub unsafe fn get_position(\n        &self,\n        unknowniid: *const GUID,\n        unknown_array_ptr: LPVOID,\n    ) -> HRESULT;\n    pub unsafe fn set_position(&self, view_position: *mut IApplicationViewPosition) -> HRESULT;\n    pub unsafe fn insert_after_window(&self, window: HWND) -> HRESULT;\n    pub unsafe fn get_extended_frame_position(&self, rect: *mut RECT) -> HRESULT;\n    pub unsafe fn get_app_user_model_id(&self, id: *mut PWSTR) -> HRESULT; // Proc17\n    pub unsafe fn set_app_user_model_id(&self, id: PCWSTR) -> HRESULT;\n    pub unsafe fn is_equal_by_app_user_model_id(&self, id: PCWSTR, out_result: *mut INT)\n    -> HRESULT;\n\n    /*** IApplicationView methods ***/\n    pub unsafe fn get_view_state(&self, out_state: *mut UINT) -> HRESULT; // Proc20\n    pub unsafe fn set_view_state(&self, state: UINT) -> HRESULT; // Proc21\n    pub unsafe fn get_neediness(&self, out_neediness: *mut INT) -> HRESULT; // Proc22\n    pub unsafe fn get_last_activation_timestamp(&self, out_timestamp: *mut ULONGLONG) -> HRESULT;\n    pub unsafe fn set_last_activation_timestamp(&self, timestamp: ULONGLONG) -> HRESULT;\n    pub unsafe fn get_virtual_desktop_id(&self, out_desktop_guid: *mut DesktopID) -> HRESULT;\n    pub unsafe fn set_virtual_desktop_id(&self, desktop_guid: *const DesktopID) -> HRESULT;\n    pub unsafe fn get_show_in_switchers(&self, out_show: *mut INT) -> HRESULT;\n    pub unsafe fn set_show_in_switchers(&self, show: INT) -> HRESULT;\n    pub unsafe fn get_scale_factor(&self, out_scale_factor: *mut INT) -> HRESULT;\n    pub unsafe fn can_receive_input(&self, out_can: *mut BOOL) -> HRESULT;\n    pub unsafe fn get_compatibility_policy_type(\n        &self,\n        out_policy_type: *mut APPLICATION_VIEW_COMPATIBILITY_POLICY,\n    ) -> HRESULT;\n    pub unsafe fn set_compatibility_policy_type(\n        &self,\n        policy_type: APPLICATION_VIEW_COMPATIBILITY_POLICY,\n    ) -> HRESULT;\n\n    pub unsafe fn get_size_constraints(\n        &self,\n        monitor: *mut IImmersiveMonitor,\n        out_size1: *mut SIZE,\n        out_size2: *mut SIZE,\n    ) -> HRESULT;\n    pub unsafe fn get_size_constraints_for_dpi(\n        &self,\n        dpi: UINT,\n        out_size1: *mut SIZE,\n        out_size2: *mut SIZE,\n    ) -> HRESULT;\n    pub unsafe fn set_size_constraints_for_dpi(\n        &self,\n        dpi: *const UINT,\n        size1: *const SIZE,\n        size2: *const SIZE,\n    ) -> HRESULT;\n\n    pub unsafe fn on_min_size_preferences_updated(&self, window: HWND) -> HRESULT;\n    pub unsafe fn apply_operation(&self, operation: *mut IApplicationViewOperation) -> HRESULT;\n    pub unsafe fn is_tray(&self, out_is: *mut BOOL) -> HRESULT;\n    pub unsafe fn is_in_high_zorder_band(&self, out_is: *mut BOOL) -> HRESULT;\n    pub unsafe fn is_splash_screen_presented(&self, out_is: *mut BOOL) -> HRESULT;\n    pub unsafe fn flash(&self) -> HRESULT;\n    pub unsafe fn get_root_switchable_owner(&self, app_view: *mut IApplicationView) -> HRESULT; // proc45\n    pub unsafe fn enumerate_ownership_tree(&self, objects: *mut IObjectArray) -> HRESULT; // proc46\n\n    pub unsafe fn get_enterprise_id(&self, out_id: *mut PWSTR) -> HRESULT; // proc47\n    pub unsafe fn is_mirrored(&self, out_is: *mut BOOL) -> HRESULT; //\n\n    pub unsafe fn unknown1(&self, arg: *mut INT) -> HRESULT;\n    pub unsafe fn unknown2(&self, arg: *mut INT) -> HRESULT;\n    pub unsafe fn unknown3(&self, arg: *mut INT) -> HRESULT;\n    pub unsafe fn unknown4(&self, arg: INT) -> HRESULT;\n    pub unsafe fn unknown5(&self, arg: *mut INT) -> HRESULT;\n    pub unsafe fn unknown6(&self, arg: INT) -> HRESULT;\n    pub unsafe fn unknown7(&self) -> HRESULT;\n    pub unsafe fn unknown8(&self, arg: *mut INT) -> HRESULT;\n    pub unsafe fn unknown9(&self, arg: INT) -> HRESULT;\n    pub unsafe fn unknown10(&self, arg: INT, arg2: INT) -> HRESULT;\n    pub unsafe fn unknown11(&self, arg: INT) -> HRESULT;\n    pub unsafe fn unknown12(&self, arg: *mut SIZE) -> HRESULT;\n}\n\n#[windows_interface::interface(\"1841c6d7-4f9d-42c0-af41-8747538f10e5\")]\npub unsafe trait IApplicationViewCollection: IUnknown {\n    pub unsafe fn get_views(&self, out_views: *mut IObjectArray) -> HRESULT;\n\n    pub unsafe fn get_views_by_zorder(&self, out_views: *mut IObjectArray) -> HRESULT;\n\n    pub unsafe fn get_views_by_app_user_model_id(\n        &self,\n        id: PCWSTR,\n        out_views: *mut IObjectArray,\n    ) -> HRESULT;\n\n    pub unsafe fn get_view_for_hwnd(\n        &self,\n        window: HWND,\n        out_view: *mut Option<IApplicationView>,\n    ) -> HRESULT;\n\n    pub unsafe fn get_view_for_application(\n        &self,\n        app: ComIn<IImmersiveApplication>,\n        out_view: *mut IApplicationView,\n    ) -> HRESULT;\n\n    pub unsafe fn get_view_for_app_user_model_id(\n        &self,\n        id: PCWSTR,\n        out_view: *mut IApplicationView,\n    ) -> HRESULT;\n\n    pub unsafe fn get_view_in_focus(&self, out_view: *mut IApplicationView) -> HRESULT;\n\n    pub unsafe fn try_get_last_active_visible_view(\n        &self,\n        out_view: *mut IApplicationView,\n    ) -> HRESULT;\n\n    pub unsafe fn refresh_collection(&self) -> HRESULT;\n\n    pub unsafe fn register_for_application_view_changes(\n        &self,\n        listener: ComIn<IApplicationViewChangeListener>,\n        out_id: *mut DWORD,\n    ) -> HRESULT;\n\n    pub unsafe fn unregister_for_application_view_changes(&self, id: DWORD) -> HRESULT;\n}\n"
  },
  {
    "path": "komorebi/src/com/mod.rs",
    "content": "// This code is largely taken verbatim from this repository: https://github.com/Ciantic/AltTabAccessor\n// which the author Jari Pennanen (Ciantic) has kindly made available with the MIT license, available\n// in full here: https://github.com/Ciantic/AltTabAccessor/blob/main/LICENSE.txt\n\nmod interfaces;\n\nuse interfaces::CLSID_ImmersiveShell;\nuse interfaces::IApplicationViewCollection;\nuse interfaces::IServiceProvider;\n\nuse std::ffi::c_void;\n\nuse windows::Win32::Foundation::HWND;\nuse windows::Win32::System::Com::CLSCTX_ALL;\nuse windows::Win32::System::Com::COINIT_MULTITHREADED;\nuse windows::Win32::System::Com::CoCreateInstance;\nuse windows::Win32::System::Com::CoInitializeEx;\nuse windows::Win32::System::Com::CoUninitialize;\nuse windows_core::Interface;\n\nstruct ComInit();\n\nimpl ComInit {\n    pub fn new() -> Self {\n        unsafe {\n            CoInitializeEx(None, COINIT_MULTITHREADED).unwrap();\n        }\n        Self()\n    }\n}\n\nimpl Drop for ComInit {\n    fn drop(&mut self) {\n        unsafe {\n            CoUninitialize();\n        }\n    }\n}\n\nthread_local! {\n    static COM_INIT: ComInit = ComInit::new();\n}\n\nfn get_iservice_provider() -> IServiceProvider {\n    COM_INIT.with(|_| unsafe { CoCreateInstance(&CLSID_ImmersiveShell, None, CLSCTX_ALL).unwrap() })\n}\n\nfn get_iapplication_view_collection(provider: &IServiceProvider) -> IApplicationViewCollection {\n    COM_INIT.with(|_| {\n        let mut obj = std::ptr::null_mut::<c_void>();\n        unsafe {\n            provider\n                .query_service(\n                    &IApplicationViewCollection::IID,\n                    &IApplicationViewCollection::IID,\n                    &mut obj,\n                )\n                .unwrap();\n        }\n\n        assert!(!obj.is_null());\n\n        unsafe { IApplicationViewCollection::from_raw(obj) }\n    })\n}\n\n#[unsafe(no_mangle)]\npub extern \"C\" fn SetCloak(hwnd: HWND, cloak_type: u32, flags: i32) {\n    COM_INIT.with(|_| {\n        let provider = get_iservice_provider();\n        let view_collection = get_iapplication_view_collection(&provider);\n        let mut view = None;\n        unsafe {\n            if view_collection.get_view_for_hwnd(hwnd, &mut view).is_err() {\n                tracing::error!(\n                    \"could not get view for hwnd {} due to os error: {}\",\n                    hwnd.0 as isize,\n                    std::io::Error::last_os_error()\n                );\n            }\n        };\n\n        view.map_or_else(\n            || {\n                tracing::error!(\"no view was found for {}\", hwnd.0 as isize);\n            },\n            |view| {\n                unsafe {\n                    if view.set_cloak(cloak_type, flags).is_err() {\n                        tracing::error!(\n                            \"could not change the cloaking status for hwnd {} due to os error: {}\",\n                            hwnd.0 as isize,\n                            std::io::Error::last_os_error()\n                        );\n                    }\n                };\n            },\n        );\n    });\n}\n"
  },
  {
    "path": "komorebi/src/container.rs",
    "content": "use std::collections::VecDeque;\n\nuse nanoid::nanoid;\nuse serde::Deserialize;\nuse serde::Serialize;\n\nuse crate::Lockable;\nuse crate::ring::Ring;\nuse crate::window::Window;\n\n#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\npub struct Container {\n    pub id: String,\n    #[serde(default)]\n    pub locked: bool,\n    windows: Ring<Window>,\n}\n\nimpl_ring_elements!(Container, Window);\n\nimpl Default for Container {\n    fn default() -> Self {\n        Self {\n            id: nanoid!(),\n            locked: false,\n            windows: Ring::default(),\n        }\n    }\n}\n\nimpl Lockable for Container {\n    fn locked(&self) -> bool {\n        self.locked\n    }\n\n    fn set_locked(&mut self, locked: bool) -> &mut Self {\n        self.locked = locked;\n        self\n    }\n}\n\nimpl Container {\n    pub fn preselect() -> Self {\n        Self {\n            id: \"PRESELECT\".to_string(),\n            locked: false,\n            windows: Default::default(),\n        }\n    }\n\n    pub fn is_preselect(&self) -> bool {\n        self.id == \"PRESELECT\"\n    }\n\n    pub fn hide(&self, omit: Option<isize>) {\n        for window in self.windows().iter().rev() {\n            let mut should_hide = omit.is_none();\n\n            if !should_hide\n                && let Some(omit) = omit\n                && omit != window.hwnd\n            {\n                should_hide = true\n            }\n\n            if should_hide {\n                window.hide();\n            }\n        }\n    }\n\n    pub fn restore(&self) {\n        if let Some(window) = self.focused_window() {\n            window.restore();\n        }\n    }\n\n    /// Hides the unfocused windows of the container and restores the focused one. This function\n    /// is used to make sure we update the window that should be shown on a stack. If the container\n    /// isn't a stack this function won't change anything.\n    pub fn load_focused_window(&mut self) {\n        let focused_idx = self.focused_window_idx();\n\n        for (i, window) in self.windows_mut().iter_mut().enumerate() {\n            if i == focused_idx {\n                window.restore_with_border(false);\n            } else {\n                window.hide_with_border(false);\n            }\n        }\n    }\n\n    pub fn hwnd_from_exe(&self, exe: &str) -> Option<isize> {\n        for window in self.windows() {\n            if let Ok(window_exe) = window.exe()\n                && exe == window_exe\n            {\n                return Option::from(window.hwnd);\n            }\n        }\n\n        None\n    }\n\n    pub fn idx_from_exe(&self, exe: &str) -> Option<usize> {\n        for (idx, window) in self.windows().iter().enumerate() {\n            if let Ok(window_exe) = window.exe()\n                && exe == window_exe\n            {\n                return Option::from(idx);\n            }\n        }\n\n        None\n    }\n\n    pub fn contains_window(&self, hwnd: isize) -> bool {\n        for window in self.windows() {\n            if window.hwnd == hwnd {\n                return true;\n            }\n        }\n\n        false\n    }\n\n    pub fn idx_for_window(&self, hwnd: isize) -> Option<usize> {\n        for (i, window) in self.windows().iter().enumerate() {\n            if window.hwnd == hwnd {\n                return Option::from(i);\n            }\n        }\n\n        None\n    }\n\n    pub fn remove_window_by_idx(&mut self, idx: usize) -> Option<Window> {\n        let window = self.windows_mut().remove(idx);\n        self.focus_window(idx.saturating_sub(1));\n        window\n    }\n\n    pub fn remove_focused_window(&mut self) -> Option<Window> {\n        let focused_idx = self.focused_window_idx();\n        self.remove_window_by_idx(focused_idx)\n    }\n\n    pub fn add_window(&mut self, window: Window) {\n        self.windows_mut().push_back(window);\n        self.focus_window(self.windows().len().saturating_sub(1));\n        let focused_window_idx = self.focused_window_idx();\n\n        for (i, window) in self.windows().iter().enumerate() {\n            if i != focused_window_idx {\n                window.hide();\n            }\n        }\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn focus_window(&mut self, idx: usize) {\n        tracing::info!(\"focusing window\");\n        self.windows.focus(idx);\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use serde_json;\n\n    #[test]\n    fn test_contains_window() {\n        let mut container = Container::default();\n\n        for i in 0..3 {\n            container.add_window(Window::from(i));\n        }\n\n        // Should return true for existing windows\n        assert!(container.contains_window(1));\n        assert_eq!(container.idx_for_window(1), Some(1));\n\n        // Should return false since window 4 doesn't exist\n        assert!(!container.contains_window(4));\n        assert_eq!(container.idx_for_window(4), None);\n    }\n\n    #[test]\n    fn test_remove_window_by_idx() {\n        let mut container = Container::default();\n\n        for i in 0..3 {\n            container.add_window(Window::from(i));\n        }\n\n        // Remove window 1\n        container.remove_window_by_idx(1);\n\n        // Should only have 2 windows left\n        assert_eq!(container.windows().len(), 2);\n\n        // Should return false since window 1 was removed\n        assert!(!container.contains_window(1));\n    }\n\n    #[test]\n    fn test_remove_focused_window() {\n        let mut container = Container::default();\n\n        for i in 0..3 {\n            container.add_window(Window::from(i));\n        }\n\n        // Should be focused on the last created window\n        assert_eq!(container.focused_window_idx(), 2);\n\n        // Remove the focused window\n        container.remove_focused_window();\n\n        // Should be focused on the window before the removed one\n        assert_eq!(container.focused_window_idx(), 1);\n\n        // Should only have 2 windows left\n        assert_eq!(container.windows().len(), 2);\n    }\n\n    #[test]\n    fn test_add_window() {\n        let mut container = Container::default();\n\n        container.add_window(Window::from(1));\n\n        assert_eq!(container.windows().len(), 1);\n        assert_eq!(container.focused_window_idx(), 0);\n        assert!(container.contains_window(1));\n    }\n\n    #[test]\n    fn test_focus_window() {\n        let mut container = Container::default();\n\n        for i in 0..3 {\n            container.add_window(Window::from(i));\n        }\n\n        // Should focus on the last created window\n        assert_eq!(container.focused_window_idx(), 2);\n\n        // focus on the window at index 1\n        container.focus_window(1);\n\n        // Should be focused on window 1\n        assert_eq!(container.focused_window_idx(), 1);\n\n        // focus on the window at index 0\n        container.focus_window(0);\n\n        // Should be focused on window 0\n        assert_eq!(container.focused_window_idx(), 0);\n    }\n\n    #[test]\n    fn test_idx_for_window() {\n        let mut container = Container::default();\n\n        for i in 0..3 {\n            container.add_window(Window::from(i));\n        }\n\n        // Should return the index of the window\n        assert_eq!(container.idx_for_window(1), Some(1));\n\n        // Should return None since window 4 doesn't exist\n        assert_eq!(container.idx_for_window(4), None);\n    }\n\n    #[test]\n    fn deserializes_with_missing_locked_field_defaults_to_false() {\n        let json = r#\"{\n            \"id\": \"test-1\",\n            \"windows\": { \"elements\": [], \"focused\": 0 }\n        }\"#;\n        let container: Container = serde_json::from_str(json).expect(\"Should deserialize\");\n\n        assert!(!container.locked);\n        assert_eq!(container.id, \"test-1\");\n        assert!(container.windows().is_empty());\n\n        let json = r#\"{\n            \"id\": \"test-2\",\n            \"windows\": { \"elements\": [ { \"hwnd\": 5 }, { \"hwnd\": 9 } ], \"focused\": 1 }\n        }\"#;\n        let container: Container = serde_json::from_str(json).unwrap();\n        assert_eq!(container.id, \"test-2\");\n        assert!(!container.locked);\n        assert_eq!(container.windows(), &[Window::from(5), Window::from(9)]);\n        assert_eq!(container.focused_window_idx(), 1);\n    }\n\n    #[test]\n    fn serializes_and_deserializes() {\n        let mut container = Container::default();\n        container.set_locked(true);\n\n        let serialized = serde_json::to_string(&container).expect(\"Should serialize\");\n        let deserialized: Container =\n            serde_json::from_str(&serialized).expect(\"Should deserialize\");\n\n        assert!(deserialized.locked);\n        assert_eq!(deserialized.id, container.id);\n    }\n}\n"
  },
  {
    "path": "komorebi/src/core/animation.rs",
    "content": "use clap::ValueEnum;\n\nuse serde::Deserialize;\nuse serde::Serialize;\nuse serde::ser::SerializeSeq;\nuse strum::Display;\nuse strum::EnumString;\n\n#[derive(Copy, Clone, Debug, Display, EnumString, ValueEnum, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Mathematical function which describes the rate at which a value changes\npub enum AnimationStyle {\n    /// Linear\n    Linear,\n    /// Ease in sine\n    EaseInSine,\n    /// Ease out sine\n    EaseOutSine,\n    /// Ease in out sine\n    EaseInOutSine,\n    /// Ease in quad\n    EaseInQuad,\n    /// Ease out quad\n    EaseOutQuad,\n    /// Ease in out quad\n    EaseInOutQuad,\n    /// Ease in cubic\n    EaseInCubic,\n    /// Ease out cubic\n    EaseOutCubic,\n    /// Ease in out cubic\n    EaseInOutCubic,\n    /// Ease in quart\n    EaseInQuart,\n    /// Ease out quart\n    EaseOutQuart,\n    /// Ease in out quart\n    EaseInOutQuart,\n    /// Ease in quint\n    EaseInQuint,\n    /// Ease out quint\n    EaseOutQuint,\n    /// Ease in out quint\n    EaseInOutQuint,\n    /// Ease in expo\n    EaseInExpo,\n    /// Ease out expo\n    EaseOutExpo,\n    /// Ease in out expo\n    EaseInOutExpo,\n    /// Ease in circ\n    EaseInCirc,\n    /// Ease out circ\n    EaseOutCirc,\n    /// Ease in out circ\n    EaseInOutCirc,\n    /// Ease in back\n    EaseInBack,\n    /// Ease out back\n    EaseOutBack,\n    /// Ease in out back\n    EaseInOutBack,\n    /// Ease in elastic\n    EaseInElastic,\n    /// Ease out elastic\n    EaseOutElastic,\n    /// Ease in out elastic\n    EaseInOutElastic,\n    /// Ease in bounce\n    EaseInBounce,\n    /// Ease out bounce\n    EaseOutBounce,\n    /// Ease in out bounce\n    EaseInOutBounce,\n    #[cfg_attr(feature = \"schemars\", schemars(title = \"CubicBezier\"))]\n    #[value(skip)]\n    /// Custom Cubic Bézier function\n    CubicBezier(f64, f64, f64, f64),\n}\n\n// Custom serde implementation\nimpl<'de> Deserialize<'de> for AnimationStyle {\n    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n    where\n        D: serde::Deserializer<'de>,\n    {\n        struct AnimationStyleVisitor;\n\n        impl<'de> serde::de::Visitor<'de> for AnimationStyleVisitor {\n            type Value = AnimationStyle;\n\n            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {\n                formatter.write_str(\"a string or an array of four f64 values\")\n            }\n\n            // Handle string variants (e.g., \"EaseInOutExpo\")\n            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>\n            where\n                E: serde::de::Error,\n            {\n                value.parse().map_err(|_| E::unknown_variant(value, &[]))\n            }\n\n            // Handle CubicBezier array (e.g., [0.32, 0.72, 0.0, 1.0])\n            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>\n            where\n                A: serde::de::SeqAccess<'de>,\n            {\n                let x1 = seq\n                    .next_element()?\n                    .ok_or_else(|| serde::de::Error::invalid_length(0, &self))?;\n                let y1 = seq\n                    .next_element()?\n                    .ok_or_else(|| serde::de::Error::invalid_length(1, &self))?;\n                let x2 = seq\n                    .next_element()?\n                    .ok_or_else(|| serde::de::Error::invalid_length(2, &self))?;\n                let y2 = seq\n                    .next_element()?\n                    .ok_or_else(|| serde::de::Error::invalid_length(3, &self))?;\n\n                // Ensure no extra elements\n                if seq.next_element::<serde::de::IgnoredAny>()?.is_some() {\n                    return Err(serde::de::Error::invalid_length(5, &self));\n                }\n\n                Ok(AnimationStyle::CubicBezier(x1, y1, x2, y2))\n            }\n        }\n\n        deserializer.deserialize_any(AnimationStyleVisitor)\n    }\n}\n\nimpl Serialize for AnimationStyle {\n    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>\n    where\n        S: serde::Serializer,\n    {\n        match self {\n            // Serialize CubicBezier as an array\n            AnimationStyle::CubicBezier(x1, y1, x2, y2) => {\n                let mut seq = serializer.serialize_seq(Some(4))?;\n                seq.serialize_element(x1)?;\n                seq.serialize_element(y1)?;\n                seq.serialize_element(x2)?;\n                seq.serialize_element(y2)?;\n                seq.end()\n            }\n            // Serialize all other variants as strings\n            _ => serializer.serialize_str(&self.to_string()),\n        }\n    }\n}\n"
  },
  {
    "path": "komorebi/src/core/asc.rs",
    "content": "use crate::config_generation::ApplicationConfiguration;\nuse crate::config_generation::ApplicationOptions;\nuse crate::config_generation::MatchingRule;\nuse color_eyre::eyre;\nuse serde::Deserialize;\nuse serde::Serialize;\nuse std::collections::BTreeMap;\nuse std::ops::Deref;\nuse std::ops::DerefMut;\nuse std::path::PathBuf;\n\n#[derive(Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\npub struct ApplicationSpecificConfiguration(pub BTreeMap<String, AscApplicationRulesOrSchema>);\n\n#[derive(Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n#[serde(untagged)]\npub enum AscApplicationRulesOrSchema {\n    AscApplicationRules(AscApplicationRules),\n    Schema(String),\n}\n\nimpl Deref for ApplicationSpecificConfiguration {\n    type Target = BTreeMap<String, AscApplicationRulesOrSchema>;\n\n    fn deref(&self) -> &Self::Target {\n        &self.0\n    }\n}\n\nimpl DerefMut for ApplicationSpecificConfiguration {\n    fn deref_mut(&mut self) -> &mut Self::Target {\n        &mut self.0\n    }\n}\n\nimpl ApplicationSpecificConfiguration {\n    pub fn load(pathbuf: &PathBuf) -> eyre::Result<Self> {\n        let content = std::fs::read_to_string(pathbuf)?;\n        Ok(serde_json::from_str(&content)?)\n    }\n\n    pub fn format(pathbuf: &PathBuf) -> eyre::Result<String> {\n        Ok(serde_json::to_string_pretty(&Self::load(pathbuf)?)?)\n    }\n}\n\n/// Rules that determine how an application is handled\n#[derive(Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\npub struct AscApplicationRules {\n    /// Rules to ignore specific windows\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub ignore: Option<Vec<MatchingRule>>,\n    /// Rules to forcibly manage specific windows\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub manage: Option<Vec<MatchingRule>>,\n    /// Rules to manage specific windows as floating windows\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub floating: Option<Vec<MatchingRule>>,\n    /// Rules to ignore specific windows from the transparency feature\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub transparency_ignore: Option<Vec<MatchingRule>>,\n    /// Rules to identify applications which minimize to the tray or have multiple windows\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub tray_and_multi_window: Option<Vec<MatchingRule>>,\n    /// Rules to identify applications which have the `WS_EX_LAYERED` Extended Window Style\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub layered: Option<Vec<MatchingRule>>,\n    /// Rules to identify applications which send the `EVENT_OBJECT_NAMECHANGE` event on launch\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub object_name_change: Option<Vec<MatchingRule>>,\n    /// Rules to identify applications which are slow to send initial event notifications\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub slow_application: Option<Vec<MatchingRule>>,\n}\n\nimpl From<Vec<ApplicationConfiguration>> for ApplicationSpecificConfiguration {\n    fn from(value: Vec<ApplicationConfiguration>) -> Self {\n        let mut map = BTreeMap::new();\n\n        for entry in &value {\n            let key = entry.name.clone();\n            let mut rules = AscApplicationRules {\n                ignore: None,\n                manage: None,\n                floating: None,\n                transparency_ignore: None,\n                tray_and_multi_window: None,\n                layered: None,\n                object_name_change: None,\n                slow_application: None,\n            };\n\n            rules.ignore = entry.ignore_identifiers.clone();\n\n            if let Some(options) = &entry.options {\n                for opt in options {\n                    match opt {\n                        ApplicationOptions::ObjectNameChange => {\n                            rules.object_name_change =\n                                Some(vec![MatchingRule::Simple(entry.identifier.clone())]);\n                        }\n                        ApplicationOptions::Layered => {\n                            rules.layered =\n                                Some(vec![MatchingRule::Simple(entry.identifier.clone())]);\n                        }\n                        ApplicationOptions::TrayAndMultiWindow => {\n                            rules.tray_and_multi_window =\n                                Some(vec![MatchingRule::Simple(entry.identifier.clone())]);\n                        }\n                        ApplicationOptions::Force => {\n                            rules.manage =\n                                Some(vec![MatchingRule::Simple(entry.identifier.clone())]);\n                        }\n                        ApplicationOptions::BorderOverflow => {}\n                    }\n                }\n            }\n\n            if rules.ignore.is_some()\n                || rules.manage.is_some()\n                || rules.floating.is_some()\n                || rules.transparency_ignore.is_some()\n                || rules.tray_and_multi_window.is_some()\n                || rules.layered.is_some()\n                || rules.object_name_change.is_some()\n                || rules.slow_application.is_some()\n            {\n                map.insert(key, AscApplicationRulesOrSchema::AscApplicationRules(rules));\n            }\n        }\n\n        Self(map)\n    }\n}\n"
  },
  {
    "path": "komorebi/src/core/config_generation.rs",
    "content": "use clap::ValueEnum;\nuse color_eyre::eyre;\nuse serde::Deserialize;\nuse serde::Serialize;\nuse strum::Display;\nuse strum::EnumString;\n\nuse super::ApplicationIdentifier;\n\n#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n#[strum(serialize_all = \"snake_case\")]\n#[serde(rename_all = \"snake_case\")]\npub enum ApplicationOptions {\n    ObjectNameChange,\n    Layered,\n    TrayAndMultiWindow,\n    Force,\n    BorderOverflow,\n}\n\nimpl ApplicationOptions {\n    #[must_use]\n    pub fn raw_cfgen(&self, kind: &ApplicationIdentifier, id: &str) -> String {\n        match self {\n            ApplicationOptions::ObjectNameChange => {\n                format!(\"komorebic.exe identify-object-name-change-application {kind} \\\"{id}\\\"\",)\n            }\n            ApplicationOptions::Layered => {\n                format!(\"komorebic.exe identify-layered-application {kind} \\\"{id}\\\"\",)\n            }\n            ApplicationOptions::TrayAndMultiWindow => {\n                format!(\"komorebic.exe identify-tray-application {kind} \\\"{id}\\\"\",)\n            }\n            ApplicationOptions::Force => {\n                format!(\"komorebic.exe manage-rule {kind} \\\"{id}\\\"\")\n            }\n            ApplicationOptions::BorderOverflow => {\n                unreachable!(\"deprecated\");\n            }\n        }\n    }\n\n    #[must_use]\n    pub fn cfgen(&self, kind: &ApplicationIdentifier, id: &str) -> String {\n        format!(\n            \"RunWait('{}', , \\\"Hide\\\")\",\n            ApplicationOptions::raw_cfgen(self, kind, id)\n        )\n    }\n}\n\n#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n#[serde(untagged)]\n/// Rule for matching applications\npub enum MatchingRule {\n    /// Simple matching rule which must evaluate to true\n    Simple(IdWithIdentifier),\n    /// Composite matching rule where all conditions must evaluate to true\n    Composite(Vec<IdWithIdentifier>),\n}\n\n#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Rule for assigning applications to a workspace\npub struct WorkspaceMatchingRule {\n    /// Target monitor index\n    pub monitor_index: usize,\n    /// Target workspace index\n    pub workspace_index: usize,\n    /// Matching rule for the application\n    pub matching_rule: MatchingRule,\n    /// Whether to apply the rule only when the application is initially launched\n    pub initial_only: bool,\n}\n\n#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Rule for matching applications\npub struct IdWithIdentifier {\n    /// Kind of identifier to target\n    pub kind: ApplicationIdentifier,\n    /// Target identifier\n    pub id: String,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    /// Matching strategy to use\n    pub matching_strategy: Option<MatchingStrategy>,\n}\n\n#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Display)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Strategy for matching identifiers\npub enum MatchingStrategy {\n    /// Should not be used, only kept for backward compatibility\n    Legacy,\n    /// Equals\n    Equals,\n    /// Starts With\n    StartsWith,\n    /// Ends With\n    EndsWith,\n    /// Contains\n    Contains,\n    /// Regex\n    Regex,\n    /// Does not end with\n    DoesNotEndWith,\n    /// Does not start with\n    DoesNotStartWith,\n    /// Does not equal\n    DoesNotEqual,\n    /// Does not contain\n    DoesNotContain,\n}\n\n#[derive(Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\npub struct IdWithIdentifierAndComment {\n    pub kind: ApplicationIdentifier,\n    pub id: String,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub comment: Option<String>,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub matching_strategy: Option<MatchingStrategy>,\n}\n\nimpl From<IdWithIdentifierAndComment> for IdWithIdentifier {\n    fn from(value: IdWithIdentifierAndComment) -> Self {\n        Self {\n            kind: value.kind,\n            id: value.id.clone(),\n            matching_strategy: value.matching_strategy,\n        }\n    }\n}\n\n#[derive(Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\npub struct ApplicationConfiguration {\n    pub name: String,\n    pub identifier: IdWithIdentifier,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub options: Option<Vec<ApplicationOptions>>,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[serde(alias = \"float_identifiers\")]\n    pub ignore_identifiers: Option<Vec<MatchingRule>>,\n}\n\nimpl ApplicationConfiguration {\n    pub fn populate_default_matching_strategies(&mut self) {\n        if self.identifier.matching_strategy.is_none() {\n            match self.identifier.kind {\n                ApplicationIdentifier::Exe | ApplicationIdentifier::Path => {\n                    self.identifier.matching_strategy = Option::from(MatchingStrategy::Equals);\n                }\n                ApplicationIdentifier::Class | ApplicationIdentifier::Title => {}\n            }\n        }\n    }\n}\n\n#[derive(Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\npub struct ApplicationConfigurationGenerator;\n\nimpl ApplicationConfigurationGenerator {\n    pub fn load(content: &str) -> eyre::Result<Vec<ApplicationConfiguration>> {\n        Ok(serde_yaml::from_str(content)?)\n    }\n\n    pub fn format(content: &str) -> eyre::Result<String> {\n        let mut cfgen = Self::load(content)?;\n        for cfg in &mut cfgen {\n            cfg.populate_default_matching_strategies();\n        }\n\n        cfgen.sort_by(|a, b| a.name.cmp(&b.name));\n        Ok(serde_yaml::to_string(&cfgen)?)\n    }\n\n    fn merge(\n        base_content: &str,\n        override_content: &str,\n    ) -> eyre::Result<Vec<ApplicationConfiguration>> {\n        let base_cfgen = Self::load(base_content)?;\n        let override_cfgen = Self::load(override_content)?;\n\n        let mut final_cfgen = base_cfgen.clone();\n\n        for entry in override_cfgen {\n            let mut replace_idx = None;\n            for (idx, base_entry) in base_cfgen.iter().enumerate() {\n                if base_entry.name == entry.name {\n                    replace_idx = Option::from(idx);\n                }\n            }\n\n            match replace_idx {\n                None => final_cfgen.push(entry),\n                Some(idx) => final_cfgen[idx] = entry,\n            }\n        }\n\n        Ok(final_cfgen)\n    }\n\n    pub fn generate_pwsh(\n        base_content: &str,\n        override_content: Option<&str>,\n    ) -> eyre::Result<Vec<String>> {\n        let mut cfgen = if let Some(override_content) = override_content {\n            Self::merge(base_content, override_content)?\n        } else {\n            Self::load(base_content)?\n        };\n\n        cfgen.sort_by(|a, b| a.name.cmp(&b.name));\n\n        let mut lines = vec![String::from(\"# Generated by komorebic.exe\"), String::new()];\n\n        let mut ignore_rules = vec![];\n\n        for app in cfgen {\n            lines.push(format!(\"# {}\", app.name));\n            if let Some(options) = app.options {\n                for opt in options {\n                    if matches!(opt, ApplicationOptions::TrayAndMultiWindow) {\n                        lines.push(String::from(\"# If you have disabled minimize/close to tray for this application, you can delete/comment out the next line\"));\n                    }\n\n                    lines.push(opt.raw_cfgen(&app.identifier.kind, &app.identifier.id));\n                }\n            }\n\n            if let Some(ignore_identifiers) = app.ignore_identifiers {\n                for matching_rule in ignore_identifiers {\n                    if let MatchingRule::Simple(float) = matching_rule {\n                        let float_rule =\n                            format!(\"komorebic.exe float-rule {} \\\"{}\\\"\", float.kind, float.id);\n\n                        // Don't want to send duped signals especially as configs get larger\n                        if !ignore_rules.contains(&float_rule) {\n                            ignore_rules.push(float_rule.clone());\n\n                            // if let Some(comment) = float.comment {\n                            //     lines.push(format!(\"# {comment}\"));\n                            // };\n\n                            lines.push(float_rule);\n                        }\n                    }\n                }\n            }\n\n            lines.push(String::new());\n        }\n\n        Ok(lines)\n    }\n\n    pub fn generate_ahk(\n        base_content: &str,\n        override_content: Option<&str>,\n    ) -> eyre::Result<Vec<String>> {\n        let mut cfgen = if let Some(override_content) = override_content {\n            Self::merge(base_content, override_content)?\n        } else {\n            Self::load(base_content)?\n        };\n\n        cfgen.sort_by(|a, b| a.name.cmp(&b.name));\n\n        let mut lines = vec![String::from(\"; Generated by komorebic.exe\"), String::new()];\n\n        let mut ignore_rules = vec![];\n\n        for app in cfgen {\n            lines.push(format!(\"; {}\", app.name));\n            if let Some(options) = app.options {\n                for opt in options {\n                    if matches!(opt, ApplicationOptions::TrayAndMultiWindow) {\n                        lines.push(String::from(\"; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line\"));\n                    }\n\n                    lines.push(opt.cfgen(&app.identifier.kind, &app.identifier.id));\n                }\n            }\n\n            if let Some(ignore_identifiers) = app.ignore_identifiers {\n                for matching_rule in ignore_identifiers {\n                    if let MatchingRule::Simple(float) = matching_rule {\n                        let float_rule = format!(\n                            \"RunWait('komorebic.exe float-rule {} \\\"{}\\\"', , \\\"Hide\\\")\",\n                            float.kind, float.id\n                        );\n\n                        // Don't want to send duped signals especially as configs get larger\n                        if !ignore_rules.contains(&float_rule) {\n                            ignore_rules.push(float_rule.clone());\n\n                            // if let Some(comment) = float.comment {\n                            //     lines.push(format!(\"; {comment}\"));\n                            // };\n\n                            lines.push(float_rule);\n                        }\n                    }\n                }\n            }\n\n            lines.push(String::new());\n        }\n\n        Ok(lines)\n    }\n}\n"
  },
  {
    "path": "komorebi/src/core/mod.rs",
    "content": "#![warn(clippy::all)]\n#![allow(clippy::missing_errors_doc, clippy::use_self, clippy::doc_markdown)]\n#![allow(deprecated)] // allow deprecated variants like HidingBehaviour::Hide to be used in derive macros\n\nuse std::num::NonZeroUsize;\nuse std::path::PathBuf;\nuse std::str::FromStr;\n\nuse clap::ValueEnum;\nuse color_eyre::eyre;\nuse serde::Deserialize;\nuse serde::Serialize;\nuse strum::Display;\nuse strum::EnumString;\n\nuse crate::KomorebiTheme;\nuse crate::animation::prefix::AnimationPrefix;\n\n// Re-export everything from komorebi-layouts\npub use komorebi_layouts::Arrangement;\npub use komorebi_layouts::Axis;\npub use komorebi_layouts::Column;\npub use komorebi_layouts::ColumnSplit;\npub use komorebi_layouts::ColumnSplitWithCapacity;\npub use komorebi_layouts::ColumnWidth;\npub use komorebi_layouts::CustomLayout;\npub use komorebi_layouts::CycleDirection;\npub use komorebi_layouts::DEFAULT_RATIO;\npub use komorebi_layouts::DEFAULT_SECONDARY_RATIO;\npub use komorebi_layouts::DefaultLayout;\npub use komorebi_layouts::Direction;\npub use komorebi_layouts::GridLayoutOptions;\npub use komorebi_layouts::Layout;\npub use komorebi_layouts::LayoutOptions;\npub use komorebi_layouts::MAX_RATIO;\npub use komorebi_layouts::MAX_RATIOS;\npub use komorebi_layouts::MIN_RATIO;\npub use komorebi_layouts::OperationDirection;\npub use komorebi_layouts::Rect;\npub use komorebi_layouts::ScrollingLayoutOptions;\npub use komorebi_layouts::Sizing;\npub use komorebi_layouts::validate_ratios;\n\n// Local modules and exports\npub use animation::AnimationStyle;\npub use pathext::PathExt;\npub use pathext::ResolvedPathBuf;\npub use pathext::replace_env_in_path;\npub use pathext::resolve_option_hashmap_usize_path;\n\npub mod animation;\npub mod asc;\npub mod config_generation;\npub mod pathext;\n\n// serde_as must be before derive\n#[serde_with::serde_as]\n#[derive(Clone, Debug, Serialize, Deserialize, Display)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n#[serde(tag = \"type\", content = \"content\")]\npub enum SocketMessage {\n    // Window / Container Commands\n    FocusWindow(OperationDirection),\n    MoveWindow(OperationDirection),\n    PreselectDirection(OperationDirection),\n    CancelPreselect,\n    CycleFocusWindow(CycleDirection),\n    CycleMoveWindow(CycleDirection),\n    StackWindow(OperationDirection),\n    UnstackWindow,\n    CycleStack(CycleDirection),\n    CycleStackIndex(CycleDirection),\n    FocusStackWindow(usize),\n    StackAll,\n    UnstackAll,\n    ResizeWindowEdge(OperationDirection, Sizing),\n    ResizeWindowAxis(Axis, Sizing),\n    MoveContainerToLastWorkspace,\n    SendContainerToLastWorkspace,\n    MoveContainerToMonitorNumber(usize),\n    CycleMoveContainerToMonitor(CycleDirection),\n    MoveContainerToWorkspaceNumber(usize),\n    MoveContainerToNamedWorkspace(String),\n    CycleMoveContainerToWorkspace(CycleDirection),\n    SendContainerToMonitorNumber(usize),\n    CycleSendContainerToMonitor(CycleDirection),\n    SendContainerToWorkspaceNumber(usize),\n    CycleSendContainerToWorkspace(CycleDirection),\n    SendContainerToMonitorWorkspaceNumber(usize, usize),\n    MoveContainerToMonitorWorkspaceNumber(usize, usize),\n    SendContainerToNamedWorkspace(String),\n    CycleMoveWorkspaceToMonitor(CycleDirection),\n    MoveWorkspaceToMonitorNumber(usize),\n    SwapWorkspacesToMonitorNumber(usize),\n    ForceFocus,\n    Close,\n    Minimize,\n    Promote,\n    PromoteSwap,\n    PromoteFocus,\n    PromoteWindow(OperationDirection),\n    EagerFocus(String),\n    LockMonitorWorkspaceContainer(usize, usize, usize),\n    UnlockMonitorWorkspaceContainer(usize, usize, usize),\n    ToggleLock,\n    ToggleFloat,\n    ToggleMonocle,\n    ToggleMaximize,\n    ToggleWindowContainerBehaviour,\n    ToggleFloatOverride,\n    WindowHidingBehaviour(HidingBehaviour),\n    ToggleCrossMonitorMoveBehaviour,\n    CrossMonitorMoveBehaviour(MoveBehaviour),\n    UnmanagedWindowOperationBehaviour(OperationBehaviour),\n    // Current Workspace Commands\n    ManageFocusedWindow,\n    UnmanageFocusedWindow,\n    AdjustContainerPadding(Sizing, i32),\n    AdjustWorkspacePadding(Sizing, i32),\n    ChangeLayout(DefaultLayout),\n    CycleLayout(CycleDirection),\n    LayoutRatios(Option<Vec<f32>>, Option<Vec<f32>>),\n    ScrollingLayoutColumns(NonZeroUsize),\n    ChangeLayoutCustom(#[serde_as(as = \"ResolvedPathBuf\")] PathBuf),\n    FlipLayout(Axis),\n    ToggleWorkspaceWindowContainerBehaviour,\n    ToggleWorkspaceFloatOverride,\n    // Monitor and Workspace Commands\n    MonitorIndexPreference(usize, i32, i32, i32, i32),\n    DisplayIndexPreference(usize, String),\n    EnsureWorkspaces(usize, usize),\n    EnsureNamedWorkspaces(usize, Vec<String>),\n    NewWorkspace,\n    ToggleTiling,\n    Stop,\n    StopIgnoreRestore,\n    TogglePause,\n    Retile,\n    RetileWithResizeDimensions,\n    QuickSave,\n    QuickLoad,\n    Save(#[serde_as(as = \"ResolvedPathBuf\")] PathBuf),\n    Load(#[serde_as(as = \"ResolvedPathBuf\")] PathBuf),\n    CycleFocusMonitor(CycleDirection),\n    CycleFocusWorkspace(CycleDirection),\n    CycleFocusEmptyWorkspace(CycleDirection),\n    FocusMonitorNumber(usize),\n    FocusMonitorAtCursor,\n    FocusLastWorkspace,\n    CloseWorkspace,\n    FocusWorkspaceNumber(usize),\n    FocusWorkspaceNumbers(usize),\n    FocusMonitorWorkspaceNumber(usize, usize),\n    FocusNamedWorkspace(String),\n    ContainerPadding(usize, usize, i32),\n    NamedWorkspaceContainerPadding(String, i32),\n    FocusedWorkspaceContainerPadding(i32),\n    WorkspacePadding(usize, usize, i32),\n    NamedWorkspacePadding(String, i32),\n    FocusedWorkspacePadding(i32),\n    WorkspaceTiling(usize, usize, bool),\n    NamedWorkspaceTiling(String, bool),\n    WorkspaceName(usize, usize, String),\n    WorkspaceLayout(usize, usize, DefaultLayout),\n    NamedWorkspaceLayout(String, DefaultLayout),\n    WorkspaceLayoutCustom(usize, usize, #[serde_as(as = \"ResolvedPathBuf\")] PathBuf),\n    NamedWorkspaceLayoutCustom(String, #[serde_as(as = \"ResolvedPathBuf\")] PathBuf),\n    WorkspaceLayoutRule(usize, usize, usize, DefaultLayout),\n    NamedWorkspaceLayoutRule(String, usize, DefaultLayout),\n    WorkspaceLayoutCustomRule(\n        usize,\n        usize,\n        usize,\n        #[serde_as(as = \"ResolvedPathBuf\")] PathBuf,\n    ),\n    NamedWorkspaceLayoutCustomRule(String, usize, #[serde_as(as = \"ResolvedPathBuf\")] PathBuf),\n    ClearWorkspaceLayoutRules(usize, usize),\n    ClearNamedWorkspaceLayoutRules(String),\n    ToggleWorkspaceLayer,\n    // Configuration\n    ReloadConfiguration,\n    ReplaceConfiguration(#[serde_as(as = \"ResolvedPathBuf\")] PathBuf),\n    ReloadStaticConfiguration(#[serde_as(as = \"ResolvedPathBuf\")] PathBuf),\n    WatchConfiguration(bool),\n    CompleteConfiguration,\n    AltFocusHack(bool),\n    Theme(Box<KomorebiTheme>),\n    Animation(bool, Option<AnimationPrefix>),\n    AnimationDuration(u64, Option<AnimationPrefix>),\n    AnimationFps(u64),\n    AnimationStyle(AnimationStyle, Option<AnimationPrefix>),\n    #[serde(alias = \"ActiveWindowBorder\")]\n    Border(bool),\n    #[serde(alias = \"ActiveWindowBorderColour\")]\n    BorderColour(WindowKind, u32, u32, u32),\n    #[serde(alias = \"ActiveWindowBorderStyle\")]\n    BorderStyle(BorderStyle),\n    BorderWidth(i32),\n    BorderOffset(i32),\n    BorderImplementation(BorderImplementation),\n    Transparency(bool),\n    ToggleTransparency,\n    TransparencyAlpha(u8),\n    InvisibleBorders(Rect),\n    StackbarMode(StackbarMode),\n    StackbarLabel(StackbarLabel),\n    StackbarFocusedTextColour(u32, u32, u32),\n    StackbarUnfocusedTextColour(u32, u32, u32),\n    StackbarBackgroundColour(u32, u32, u32),\n    StackbarHeight(i32),\n    StackbarTabWidth(i32),\n    StackbarFontSize(i32),\n    StackbarFontFamily(Option<String>),\n    WorkAreaOffset(Rect),\n    MonitorWorkAreaOffset(usize, Rect),\n    WorkspaceWorkAreaOffset(usize, usize, Rect),\n    ToggleWindowBasedWorkAreaOffset,\n    ResizeDelta(i32),\n    InitialWorkspaceRule(ApplicationIdentifier, String, usize, usize),\n    InitialNamedWorkspaceRule(ApplicationIdentifier, String, String),\n    WorkspaceRule(ApplicationIdentifier, String, usize, usize),\n    NamedWorkspaceRule(ApplicationIdentifier, String, String),\n    ClearWorkspaceRules(usize, usize),\n    ClearNamedWorkspaceRules(String),\n    ClearAllWorkspaceRules,\n    EnforceWorkspaceRules,\n    SessionFloatRule,\n    SessionFloatRules,\n    ClearSessionFloatRules,\n    #[serde(alias = \"FloatRule\")]\n    IgnoreRule(ApplicationIdentifier, String),\n    ManageRule(ApplicationIdentifier, String),\n    IdentifyObjectNameChangeApplication(ApplicationIdentifier, String),\n    IdentifyTrayApplication(ApplicationIdentifier, String),\n    IdentifyLayeredApplication(ApplicationIdentifier, String),\n    IdentifyBorderOverflowApplication(ApplicationIdentifier, String),\n    State,\n    GlobalState,\n    VisibleWindows,\n    MonitorInformation,\n    Query(StateQuery),\n    FocusFollowsMouse(FocusFollowsMouseImplementation, bool),\n    ToggleFocusFollowsMouse(FocusFollowsMouseImplementation),\n    MouseFollowsFocus(bool),\n    ToggleMouseFollowsFocus,\n    RemoveTitleBar(ApplicationIdentifier, String),\n    ToggleTitleBars,\n    AddSubscriberSocket(String),\n    AddSubscriberSocketWithOptions(String, SubscribeOptions),\n    RemoveSubscriberSocket(String),\n    AddSubscriberPipe(String),\n    RemoveSubscriberPipe(String),\n    ApplicationSpecificConfigurationSchema,\n    NotificationSchema,\n    SocketSchema,\n    StaticConfigSchema,\n    GenerateStaticConfig,\n    DebugWindow(isize),\n}\n\nimpl SocketMessage {\n    pub fn as_bytes(&self) -> eyre::Result<Vec<u8>> {\n        Ok(serde_json::to_string(self)?.as_bytes().to_vec())\n    }\n}\n\nimpl FromStr for SocketMessage {\n    type Err = serde_json::Error;\n\n    fn from_str(s: &str) -> eyre::Result<Self, Self::Err> {\n        serde_json::from_str(s)\n    }\n}\n\n#[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\npub struct SubscribeOptions {\n    /// Only emit notifications when the window manager state has changed\n    pub filter_state_changes: bool,\n}\n\n#[derive(Debug, Copy, Clone, Eq, PartialEq, Display, Serialize, Deserialize, ValueEnum)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Stackbar mode\npub enum StackbarMode {\n    /// Always show\n    Always,\n    /// Never show\n    Never,\n    /// Show on stack\n    OnStack,\n}\n\n#[derive(Debug, Copy, Default, Clone, Eq, PartialEq, Display, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Starbar label\npub enum StackbarLabel {\n    #[default]\n    /// Process name\n    Process,\n    /// Window title\n    Title,\n}\n\n#[derive(\n    Default, Copy, Clone, Debug, Eq, PartialEq, Display, Serialize, Deserialize, ValueEnum,\n)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Border style\npub enum BorderStyle {\n    #[default]\n    /// Use the system border style\n    System,\n    /// Use the Windows 11-style rounded borders\n    Rounded,\n    /// Use the Windows 10-style square borders\n    Square,\n}\n\n#[derive(\n    Default, Copy, Clone, Debug, Eq, PartialEq, Display, Serialize, Deserialize, ValueEnum,\n)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Border style\npub enum BorderImplementation {\n    #[default]\n    /// Use the adjustable komorebi border implementation\n    Komorebi,\n    /// Use the thin Windows accent border implementation\n    Windows,\n}\n\n#[derive(\n    Copy,\n    Clone,\n    Debug,\n    Default,\n    Serialize,\n    Deserialize,\n    Display,\n    EnumString,\n    ValueEnum,\n    PartialEq,\n    Eq,\n    Hash,\n)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Window kind\npub enum WindowKind {\n    /// Single window\n    Single,\n    /// Stack container\n    Stack,\n    /// Monocle container\n    Monocle,\n    #[default]\n    /// Unfocused window\n    Unfocused,\n    /// Unfocused locked container\n    UnfocusedLocked,\n    /// Floating window\n    Floating,\n}\n\n#[derive(Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\npub enum StateQuery {\n    FocusedMonitorIndex,\n    FocusedWorkspaceIndex,\n    FocusedContainerIndex,\n    FocusedWindowIndex,\n    FocusedWorkspaceName,\n    FocusedWorkspaceLayout,\n    FocusedContainerKind,\n    Version,\n}\n\n#[derive(\n    Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Display, EnumString, ValueEnum,\n)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Application identifier\npub enum ApplicationIdentifier {\n    /// Executable name\n    #[serde(alias = \"exe\")]\n    Exe,\n    /// Class\n    #[serde(alias = \"class\")]\n    Class,\n    #[serde(alias = \"title\")]\n    /// Window title\n    Title,\n    /// Executable path\n    #[serde(alias = \"path\")]\n    Path,\n}\n\n#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Display, EnumString, ValueEnum)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Focus follows mouse implementation\npub enum FocusFollowsMouseImplementation {\n    /// Custom FFM implementation (slightly more CPU-intensive)\n    Komorebi,\n    /// Native (legacy) Windows FFM implementation\n    Windows,\n}\n\n#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Window management behaviour\npub struct WindowManagementBehaviour {\n    /// The current [`WindowContainerBehaviour`] to be used\n    pub current_behaviour: WindowContainerBehaviour,\n    /// Override of `current_behaviour` to open new windows as floating windows\n    /// that can be later toggled to tiled, when false it will default to\n    /// `current_behaviour` again.\n    pub float_override: bool,\n    /// Determines if a new window should be spawned floating when on the floating layer and the\n    /// floating layer behaviour is set to float. This value is always calculated when checking for\n    /// the management behaviour on a specific workspace.\n    pub floating_layer_override: bool,\n    /// The floating layer behaviour to be used if the float override is being used\n    pub floating_layer_behaviour: FloatingLayerBehaviour,\n    /// The `Placement` to be used when toggling a window to float\n    pub toggle_float_placement: Placement,\n    /// The `Placement` to be used when spawning a window on the floating layer with the\n    /// `FloatingLayerBehaviour` set to `FloatingLayerBehaviour::Float`\n    pub floating_layer_placement: Placement,\n    /// The `Placement` to be used when spawning a window with float override active\n    pub float_override_placement: Placement,\n    /// The `Placement` to be used when spawning a window that matches a `floating_applications` rule\n    pub float_rule_placement: Placement,\n}\n\n#[derive(\n    Clone, Copy, Debug, Default, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq,\n)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Window container behaviour when a new window is opened\npub enum WindowContainerBehaviour {\n    /// Create a new container for each new window\n    #[default]\n    Create,\n    /// Append new windows to the focused window container\n    Append,\n}\n\n#[derive(\n    Clone, Copy, Debug, Default, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq,\n)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Floating layer behaviour when a new window is opened\npub enum FloatingLayerBehaviour {\n    /// Tile new windows (unless they match a float rule or float override is active)\n    #[default]\n    Tile,\n    /// Float new windows\n    Float,\n}\n\n#[derive(\n    Clone, Copy, Debug, Default, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq,\n)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Placement behaviour for floating windows\npub enum Placement {\n    /// Does not change the size or position of the window\n    #[default]\n    None,\n    /// Center the window without changing the size\n    Center,\n    /// Center the window and resize it according to the `AspectRatio`\n    CenterAndResize,\n}\n\nimpl FloatingLayerBehaviour {\n    pub fn should_float(&self) -> bool {\n        match self {\n            FloatingLayerBehaviour::Tile => false,\n            FloatingLayerBehaviour::Float => true,\n        }\n    }\n}\n\nimpl Placement {\n    pub fn should_center(&self) -> bool {\n        match self {\n            Placement::None => false,\n            Placement::Center | Placement::CenterAndResize => true,\n        }\n    }\n\n    pub fn should_resize(&self) -> bool {\n        match self {\n            Placement::None | Placement::Center => false,\n            Placement::CenterAndResize => true,\n        }\n    }\n}\n\n#[derive(\n    Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize, Display, EnumString, ValueEnum,\n)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Move behaviour when the operation works across a monitor boundary\npub enum MoveBehaviour {\n    /// Swap the window container with the window container at the edge of the adjacent monitor\n    #[default]\n    Swap,\n    /// Insert the window container into the focused workspace on the adjacent monitor\n    Insert,\n    /// Do nothing if trying to move a window container in the direction of an adjacent monitor\n    NoOp,\n}\n\n#[derive(\n    Clone, Copy, Debug, Default, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq,\n)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Behaviour when an action would cross a monitor boundary\npub enum CrossBoundaryBehaviour {\n    /// Attempt to perform actions across a workspace boundary\n    Workspace,\n    /// Attempt to perform actions across a monitor boundary\n    #[default]\n    Monitor,\n}\n\n#[derive(Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Window hiding behaviour\npub enum HidingBehaviour {\n    /// END OF LIFE FEATURE: Use the `SW_HIDE` flag to hide windows when switching workspaces (has issues with Electron apps)\n    #[deprecated(note = \"End of life feature\")]\n    Hide,\n    /// Use the `SW_MINIMIZE` flag to hide windows when switching workspaces (has issues with frequent workspace switching)\n    Minimize,\n    /// Use the undocumented SetCloak Win32 function to hide windows when switching workspaces\n    Cloak,\n}\n\n#[derive(\n    Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize, Display, EnumString, ValueEnum,\n)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Operation behaviour for temporarily unmanaged and floating windows\npub enum OperationBehaviour {\n    /// Process commands on temporarily unmanaged/floated windows\n    #[default]\n    Op,\n    /// Ignore commands on temporarily unmanaged/floated windows\n    NoOp,\n}\n\n#[derive(\n    Clone, Copy, Debug, Default, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq,\n)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Window handling behaviour\npub enum WindowHandlingBehaviour {\n    #[default]\n    /// Synchronous\n    Sync,\n    /// Asynchronous\n    Async,\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn deserializes() {\n        // Set a variable for testing\n        unsafe {\n            std::env::set_var(\"VAR\", \"VALUE\");\n        }\n\n        let json = r#\"{\"type\":\"WorkspaceLayoutCustomRule\",\"content\":[0,0,0,\"/path/%VAR%/d\"]}\"#;\n        let message: SocketMessage = serde_json::from_str(json).unwrap();\n\n        let SocketMessage::WorkspaceLayoutCustomRule(\n            _workspace_index,\n            _workspace_number,\n            _monitor_index,\n            path,\n        ) = message\n        else {\n            panic!(\"Expected WorkspaceLayoutCustomRule\");\n        };\n\n        assert_eq!(path, PathBuf::from(\"/path/VALUE/d\"));\n    }\n}\n"
  },
  {
    "path": "komorebi/src/core/pathext.rs",
    "content": "use std::collections::HashMap;\nuse std::ffi::OsStr;\nuse std::path::Component;\nuse std::path::Path;\nuse std::path::PathBuf;\n\nuse serde::Deserialize;\nuse serde::Serialize;\n\n/// Path extension trait\npub trait PathExt {\n    /// Resolve environment variable components in a path.\n    ///\n    /// Resolves the following formats:\n    /// - CMD: `%variable%`\n    /// - PowerShell: `$Env:variable`\n    /// - Bash: `$variable`.\n    fn replace_env(&self) -> PathBuf;\n}\n\n/// Blanket implementation for all types that can be converted to a `Path`.\nimpl<P: AsRef<Path>> PathExt for P {\n    fn replace_env(&self) -> PathBuf {\n        let mut out = PathBuf::new();\n\n        for c in self.as_ref().components() {\n            match c {\n                Component::Normal(mut c) => {\n                    // Special case for ~ and $HOME, replace with $Env:USERPROFILE\n                    if c == OsStr::new(\"~\") || c.eq_ignore_ascii_case(\"$HOME\") {\n                        c = OsStr::new(\"$Env:USERPROFILE\");\n                    }\n\n                    let bytes = c.as_encoded_bytes();\n\n                    // %LOCALAPPDATA%\n                    let var = if bytes[0] == b'%' && bytes[bytes.len() - 1] == b'%' {\n                        Some(&bytes[1..bytes.len() - 1])\n                    } else {\n                        // prefix length is 5 for $Env: and 1 for $\n                        // so we take the minimum of 5 and the length of the bytes\n                        let prefix = &bytes[..5.min(bytes.len())];\n                        let prefix = unsafe { OsStr::from_encoded_bytes_unchecked(prefix) };\n\n                        // $Env:LOCALAPPDATA\n                        if prefix.eq_ignore_ascii_case(\"$Env:\") {\n                            Some(&bytes[5..])\n                        } else if bytes[0] == b'$' {\n                            // $LOCALAPPDATA\n                            Some(&bytes[1..])\n                        } else {\n                            // not a variable\n                            None\n                        }\n                    };\n\n                    // if component is a variable, get the value from the environment\n                    if let Some(var) = var {\n                        let var = unsafe { OsStr::from_encoded_bytes_unchecked(var) };\n                        if let Some(value) = std::env::var_os(var) {\n                            out.push(value);\n                            continue;\n                        }\n                    }\n\n                    // if not a variable, or a value couldn't be obtained from environemnt\n                    // then push the component as is\n                    out.push(c);\n                }\n\n                // other components are pushed as is\n                _ => out.push(c),\n            }\n        }\n\n        out\n    }\n}\n\n/// Replace environment variables in a path. This is a wrapper around\n/// [`PathExt::replace_env`] to be used in Clap arguments parsing.\npub fn replace_env_in_path(input: &str) -> Result<PathBuf, std::convert::Infallible> {\n    Ok(input.replace_env())\n}\n\n/// A wrapper around [`PathBuf`] that has a custom [Deserialize] implementation\n/// that uses [`PathExt::replace_env`] to resolve environment variables\n#[derive(Clone, Debug)]\npub struct ResolvedPathBuf(PathBuf);\n\nimpl ResolvedPathBuf {\n    /// Create a new [`ResolvedPathBuf`] from a [`PathBuf`]\n    pub fn new(path: PathBuf) -> Self {\n        Self(path.replace_env())\n    }\n}\n\nimpl From<ResolvedPathBuf> for PathBuf {\n    fn from(path: ResolvedPathBuf) -> Self {\n        path.0\n    }\n}\n\nimpl serde_with::SerializeAs<PathBuf> for ResolvedPathBuf {\n    fn serialize_as<S>(path: &PathBuf, serializer: S) -> Result<S::Ok, S::Error>\n    where\n        S: serde::Serializer,\n    {\n        path.serialize(serializer)\n    }\n}\n\nimpl<'de> serde_with::DeserializeAs<'de, PathBuf> for ResolvedPathBuf {\n    fn deserialize_as<D>(deserializer: D) -> Result<PathBuf, D::Error>\n    where\n        D: serde::Deserializer<'de>,\n    {\n        let path = PathBuf::deserialize(deserializer)?;\n        Ok(path.replace_env())\n    }\n}\n\n#[cfg(feature = \"schemars\")]\nimpl serde_with::schemars_1::JsonSchemaAs<PathBuf> for ResolvedPathBuf {\n    fn schema_name() -> std::borrow::Cow<'static, str> {\n        std::borrow::Cow::Borrowed(\"PathBuf\")\n    }\n\n    fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {\n        schemars::json_schema!({\n            \"type\": \"string\",\n            \"description\": \"A file system path. Environment variables like %VAR%, $Env:VAR, or $VAR are automatically resolved.\"\n        })\n    }\n}\n\n/// Custom deserializer for [`Option<HashMap<usize, PathBuf>>`] that uses\n/// [`PathExt::replace_env`] to resolve environment variables in the paths.\n///\n/// This is used in `WorkspaceConfig` struct because we can't use\n/// #[serde_with::serde_as] as it doesn't handle [`Option<HashMap<usize, ResolvedPathBuf>>`]\n/// quite well and generated compiler errors that can't be fixed because of Rust's orphan rule.\npub fn resolve_option_hashmap_usize_path<'de, D>(\n    deserializer: D,\n) -> Result<Option<HashMap<usize, PathBuf>>, D::Error>\nwhere\n    D: serde::Deserializer<'de>,\n{\n    let map = Option::<HashMap<usize, PathBuf>>::deserialize(deserializer)?;\n    Ok(map.map(|map| map.into_iter().map(|(k, v)| (k, v.replace_env())).collect()))\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    // helper functions\n    fn expected<P: AsRef<Path>>(p: P) -> PathBuf {\n        // Ensure that the path is using the correct path separator for the OS.\n        p.as_ref().components().collect::<PathBuf>()\n    }\n\n    fn resolve<P: AsRef<Path>>(p: P) -> PathBuf {\n        p.replace_env()\n    }\n\n    #[test]\n    fn resolves_env_vars() {\n        // Set a variable for testing\n        unsafe {\n            std::env::set_var(\"VAR\", \"VALUE\");\n        }\n\n        // %VAR% format\n        assert_eq!(resolve(\"/path/%VAR%/d\"), expected(\"/path/VALUE/d\"));\n        // $env:VAR format\n        assert_eq!(resolve(\"/path/$env:VAR/d\"), expected(\"/path/VALUE/d\"));\n        // $VAR format\n        assert_eq!(resolve(\"/path/$VAR/d\"), expected(\"/path/VALUE/d\"));\n\n        // non-existent variable\n        assert_eq!(resolve(\"/path/%ASD%/to/d\"), expected(\"/path/%ASD%/to/d\"));\n        assert_eq!(\n            resolve(\"/path/$env:ASD/to/d\"),\n            expected(\"/path/$env:ASD/to/d\")\n        );\n        assert_eq!(resolve(\"/path/$ASD/to/d\"), expected(\"/path/$ASD/to/d\"));\n\n        // Set a $env:USERPROFILE variable for testing\n        unsafe {\n            std::env::set_var(\"USERPROFILE\", \"C:\\\\Users\\\\user\");\n        }\n\n        // ~ and $HOME should be replaced with $Env:USERPROFILE\n        assert_eq!(resolve(\"~\"), expected(\"C:\\\\Users\\\\user\"));\n        assert_eq!(resolve(\"$HOME\"), expected(\"C:\\\\Users\\\\user\"));\n    }\n}\n"
  },
  {
    "path": "komorebi/src/focus_manager.rs",
    "content": "#![deny(clippy::unwrap_used, clippy::expect_used)]\n\nuse crossbeam_channel::Receiver;\nuse crossbeam_channel::Sender;\nuse parking_lot::Mutex;\nuse std::ops::Deref;\nuse std::sync::Arc;\nuse std::sync::OnceLock;\n\nuse crate::Window;\nuse crate::WindowManager;\n\npub struct Notification(isize);\n\nimpl Deref for Notification {\n    type Target = isize;\n\n    fn deref(&self) -> &Self::Target {\n        &self.0\n    }\n}\n\nstatic CHANNEL: OnceLock<(Sender<Notification>, Receiver<Notification>)> = OnceLock::new();\n\npub fn channel() -> &'static (Sender<Notification>, Receiver<Notification>) {\n    CHANNEL.get_or_init(|| crossbeam_channel::bounded(20))\n}\n\nfn event_tx() -> Sender<Notification> {\n    channel().0.clone()\n}\n\nfn event_rx() -> Receiver<Notification> {\n    channel().1.clone()\n}\n\n// Currently this should only be used for async focus updates, such as\n// when an animation finishes and we need to focus to set the cursor\n// position if the user has mouse follows focus enabled\npub fn send_notification(hwnd: isize) {\n    if event_tx().try_send(Notification(hwnd)).is_err() {\n        tracing::warn!(\"channel is full; dropping notification\")\n    }\n}\n\npub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) {\n    std::thread::spawn(move || {\n        loop {\n            match handle_notifications(wm.clone()) {\n                Ok(()) => {\n                    tracing::warn!(\"restarting finished thread\");\n                }\n                Err(error) => {\n                    tracing::warn!(\"restarting failed thread: {}\", error);\n                }\n            }\n        }\n    });\n}\n\npub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result<()> {\n    tracing::info!(\"listening\");\n\n    let receiver = event_rx();\n\n    for notification in receiver {\n        let mouse_follows_focus = wm.lock().mouse_follows_focus;\n        let _ = Window::from(*notification).focus(mouse_follows_focus);\n    }\n\n    Ok(())\n}\n"
  },
  {
    "path": "komorebi/src/lib.rs",
    "content": "#![warn(clippy::all)]\n\npub mod animation;\npub mod border_manager;\npub mod com;\n#[macro_use]\npub mod ring;\npub mod container;\npub mod core;\npub mod focus_manager;\npub mod lockable_sequence;\npub mod monitor;\npub mod monitor_reconciliator;\npub mod process_command;\npub mod process_event;\npub mod process_movement;\npub mod reaper;\npub mod set_window_position;\npub mod splash;\npub mod stackbar_manager;\npub mod state;\npub mod static_config;\npub mod styles;\npub mod theme_manager;\npub mod transparency_manager;\npub mod window;\npub mod window_manager;\npub mod window_manager_event;\npub mod windows_api;\npub mod windows_callbacks;\npub mod winevent;\npub mod winevent_listener;\npub mod workspace;\n\nuse lazy_static::lazy_static;\nuse monitor_reconciliator::MonitorNotification;\nuse std::collections::HashMap;\nuse std::collections::VecDeque;\nuse std::fs::File;\nuse std::io::Write;\nuse std::net::TcpStream;\nuse std::path::PathBuf;\nuse std::process::Command;\nuse std::sync::Arc;\nuse std::sync::atomic::AtomicBool;\nuse std::sync::atomic::AtomicI32;\nuse std::sync::atomic::AtomicU32;\nuse std::sync::atomic::AtomicU64;\nuse std::sync::atomic::Ordering;\n\npub use core::*;\npub use komorebi_themes::colour::*;\npub use process_command::*;\npub use process_event::*;\npub use static_config::*;\npub use win32_display_data;\npub use window::*;\npub use window_manager::*;\npub use window_manager_event::*;\npub use windows_api::WindowsApi;\npub use windows_api::*;\n\nuse crate::core::config_generation::IdWithIdentifier;\nuse crate::core::config_generation::MatchingRule;\nuse crate::core::config_generation::MatchingStrategy;\nuse crate::core::config_generation::WorkspaceMatchingRule;\nuse color_eyre::eyre;\nuse crossbeam_utils::atomic::AtomicCell;\nuse os_info::Version;\nuse parking_lot::Mutex;\nuse parking_lot::RwLock;\nuse regex::Regex;\nuse serde::Deserialize;\nuse serde::Serialize;\nuse state::State;\nuse uds_windows::UnixStream;\nuse which::which;\nuse winreg::RegKey;\nuse winreg::enums::HKEY_CURRENT_USER;\n\nlazy_static! {\n    static ref HIDDEN_HWNDS: Arc<Mutex<Vec<isize>>> = Arc::new(Mutex::new(vec![]));\n    static ref LAYERED_WHITELIST: Arc<Mutex<Vec<MatchingRule>>> = Arc::new(Mutex::new(vec![\n        MatchingRule::Simple(IdWithIdentifier {\n            kind: ApplicationIdentifier::Exe,\n            id: String::from(\"steam.exe\"),\n            matching_strategy: Option::from(MatchingStrategy::Equals),\n        }),\n    ]));\n    static ref TRAY_AND_MULTI_WINDOW_IDENTIFIERS: Arc<Mutex<Vec<MatchingRule>>> =\n        Arc::new(Mutex::new(vec![\n            MatchingRule::Simple(IdWithIdentifier {\n                kind: ApplicationIdentifier::Exe,\n                id: String::from(\"explorer.exe\"),\n                matching_strategy: Option::from(MatchingStrategy::Equals),\n            }),\n            MatchingRule::Simple(IdWithIdentifier {\n                kind: ApplicationIdentifier::Exe,\n                id: String::from(\"firefox.exe\"),\n                matching_strategy: Option::from(MatchingStrategy::Equals),\n            }),\n            MatchingRule::Simple(IdWithIdentifier {\n                kind: ApplicationIdentifier::Exe,\n                id: String::from(\"chrome.exe\"),\n                matching_strategy: Option::from(MatchingStrategy::Equals),\n            }),\n            MatchingRule::Simple(IdWithIdentifier {\n                kind: ApplicationIdentifier::Exe,\n                id: String::from(\"idea64.exe\"),\n                matching_strategy: Option::from(MatchingStrategy::Equals),\n            }),\n            MatchingRule::Simple(IdWithIdentifier {\n                kind: ApplicationIdentifier::Exe,\n                id: String::from(\"ApplicationFrameHost.exe\"),\n                matching_strategy: Option::from(MatchingStrategy::Equals),\n            }),\n            MatchingRule::Simple(IdWithIdentifier {\n                kind: ApplicationIdentifier::Exe,\n                id: String::from(\"steam.exe\"),\n                matching_strategy: Option::from(MatchingStrategy::Equals),\n            })\n        ]));\n    static ref OBJECT_NAME_CHANGE_ON_LAUNCH: Arc<Mutex<Vec<MatchingRule>>> = Arc::new(Mutex::new(vec![\n        MatchingRule::Simple(IdWithIdentifier {\n            kind: ApplicationIdentifier::Exe,\n            id: String::from(\"firefox.exe\"),\n            matching_strategy: Option::from(MatchingStrategy::Equals),\n        }),\n        MatchingRule::Simple(IdWithIdentifier {\n            kind: ApplicationIdentifier::Exe,\n            id: String::from(\"idea64.exe\"),\n            matching_strategy: Option::from(MatchingStrategy::Equals),\n        }),\n    ]));\n    static ref OBJECT_NAME_CHANGE_TITLE_IGNORE_LIST: Arc<Mutex<Vec<Regex>>> = Arc::new(Mutex::new(Vec::new()));\n    static ref TRANSPARENCY_BLACKLIST: Arc<Mutex<Vec<MatchingRule>>> = Arc::new(Mutex::new(Vec::new()));\n    static ref MONITOR_INDEX_PREFERENCES: Arc<Mutex<HashMap<usize, Rect>>> =\n        Arc::new(Mutex::new(HashMap::new()));\n    static ref DISPLAY_INDEX_PREFERENCES: Arc<RwLock<HashMap<usize, String>>> =\n        Arc::new(RwLock::new(HashMap::new()));\n    static ref WORKSPACE_MATCHING_RULES: Arc<Mutex<Vec<WorkspaceMatchingRule>>> =\n        Arc::new(Mutex::new(Vec::new()));\n    static ref REGEX_IDENTIFIERS: Arc<Mutex<HashMap<String, Regex>>> =\n        Arc::new(Mutex::new(HashMap::new()));\n    static ref MANAGE_IDENTIFIERS: Arc<Mutex<Vec<MatchingRule>>> = Arc::new(Mutex::new(vec![]));\n    static ref IGNORE_IDENTIFIERS: Arc<Mutex<Vec<MatchingRule>>> = Arc::new(Mutex::new(vec![\n        // mstsc.exe creates these on Windows 11 when a WSL process is launched\n        // https://github.com/LGUG2Z/komorebi/issues/74\n        MatchingRule::Simple(IdWithIdentifier {\n            kind: ApplicationIdentifier::Class,\n            id: String::from(\"OPContainerClass\"),\n            matching_strategy: Option::from(MatchingStrategy::Equals),\n        }),\n        MatchingRule::Simple(IdWithIdentifier {\n            kind: ApplicationIdentifier::Class,\n            id: String::from(\"IHWindowClass\"),\n            matching_strategy: Option::from(MatchingStrategy::Equals),\n        }),\n        MatchingRule::Simple(IdWithIdentifier {\n            kind: ApplicationIdentifier::Exe,\n            id: String::from(\"komorebi-bar.exe\"),\n            matching_strategy: Option::from(MatchingStrategy::Equals),\n        })\n    ]));\n    static ref SESSION_FLOATING_APPLICATIONS: Arc<Mutex<Vec<MatchingRule>>> = Arc::new(Mutex::new(Vec::new()));\n    static ref FLOATING_APPLICATIONS: Arc<Mutex<Vec<MatchingRule>>> = Arc::new(Mutex::new(vec![\n        MatchingRule::Simple(IdWithIdentifier {\n            kind: ApplicationIdentifier::Exe,\n            id: String::from(\"komorebi-shortcuts.exe\"),\n            matching_strategy: Option::from(MatchingStrategy::Equals),\n        })\n\n    ]));\n    static ref PERMAIGNORE_CLASSES: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![\n        \"Chrome_RenderWidgetHostHWND\".to_string(),\n    ]));\n    static ref WSL2_UI_PROCESSES: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![\n        \"X410.exe\".to_string(),\n        \"vcxsrv.exe\".to_string(),\n    ]));\n    static ref SLOW_APPLICATION_IDENTIFIERS: Arc<Mutex<Vec<MatchingRule>>> = Arc::new(Mutex::new(vec![\n        MatchingRule::Simple(IdWithIdentifier {\n            kind: ApplicationIdentifier::Exe,\n            id: String::from(\"firefox.exe\"),\n            matching_strategy: Option::from(MatchingStrategy::Equals),\n        }),\n    ]));\n    static ref DUPLICATE_MONITOR_SERIAL_IDS: Arc<RwLock<Vec<String>>> =\n        Arc::new(RwLock::new(Vec::new()));\n    static ref SUBSCRIPTION_PIPES: Arc<Mutex<HashMap<String, File>>> =\n        Arc::new(Mutex::new(HashMap::new()));\n    pub static ref SUBSCRIPTION_SOCKETS: Arc<Mutex<HashMap<String, PathBuf>>> =\n        Arc::new(Mutex::new(HashMap::new()));\n    pub static ref SUBSCRIPTION_SOCKET_OPTIONS: Arc<Mutex<HashMap<String, SubscribeOptions>>> =\n        Arc::new(Mutex::new(HashMap::new()));\n    static ref TCP_CONNECTIONS: Arc<Mutex<HashMap<String, TcpStream>>> =\n        Arc::new(Mutex::new(HashMap::new()));\n    static ref HIDING_BEHAVIOUR: Arc<Mutex<HidingBehaviour>> =\n        Arc::new(Mutex::new(HidingBehaviour::Cloak));\n    pub static ref HOME_DIR: PathBuf = {\n        std::env::var(\"KOMOREBI_CONFIG_HOME\").map_or_else(|_| dirs::home_dir().expect(\"there is no home directory\"), |home_path| {\n            let home = home_path.replace_env();\n\n            assert!(\n                home.is_dir(),\n                \"$Env:KOMOREBI_CONFIG_HOME is set to '{home_path}', which is not a valid directory\"\n            );\n\n\n            home\n        })\n    };\n    pub static ref DATA_DIR: PathBuf = dirs::data_local_dir().expect(\"there is no local data directory\").join(\"komorebi\");\n    pub static ref AHK_EXE: String = {\n        let mut ahk: String = String::from(\"autohotkey.exe\");\n\n        if let Ok(komorebi_ahk_exe) = std::env::var(\"KOMOREBI_AHK_EXE\")\n            && which(&komorebi_ahk_exe).is_ok() {\n                ahk = komorebi_ahk_exe;\n            }\n\n        ahk\n    };\n    static ref WINDOWS_11: bool = {\n        matches!(\n            os_info::get().version(),\n            Version::Semantic(_, _, x) if x >= &22000\n        )\n    };\n\n    // Use app-specific titlebar removal options where possible\n    // eg. Windows Terminal, IntelliJ IDEA, Firefox\n    static ref NO_TITLEBAR: Arc<Mutex<Vec<MatchingRule>>> = Arc::new(Mutex::new(vec![]));\n\n    static ref WINDOWS_BY_BAR_HWNDS: Arc<Mutex<HashMap<isize, VecDeque<isize>>>> =\n        Arc::new(Mutex::new(HashMap::new()));\n\n    static ref FLOATING_WINDOW_TOGGLE_ASPECT_RATIO: Arc<Mutex<AspectRatio>> = Arc::new(Mutex::new(AspectRatio::Predefined(PredefinedAspectRatio::Widescreen)));\n\n    static ref CURRENT_VIRTUAL_DESKTOP: Arc<Mutex<Option<Vec<u8>>>> = Arc::new(Mutex::new(None));\n}\n\npub static DEFAULT_WORKSPACE_PADDING: AtomicI32 = AtomicI32::new(10);\npub static DEFAULT_CONTAINER_PADDING: AtomicI32 = AtomicI32::new(10);\npub static DEFAULT_RESIZE_DELTA: i32 = 50;\n\npub static DEFAULT_MOUSE_FOLLOWS_FOCUS: bool = true;\npub static INITIAL_CONFIGURATION_LOADED: AtomicBool = AtomicBool::new(false);\npub static CUSTOM_FFM: AtomicBool = AtomicBool::new(false);\npub static SESSION_ID: AtomicU32 = AtomicU32::new(0);\n\npub static REMOVE_TITLEBARS: AtomicBool = AtomicBool::new(false);\n\npub static SLOW_APPLICATION_COMPENSATION_TIME: AtomicU64 = AtomicU64::new(20);\n\npub static WINDOW_HANDLING_BEHAVIOUR: AtomicCell<WindowHandlingBehaviour> =\n    AtomicCell::new(WindowHandlingBehaviour::Sync);\n\nshadow_rs::shadow!(build);\n\npub const PUBLIC_KEY: [u8; 32] = [\n    0x5a, 0x69, 0x4a, 0xe1, 0x3c, 0x4b, 0xc8, 0x4e, 0xc3, 0x79, 0x0f, 0xab, 0x27, 0x6b, 0x7e, 0xdd,\n    0x6b, 0x39, 0x6f, 0xa2, 0xc3, 0x9f, 0x3d, 0x48, 0xf2, 0x72, 0x56, 0x41, 0x1b, 0xc8, 0x08, 0xdb,\n];\n\n#[derive(Default, Debug, Clone, PartialEq, Deserialize)]\npub struct License {\n    #[serde(rename = \"hasValidSubscription\")]\n    pub has_valid_subscription: bool,\n    pub timestamp: i64,\n    #[serde(rename = \"currentEndPeriod\")]\n    pub current_end_period: Option<i64>,\n    pub signature: String,\n}\n\n/// A trait for types that can be marked as locked or unlocked.\npub trait Lockable {\n    /// Returns `true` if the item is locked.\n    fn locked(&self) -> bool;\n    /// Sets the locked state of the item.\n    fn set_locked(&mut self, locked: bool) -> &mut Self;\n}\n\n#[must_use]\npub fn current_virtual_desktop() -> Option<Vec<u8>> {\n    let hkcu = RegKey::predef(HKEY_CURRENT_USER);\n\n    // This is the path on Windows 10\n    let mut current = hkcu\n        .open_subkey(format!(\n            r#\"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\SessionInfo\\{}\\VirtualDesktops\"#,\n            SESSION_ID.load(Ordering::SeqCst)\n        ))\n        .ok()\n        .and_then(\n            |desktops| match desktops.get_raw_value(\"CurrentVirtualDesktop\") {\n                Ok(current) => Option::from(current.bytes),\n                Err(_) => None,\n            },\n        );\n\n    // This is the path on Windows 11\n    if current.is_none() {\n        current = hkcu\n            .open_subkey(r\"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\VirtualDesktops\")\n            .ok()\n            .and_then(\n                |desktops| match desktops.get_raw_value(\"CurrentVirtualDesktop\") {\n                    Ok(current) => Option::from(current.bytes),\n                    Err(_) => None,\n                },\n            );\n    }\n\n    // For Win10 users that do not use virtual desktops, the CurrentVirtualDesktop value will not\n    // exist until one has been created in the task view\n\n    // The registry value will also not exist on user login if virtual desktops have been created\n    // but the task view has not been initiated\n\n    // In both of these cases, we return None, and the virtual desktop validation will never run. In\n    // the latter case, if the user desires this validation after initiating the task view, komorebi\n    // should be restarted, and then when this // fn runs again for the first time, it will pick up\n    // the value of CurrentVirtualDesktop and validate against it accordingly\n    current\n}\n\n#[derive(Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n#[serde(untagged)]\npub enum NotificationEvent {\n    WindowManager(WindowManagerEvent),\n    Socket(SocketMessage),\n    Monitor(MonitorNotification),\n    VirtualDesktop(VirtualDesktopNotification),\n}\n\n#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\npub enum VirtualDesktopNotification {\n    EnteredAssociatedVirtualDesktop,\n    LeftAssociatedVirtualDesktop,\n}\n\n#[derive(Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\npub struct Notification {\n    pub event: NotificationEvent,\n    pub state: State,\n}\n\npub fn notify_subscribers(\n    notification: Notification,\n    state_has_been_modified: bool,\n) -> eyre::Result<()> {\n    let is_override_event = matches!(\n        notification.event,\n        NotificationEvent::Socket(SocketMessage::AddSubscriberSocket(_))\n            | NotificationEvent::Socket(SocketMessage::AddSubscriberSocketWithOptions(_, _))\n            | NotificationEvent::Socket(SocketMessage::Theme(_))\n            | NotificationEvent::Socket(SocketMessage::ReloadStaticConfiguration(_))\n            | NotificationEvent::WindowManager(WindowManagerEvent::TitleUpdate(_, _))\n            | NotificationEvent::WindowManager(WindowManagerEvent::Show(_, _))\n            | NotificationEvent::WindowManager(WindowManagerEvent::Uncloak(_, _))\n    );\n\n    let notification = &serde_json::to_string(&notification)?;\n    let mut stale_sockets = vec![];\n    let mut sockets = SUBSCRIPTION_SOCKETS.lock();\n    let options = SUBSCRIPTION_SOCKET_OPTIONS.lock();\n\n    for (socket, path) in &mut *sockets {\n        let apply_state_filter = (*options)\n            .get(socket)\n            .copied()\n            .unwrap_or_default()\n            .filter_state_changes;\n\n        if !apply_state_filter || state_has_been_modified || is_override_event {\n            match UnixStream::connect(path) {\n                Ok(mut stream) => {\n                    tracing::debug!(\"pushed notification to subscriber: {socket}\");\n                    stream.write_all(notification.as_bytes())?;\n                }\n                Err(_) => {\n                    stale_sockets.push(socket.clone());\n                }\n            }\n        }\n    }\n\n    for socket in stale_sockets {\n        tracing::warn!(\"removing stale subscription: {socket}\");\n        sockets.remove(&socket);\n        let socket_path = DATA_DIR.join(socket);\n        if let Err(error) = std::fs::remove_file(&socket_path) {\n            tracing::error!(\n                \"could not remove stale subscriber socket file at {}: {error}\",\n                socket_path.display()\n            )\n        }\n    }\n\n    let mut stale_pipes = vec![];\n    let mut pipes = SUBSCRIPTION_PIPES.lock();\n    for (subscriber, pipe) in &mut *pipes {\n        match writeln!(pipe, \"{notification}\") {\n            Ok(()) => {\n                tracing::debug!(\"pushed notification to subscriber: {subscriber}\");\n            }\n            Err(error) => {\n                // ERROR_FILE_NOT_FOUND\n                // 2 (0x2)\n                // The system cannot find the file specified.\n\n                // ERROR_NO_DATA\n                // 232 (0xE8)\n                // The pipe is being closed.\n\n                // Remove the subscription; the process will have to subscribe again\n                if let Some(2 | 232) = error.raw_os_error() {\n                    stale_pipes.push(subscriber.clone());\n                }\n            }\n        }\n    }\n\n    for subscriber in stale_pipes {\n        tracing::warn!(\"removing stale subscription: {}\", subscriber);\n        pipes.remove(&subscriber);\n    }\n\n    Ok(())\n}\n\npub fn load_configuration() -> eyre::Result<()> {\n    let config_pwsh = HOME_DIR.join(\"komorebi.ps1\");\n    let config_ahk = HOME_DIR.join(\"komorebi.ahk\");\n\n    if config_pwsh.exists() {\n        let powershell_exe = if which(\"pwsh.exe\").is_ok() {\n            \"pwsh.exe\"\n        } else {\n            \"powershell.exe\"\n        };\n\n        tracing::info!(\"loading configuration file: {}\", config_pwsh.display());\n\n        Command::new(powershell_exe)\n            .arg(config_pwsh.as_os_str())\n            .output()?;\n    } else if config_ahk.exists() && which(&*AHK_EXE).is_ok() {\n        tracing::info!(\"loading configuration file: {}\", config_ahk.display());\n\n        Command::new(&*AHK_EXE)\n            .arg(config_ahk.as_os_str())\n            .output()?;\n    }\n\n    Ok(())\n}\n"
  },
  {
    "path": "komorebi/src/lockable_sequence.rs",
    "content": "use std::collections::VecDeque;\n\nuse crate::Lockable;\n\n/// A sequence supporting insertion, removal, and swapping of elements while preserving the absolute\n/// positions of locked items.\npub trait LockableSequence<T: Lockable> {\n    /// Inserts a value at `idx`, keeping locked elements at their absolute positions.\n    fn insert_respecting_locks(&mut self, idx: usize, value: T) -> usize;\n    /// Removes the element at `idx`, keeping locked elements at their absolute positions.\n    fn remove_respecting_locks(&mut self, idx: usize) -> Option<T>;\n    /// Swaps the elements at indices `i` and `j`, keeping locked elements at their absolute positions.\n    fn swap_respecting_locks(&mut self, i: usize, j: usize);\n}\n\nimpl<T: Lockable> LockableSequence<T> for VecDeque<T> {\n    /// Insert `value` at logical index `idx`, with trying to keep locked elements\n    /// (`is_locked()`) anchored at their original positions.\n    ///\n    /// Returns the final index of the inserted element.\n    fn insert_respecting_locks(&mut self, mut idx: usize, value: T) -> usize {\n        // 1. Bounds check: if index is out of range, simply append.\n        if idx >= self.len() {\n            self.push_back(value);\n            return self.len() - 1; // last index\n        }\n\n        // 2. Normal VecDeque insertion\n        self.insert(idx, value);\n\n        // 3. Walk left-to-right once, swapping any misplaced locked element. After\n        // the VecDeque::insert all items after `idx` have moved right by one. For every locked\n        // element that is now to the right of an unlocked one, swap it back left exactly once.\n        for index in (idx + 1)..self.len() {\n            if self[index].locked() && !self[index - 1].locked() {\n                self.swap(index - 1, index);\n\n                // If the element we just inserted participated in the swap,\n                // update `idx` so we can return its final location.\n                if idx == index - 1 {\n                    idx = index;\n                }\n            }\n        }\n        idx\n    }\n\n    /// Remove element at `idx`, with trying to keep locked elements\n    /// (`is_locked()`) anchored at their original positions.\n    ///\n    /// Returns the removed element, or `None` if `idx` is out of bounds.\n    fn remove_respecting_locks(&mut self, idx: usize) -> Option<T> {\n        // 1. Bounds check: if index is out of range, do nothing.\n        if idx >= self.len() {\n            return None;\n        }\n\n        // 2. Remove the element at the requested index.\n        //    All elements after idx are now shifted left by 1.\n        let removed = self.remove(idx)?;\n\n        // 3. If less than 2 elements remain, nothing to shift.\n        if self.len() < 2 {\n            return Some(removed);\n        }\n\n        // 4. Iterate from the element just after the removed spot up to the second-to-last\n        //    element, right-to-left. This loop \"fixes\" locked elements that were shifted left\n        //    off their anchored positions: If a locked element now has an unlocked element\n        //    to its right, swap them back to restore locked order.\n        for index in (idx..self.len() - 1).rev() {\n            // If current is locked and the next one is not locked, swap them.\n            if self[index].locked() && !self[index + 1].locked() {\n                self.swap(index, index + 1);\n            }\n        }\n\n        // 5. Return the removed value.\n        Some(removed)\n    }\n\n    /// Swaps the elements at indices `i` and `j`, along with their `locked` status, ensuring\n    /// the lock state remains associated with the position rather than the element itself.\n    fn swap_respecting_locks(&mut self, i: usize, j: usize) {\n        self.swap(i, j);\n        let locked_i = self[i].locked();\n        let locked_j = self[j].locked();\n        self[i].set_locked(locked_j);\n        self[j].set_locked(locked_i);\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[derive(Debug, PartialEq)]\n    struct TestItem {\n        val: i32,\n        locked: bool,\n    }\n\n    impl Lockable for TestItem {\n        fn locked(&self) -> bool {\n            self.locked\n        }\n\n        fn set_locked(&mut self, locked: bool) -> &mut Self {\n            self.locked = locked;\n            self\n        }\n    }\n\n    fn vals(v: &VecDeque<TestItem>) -> Vec<i32> {\n        v.iter().map(|x| x.val).collect()\n    }\n\n    fn test_deque(items: &[(i32, bool)]) -> VecDeque<TestItem> {\n        items\n            .iter()\n            .cloned()\n            .map(|(val, locked)| TestItem { val, locked })\n            .collect()\n    }\n\n    #[test]\n    fn test_insert_respecting_locks() {\n        // Test case 1: Basic insertion with locked index\n        {\n            // Lock index 2\n            let mut ring = test_deque(&[(0, false), (1, false), (2, true), (3, false), (4, false)]);\n            // Insert at index 0, should shift elements while keeping index 2 locked\n            ring.insert_respecting_locks(\n                0,\n                TestItem {\n                    val: 99,\n                    locked: false,\n                },\n            );\n            // Element '2' remains at index 2, element '1' that was at index 1 is now at index 3\n            assert_eq!(vals(&ring), vec![99, 0, 2, 1, 3, 4]);\n        }\n\n        // Test case 2: Insert at a locked index (should insert after locked)\n        {\n            // Lock index 2\n            let mut ring = test_deque(&[(0, false), (1, false), (2, true), (3, false), (4, false)]);\n            // Try to insert at locked index 2, should insert at index 3 instead\n            let actual_index = ring.insert_respecting_locks(\n                2,\n                TestItem {\n                    val: 99,\n                    locked: false,\n                },\n            );\n            assert_eq!(actual_index, 3);\n            assert_eq!(vals(&ring), vec![0, 1, 2, 99, 3, 4]);\n        }\n\n        // Test case 3: Multiple locked indices\n        {\n            // Lock index 1 and 3\n            let mut ring = test_deque(&[(0, false), (1, true), (2, false), (3, true), (4, false)]);\n            // Insert at index 0, should maintain locked indices\n            ring.insert_respecting_locks(\n                0,\n                TestItem {\n                    val: 99,\n                    locked: false,\n                },\n            );\n            // Elements '1' and '3' remain at indices 1 and 3\n            assert_eq!(vals(&ring), vec![99, 1, 0, 3, 2, 4]);\n        }\n\n        // Test case 4: Insert at end\n        {\n            // Lock index 2\n            let mut ring = test_deque(&[(0, false), (1, false), (2, true), (3, false), (4, false)]);\n            let actual_index = ring.insert_respecting_locks(\n                5,\n                TestItem {\n                    val: 99,\n                    locked: false,\n                },\n            );\n            assert_eq!(actual_index, 5);\n            assert_eq!(vals(&ring), vec![0, 1, 2, 3, 4, 99]);\n        }\n\n        // Test case 5: Empty ring\n        {\n            let mut ring = test_deque(&[]);\n            // Insert into empty deque\n            let actual_index = ring.insert_respecting_locks(\n                0,\n                TestItem {\n                    val: 99,\n                    locked: false,\n                },\n            );\n            assert_eq!(actual_index, 0);\n            assert_eq!(vals(&ring), vec![99]);\n        }\n\n        // Test case 6: All indices locked\n        {\n            // Lock all indices\n            let mut ring = test_deque(&[(0, true), (1, true), (2, true), (3, true), (4, true)]);\n            // Try to insert at index 2, should insert at the end\n            let actual_index = ring.insert_respecting_locks(\n                2,\n                TestItem {\n                    val: 99,\n                    locked: false,\n                },\n            );\n            assert_eq!(actual_index, 5);\n            assert_eq!(vals(&ring), vec![0, 1, 2, 3, 4, 99]);\n        }\n\n        // Test case 7: Consecutive locked indices\n        {\n            // Lock index 2 and 3\n            let mut ring = test_deque(&[(0, false), (1, false), (2, true), (3, true), (4, false)]);\n            // Insert at index 1, should maintain consecutive locked indices\n            ring.insert_respecting_locks(\n                1,\n                TestItem {\n                    val: 99,\n                    locked: false,\n                },\n            );\n            // Elements '2' and '3' remain at indices 2 and 3\n            assert_eq!(vals(&ring), vec![0, 99, 2, 3, 1, 4]);\n        }\n    }\n\n    #[test]\n    fn test_remove_respecting_locks() {\n        // Test case 1: Remove a non-locked index before a locked index\n        {\n            // Lock index 2\n            let mut ring = test_deque(&[(0, false), (1, false), (2, true), (3, false), (4, false)]);\n            let removed = ring.remove_respecting_locks(0);\n            assert_eq!(removed.map(|x| x.val), Some(0));\n            // Elements '2' remain at index 2\n            assert_eq!(vals(&ring), vec![1, 3, 2, 4]);\n        }\n\n        // Test case 2: Remove a locked index\n        {\n            // Lock index 2\n            let mut ring = test_deque(&[(0, false), (1, false), (2, true), (3, false), (4, false)]);\n            let removed = ring.remove_respecting_locks(2);\n            assert_eq!(removed.map(|x| x.val), Some(2));\n            // Elements should stay at the same places\n            assert_eq!(vals(&ring), vec![0, 1, 3, 4]);\n        }\n\n        // Test case 3: Remove an index after a locked index\n        {\n            // Lock index 1\n            let mut ring = test_deque(&[(0, false), (1, true), (2, false), (3, false), (4, false)]);\n            let removed = ring.remove_respecting_locks(3);\n            assert_eq!(removed.map(|x| x.val), Some(3));\n            // Elements should stay at the same places\n            assert_eq!(vals(&ring), vec![0, 1, 2, 4]);\n        }\n\n        // Test case 4: Multiple locked indices\n        {\n            // Lock index 1 and 3\n            let mut ring = test_deque(&[(0, false), (1, true), (2, false), (3, true), (4, false)]);\n            let removed = ring.remove_respecting_locks(0);\n            assert_eq!(removed.map(|x| x.val), Some(0));\n            // Elements '1' and '3' remain at indices '1' and '3'\n            assert_eq!(vals(&ring), vec![2, 1, 4, 3]);\n        }\n\n        // Test case 5: Remove the last element\n        {\n            // Lock index 2\n            let mut ring = test_deque(&[(0, false), (1, false), (2, true), (3, false), (4, false)]);\n            let removed = ring.remove_respecting_locks(4);\n            assert_eq!(removed.map(|x| x.val), Some(4));\n            // Index 2 should still be at the same place\n            assert_eq!(vals(&ring), vec![0, 1, 2, 3]);\n        }\n\n        // Test case 6: Invalid index\n        {\n            // Lock index 2\n            let mut ring = test_deque(&[(0, false), (1, false), (2, true), (3, false), (4, false)]);\n            let removed = ring.remove_respecting_locks(10);\n            assert_eq!(removed, None);\n            // Deque unchanged\n            assert_eq!(vals(&ring), vec![0, 1, 2, 3, 4]);\n        }\n\n        // Test case 7: Remove enough elements to make a locked index invalid\n        {\n            // Lock index 2\n            let mut ring = test_deque(&[(0, false), (1, false), (2, true)]);\n            ring.remove_respecting_locks(0);\n            // Index 2 should now be '1'\n            assert_eq!(vals(&ring), vec![1, 2]);\n        }\n\n        // Test case 8: Removing an element before multiple locked indices\n        {\n            // Lock index 2 and 4\n            let mut ring = test_deque(&[\n                (0, false),\n                (1, false),\n                (2, true),\n                (3, false),\n                (4, true),\n                (5, false),\n            ]);\n            let removed = ring.remove_respecting_locks(1);\n            assert_eq!(removed.map(|x| x.val), Some(1));\n            // Both indices should still be at the same place\n            assert_eq!(vals(&ring), vec![0, 3, 2, 5, 4]);\n        }\n    }\n\n    #[test]\n    fn test_swap_respecting_locks_various_cases() {\n        // Swap unlocked and locked\n        let mut ring = test_deque(&[(0, false), (1, true), (2, false), (3, false)]);\n        ring.swap_respecting_locks(0, 1);\n        assert_eq!(vals(&ring), vec![1, 0, 2, 3]);\n        assert!(!ring[0].locked);\n        assert!(ring[1].locked);\n        ring.swap_respecting_locks(0, 1);\n        assert_eq!(vals(&ring), vec![0, 1, 2, 3]);\n        assert!(!ring[0].locked);\n        assert!(ring[1].locked);\n\n        // Both locked\n        let mut ring = test_deque(&[(0, true), (1, false), (2, true)]);\n        ring.swap_respecting_locks(0, 2);\n        assert_eq!(vals(&ring), vec![2, 1, 0]);\n        assert!(ring[0].locked);\n        assert!(!ring[1].locked);\n        assert!(ring[2].locked);\n\n        // Both unlocked\n        let mut ring = test_deque(&[(0, false), (1, true), (2, false)]);\n        ring.swap_respecting_locks(0, 2);\n        assert_eq!(vals(&ring), vec![2, 1, 0]);\n        assert!(!ring[0].locked);\n        assert!(ring[1].locked);\n        assert!(!ring[2].locked);\n    }\n}\n"
  },
  {
    "path": "komorebi/src/main.rs",
    "content": "#![warn(clippy::all)]\n#![allow(\n    clippy::missing_errors_doc,\n    clippy::redundant_pub_crate,\n    clippy::significant_drop_tightening,\n    clippy::significant_drop_in_scrutinee,\n    clippy::doc_markdown\n)]\n\nuse std::env::temp_dir;\nuse std::net::Shutdown;\nuse std::path::PathBuf;\nuse std::sync::Arc;\nuse std::sync::atomic::Ordering;\n#[cfg(feature = \"deadlock_detection\")]\nuse std::time::Duration;\n\nuse clap::Parser;\nuse clap::ValueEnum;\nuse color_eyre::eyre;\nuse color_eyre::eyre::bail;\nuse crossbeam_utils::Backoff;\nuse komorebi::animation::ANIMATION_ENABLED_GLOBAL;\nuse komorebi::animation::ANIMATION_ENABLED_PER_ANIMATION;\nuse komorebi::animation::AnimationEngine;\nuse komorebi::replace_env_in_path;\nuse parking_lot::Mutex;\n#[cfg(feature = \"deadlock_detection\")]\nuse parking_lot::deadlock;\nuse serde::Deserialize;\nuse sysinfo::Process;\nuse sysinfo::ProcessesToUpdate;\nuse tracing_appender::non_blocking::WorkerGuard;\nuse tracing_subscriber::EnvFilter;\nuse tracing_subscriber::layer::SubscriberExt;\nuse uds_windows::UnixStream;\n\nuse komorebi::CUSTOM_FFM;\nuse komorebi::DATA_DIR;\nuse komorebi::HOME_DIR;\nuse komorebi::INITIAL_CONFIGURATION_LOADED;\nuse komorebi::SESSION_ID;\nuse komorebi::border_manager;\nuse komorebi::focus_manager;\nuse komorebi::load_configuration;\nuse komorebi::monitor_reconciliator;\nuse komorebi::process_command::listen_for_commands;\nuse komorebi::process_command::listen_for_commands_tcp;\nuse komorebi::process_event::listen_for_events;\nuse komorebi::process_movement::listen_for_movements;\nuse komorebi::reaper;\nuse komorebi::stackbar_manager;\nuse komorebi::state::State;\nuse komorebi::static_config::StaticConfig;\nuse komorebi::theme_manager;\nuse komorebi::transparency_manager;\nuse komorebi::window_manager::WindowManager;\nuse komorebi::windows_api::WindowsApi;\nuse komorebi::winevent_listener;\n\nfn setup(log_level: LogLevel) -> eyre::Result<(WorkerGuard, WorkerGuard)> {\n    if std::env::var(\"RUST_LIB_BACKTRACE\").is_err() {\n        unsafe {\n            std::env::set_var(\"RUST_LIB_BACKTRACE\", \"1\");\n        }\n    }\n\n    color_eyre::install()?;\n\n    if std::env::var(\"RUST_LOG\").is_err() {\n        unsafe {\n            std::env::set_var(\n                \"RUST_LOG\",\n                match log_level {\n                    LogLevel::Error => \"error\",\n                    LogLevel::Warn => \"warn\",\n                    LogLevel::Info => \"info\",\n                    LogLevel::Debug => \"debug\",\n                    LogLevel::Trace => \"trace\",\n                },\n            );\n        }\n    }\n\n    let appender = tracing_appender::rolling::daily(std::env::temp_dir(), \"komorebi_plaintext.log\");\n    let color_appender = tracing_appender::rolling::daily(std::env::temp_dir(), \"komorebi.log\");\n    let (non_blocking, guard) = tracing_appender::non_blocking(appender);\n    let (color_non_blocking, color_guard) = tracing_appender::non_blocking(color_appender);\n\n    tracing::subscriber::set_global_default(\n        tracing_subscriber::fmt::Subscriber::builder()\n            .with_env_filter(EnvFilter::from_default_env())\n            .finish()\n            .with(\n                tracing_subscriber::fmt::Layer::default()\n                    .with_writer(non_blocking)\n                    .with_ansi(false),\n            )\n            .with(\n                tracing_subscriber::fmt::Layer::default()\n                    .with_writer(color_non_blocking)\n                    .with_ansi(true),\n            ),\n    )?;\n\n    // https://github.com/tokio-rs/tracing/blob/master/examples/examples/panic_hook.rs\n    // Set a panic hook that records the panic as a `tracing` event at the\n    // `ERROR` verbosity level.\n    //\n    // If we are currently in a span when the panic occurred, the logged event\n    // will include the current span, allowing the context in which the panic\n    // occurred to be recorded.\n    std::panic::set_hook(Box::new(|panic| {\n        // If the panic has a source location, record it as structured fields.\n        panic.location().map_or_else(\n            || {\n                tracing::error!(message = %panic);\n            },\n            |location| {\n                // On nightly Rust, where the `PanicInfo` type also exposes a\n                // `message()` method returning just the message, we could record\n                // just the message instead of the entire `fmt::Display`\n                // implementation, avoiding the duplciated location\n                tracing::error!(\n                    message = %panic,\n                    panic.file = location.file(),\n                    panic.line = location.line(),\n                    panic.column = location.column(),\n                );\n            },\n        );\n    }));\n\n    Ok((guard, color_guard))\n}\n\n#[cfg(feature = \"deadlock_detection\")]\n#[tracing::instrument]\nfn detect_deadlocks() {\n    // Create a background thread which checks for deadlocks every 10s\n    std::thread::spawn(move || {\n        loop {\n            tracing::info!(\"running deadlock detector\");\n            std::thread::sleep(Duration::from_secs(5));\n            let deadlocks = deadlock::check_deadlock();\n            if deadlocks.is_empty() {\n                continue;\n            }\n\n            tracing::error!(\"{} deadlocks detected\", deadlocks.len());\n            for (i, threads) in deadlocks.iter().enumerate() {\n                tracing::error!(\"deadlock #{}\", i);\n                for t in threads {\n                    tracing::error!(\"thread id: {:#?}\", t.thread_id());\n                    tracing::error!(\"{:#?}\", t.backtrace());\n                }\n            }\n        }\n    });\n}\n\n#[derive(Default, Deserialize, ValueEnum, Clone)]\n#[serde(rename_all = \"snake_case\")]\nenum LogLevel {\n    Error,\n    Warn,\n    #[default]\n    Info,\n    Debug,\n    Trace,\n}\n\n#[derive(Parser)]\n#[clap(author, about, version = komorebi::build::CLAP_LONG_VERSION)]\nstruct Opts {\n    /// Allow the use of komorebi's custom focus-follows-mouse implementation\n    #[clap(short, long = \"ffm\")]\n    focus_follows_mouse: bool,\n    /// Wait for `komorebic complete-configuration` to be sent before processing events\n    #[clap(short, long)]\n    await_configuration: bool,\n    /// Start a TCP server on the given port to allow the direct sending of SocketMessages\n    #[clap(short, long)]\n    tcp_port: Option<usize>,\n    /// Path to a static configuration JSON file\n    #[clap(short, long)]\n    #[clap(value_parser = replace_env_in_path)]\n    config: Option<PathBuf>,\n    /// Do not attempt to auto-apply a dumped state temp file from a previously running instance of komorebi\n    #[clap(long)]\n    clean_state: bool,\n    /// Level of log output verbosity\n    #[clap(long, value_enum, default_value_t=LogLevel::Info)]\n    log_level: LogLevel,\n}\n\n#[tracing::instrument]\n#[allow(clippy::cognitive_complexity)]\nfn main() -> eyre::Result<()> {\n    let opts: Opts = Opts::parse();\n    CUSTOM_FFM.store(opts.focus_follows_mouse, Ordering::SeqCst);\n\n    let mut set_foreground_window_retries = 5;\n    let mut set_foreground_window_succeeded = false;\n\n    let process_id = WindowsApi::current_process_id();\n    while set_foreground_window_retries > 0 && !set_foreground_window_succeeded {\n        match WindowsApi::allow_set_foreground_window(process_id) {\n            Ok(_) => {\n                set_foreground_window_succeeded = true;\n            }\n            Err(error) => {\n                tracing::error!(\"{error}\");\n                set_foreground_window_retries -= 1;\n            }\n        }\n\n        if set_foreground_window_retries == 0 {\n            bail!(\"failed call to AllowSetForegroundWindow after 5 retries\");\n        }\n    }\n\n    WindowsApi::set_process_dpi_awareness_context()?;\n\n    let session_id = WindowsApi::process_id_to_session_id()?;\n    SESSION_ID.store(session_id, Ordering::SeqCst);\n\n    let mut system = sysinfo::System::new();\n    system.refresh_processes(ProcessesToUpdate::All, true);\n\n    let matched_procs: Vec<&Process> = system.processes_by_name(\"komorebi.exe\".as_ref()).collect();\n\n    if matched_procs.len() > 1 {\n        let mut len = matched_procs.len();\n        for proc in matched_procs {\n            if let Some(executable_path) = proc.exe()\n                && executable_path.to_string_lossy().contains(\"shims\")\n            {\n                len -= 1;\n            }\n        }\n\n        if len > 1 {\n            tracing::error!(\n                \"komorebi.exe is already running, please exit the existing process before starting a new one\"\n            );\n            std::process::exit(1);\n        }\n    }\n\n    // File logging worker guard has to have an assignment in the main fn to work\n    let (_guard, _color_guard) = setup(opts.log_level)?;\n\n    WindowsApi::foreground_lock_timeout()?;\n\n    winevent_listener::start();\n\n    #[cfg(feature = \"deadlock_detection\")]\n    detect_deadlocks();\n\n    let static_config = opts.config.map_or_else(\n        || {\n            let komorebi_json = HOME_DIR.join(\"komorebi.json\");\n            if komorebi_json.is_file() {\n                Option::from(komorebi_json)\n            } else {\n                None\n            }\n        },\n        Option::from,\n    );\n\n    std::fs::create_dir_all(&*DATA_DIR)?;\n\n    let wm = if let Some(config) = &static_config {\n        tracing::info!(\n            \"creating window manager from static configuration file: {}\",\n            config.display()\n        );\n\n        Arc::new(Mutex::new(StaticConfig::preload(\n            config,\n            winevent_listener::event_rx(),\n            None,\n        )?))\n    } else {\n        Arc::new(Mutex::new(WindowManager::new(\n            winevent_listener::event_rx(),\n            None,\n        )?))\n    };\n\n    wm.lock().init()?;\n\n    if let Some(config) = &static_config {\n        StaticConfig::postload(config, &wm)?;\n    }\n\n    if !opts.await_configuration && !INITIAL_CONFIGURATION_LOADED.load(Ordering::SeqCst) {\n        INITIAL_CONFIGURATION_LOADED.store(true, Ordering::SeqCst);\n    };\n\n    if static_config.is_none() {\n        std::thread::spawn(|| load_configuration().expect(\"could not load configuration\"));\n\n        if opts.await_configuration {\n            let backoff = Backoff::new();\n            while !INITIAL_CONFIGURATION_LOADED.load(Ordering::SeqCst) {\n                backoff.snooze();\n            }\n        }\n    }\n\n    let dumped_state = temp_dir().join(\"komorebi.state.json\");\n\n    if !opts.clean_state && dumped_state.is_file() {\n        if let Ok(state) = serde_json::from_str(&std::fs::read_to_string(&dumped_state)?) {\n            wm.lock().apply_state(state);\n        } else {\n            tracing::warn!(\n                \"cannot apply state from {}; state struct is not up to date\",\n                dumped_state.display()\n            );\n        }\n    }\n\n    wm.lock().retile_all(false)?;\n\n    border_manager::listen_for_notifications(wm.clone());\n    stackbar_manager::listen_for_notifications(wm.clone());\n    transparency_manager::listen_for_notifications(wm.clone());\n    monitor_reconciliator::listen_for_notifications(wm.clone())?;\n    reaper::listen_for_notifications(wm.clone(), wm.lock().known_hwnds.clone());\n    focus_manager::listen_for_notifications(wm.clone());\n    theme_manager::listen_for_notifications();\n\n    listen_for_commands(wm.clone());\n\n    if let Some(port) = opts.tcp_port {\n        listen_for_commands_tcp(wm.clone(), port);\n    }\n\n    listen_for_events(wm.clone());\n\n    if CUSTOM_FFM.load(Ordering::SeqCst) {\n        listen_for_movements(wm.clone());\n    }\n\n    let (ctrlc_sender, ctrlc_receiver) = crossbeam_channel::bounded(1);\n    ctrlc::set_handler(move || {\n        ctrlc_sender\n            .send(())\n            .expect(\"could not send signal on ctrl-c channel\");\n    })?;\n\n    ctrlc_receiver\n        .recv()\n        .expect(\"could not receive signal on ctrl-c channel\");\n\n    tracing::error!(\"received ctrl-c, restoring all hidden windows and terminating process\");\n\n    let state = State::from(&*wm.lock());\n    std::fs::write(dumped_state, serde_json::to_string_pretty(&state)?)?;\n\n    ANIMATION_ENABLED_PER_ANIMATION.lock().clear();\n    ANIMATION_ENABLED_GLOBAL.store(false, Ordering::SeqCst);\n    wm.lock().restore_all_windows(false)?;\n    AnimationEngine::wait_for_all_animations();\n\n    if WindowsApi::focus_follows_mouse()? {\n        WindowsApi::disable_focus_follows_mouse()?;\n    }\n\n    let sockets = komorebi::SUBSCRIPTION_SOCKETS.lock();\n    for path in (*sockets).values() {\n        if let Ok(stream) = UnixStream::connect(path) {\n            stream.shutdown(Shutdown::Both)?;\n        }\n    }\n\n    let socket = DATA_DIR.join(\"komorebi.sock\");\n    let _ = std::fs::remove_file(socket);\n\n    std::process::exit(130);\n}\n"
  },
  {
    "path": "komorebi/src/monitor.rs",
    "content": "use std::collections::HashMap;\nuse std::collections::VecDeque;\nuse std::sync::atomic::Ordering;\n\nuse color_eyre::eyre;\nuse color_eyre::eyre::OptionExt;\nuse color_eyre::eyre::bail;\nuse serde::Deserialize;\nuse serde::Serialize;\n\nuse crate::border_manager::BORDER_ENABLED;\nuse crate::border_manager::BORDER_OFFSET;\nuse crate::border_manager::BORDER_WIDTH;\nuse crate::core::Rect;\n\nuse crate::DEFAULT_CONTAINER_PADDING;\nuse crate::DEFAULT_WORKSPACE_PADDING;\nuse crate::DefaultLayout;\nuse crate::FloatingLayerBehaviour;\nuse crate::Layout;\nuse crate::OperationDirection;\nuse crate::Wallpaper;\nuse crate::WindowsApi;\nuse crate::container::Container;\nuse crate::ring::Ring;\nuse crate::workspace::Workspace;\nuse crate::workspace::WorkspaceGlobals;\nuse crate::workspace::WorkspaceLayer;\n\n#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\npub struct Monitor {\n    pub id: isize,\n    pub name: String,\n    pub device: String,\n    pub device_id: String,\n    pub serial_number_id: Option<String>,\n    pub size: Rect,\n    pub work_area_size: Rect,\n    pub work_area_offset: Option<Rect>,\n    pub window_based_work_area_offset: Option<Rect>,\n    pub window_based_work_area_offset_limit: isize,\n    pub workspaces: Ring<Workspace>,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub last_focused_workspace: Option<usize>,\n    pub workspace_names: HashMap<usize, String>,\n    pub container_padding: Option<i32>,\n    pub workspace_padding: Option<i32>,\n    pub wallpaper: Option<Wallpaper>,\n    pub floating_layer_behaviour: Option<FloatingLayerBehaviour>,\n}\n\nimpl_ring_elements!(Monitor, Workspace);\n\n#[derive(Serialize)]\npub struct MonitorInformation {\n    pub id: isize,\n    pub name: String,\n    pub device: String,\n    pub device_id: String,\n    pub serial_number_id: Option<String>,\n    pub size: Rect,\n}\n\nimpl From<&Monitor> for MonitorInformation {\n    fn from(monitor: &Monitor) -> Self {\n        Self {\n            id: monitor.id,\n            name: monitor.name.clone(),\n            device: monitor.device.clone(),\n            device_id: monitor.device_id.clone(),\n            serial_number_id: monitor.serial_number_id.clone(),\n            size: monitor.size,\n        }\n    }\n}\n\npub fn new(\n    id: isize,\n    size: Rect,\n    work_area_size: Rect,\n    name: String,\n    device: String,\n    device_id: String,\n    serial_number_id: Option<String>,\n) -> Monitor {\n    let mut workspaces = Ring::default();\n    workspaces.elements_mut().push_back(Workspace::default());\n\n    Monitor {\n        id,\n        name,\n        device,\n        device_id,\n        serial_number_id,\n        size,\n        work_area_size,\n        work_area_offset: None,\n        window_based_work_area_offset: None,\n        window_based_work_area_offset_limit: 1,\n        workspaces,\n        last_focused_workspace: None,\n        workspace_names: HashMap::default(),\n        container_padding: None,\n        workspace_padding: None,\n        wallpaper: None,\n        floating_layer_behaviour: None,\n    }\n}\n\nimpl Monitor {\n    pub fn new(\n        id: isize,\n        size: Rect,\n        work_area_size: Rect,\n        name: String,\n        device: String,\n        device_id: String,\n        serial_number_id: Option<String>,\n    ) -> Self {\n        new(\n            id,\n            size,\n            work_area_size,\n            name,\n            device,\n            device_id,\n            serial_number_id,\n        )\n    }\n\n    pub fn placeholder() -> Self {\n        Self {\n            id: 0,\n            name: \"PLACEHOLDER\".to_string(),\n            device: \"\".to_string(),\n            device_id: \"\".to_string(),\n            serial_number_id: None,\n            size: Default::default(),\n            work_area_size: Default::default(),\n            work_area_offset: None,\n            window_based_work_area_offset: None,\n            window_based_work_area_offset_limit: 0,\n            workspaces: Default::default(),\n            last_focused_workspace: None,\n            workspace_names: Default::default(),\n            container_padding: None,\n            workspace_padding: None,\n            wallpaper: None,\n            floating_layer_behaviour: None,\n        }\n    }\n\n    pub fn focused_workspace_name(&self) -> Option<String> {\n        self.focused_workspace()\n            .map(|w| w.name.clone())\n            .unwrap_or(None)\n    }\n\n    pub fn focused_workspace_layout(&self) -> Option<Layout> {\n        self.focused_workspace().and_then(|workspace| {\n            if workspace.tile {\n                Some(workspace.layout.clone())\n            } else {\n                None\n            }\n        })\n    }\n\n    pub fn load_focused_workspace(&mut self, mouse_follows_focus: bool) -> eyre::Result<()> {\n        let focused_idx = self.focused_workspace_idx();\n        let hmonitor = self.id;\n        let monitor_wp = self.wallpaper.clone();\n        for (i, workspace) in self.workspaces_mut().iter_mut().enumerate() {\n            if i == focused_idx {\n                workspace.restore(mouse_follows_focus, hmonitor, &monitor_wp)?;\n            } else {\n                workspace.hide(None);\n            }\n        }\n\n        Ok(())\n    }\n\n    /// Updates the `globals` field of all workspaces\n    pub fn update_workspaces_globals(&mut self, offset: Option<Rect>) {\n        let container_padding = self\n            .container_padding\n            .or(Some(DEFAULT_CONTAINER_PADDING.load(Ordering::SeqCst)));\n        let workspace_padding = self\n            .workspace_padding\n            .or(Some(DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst)));\n        let (border_width, border_offset) = {\n            let border_enabled = BORDER_ENABLED.load(Ordering::SeqCst);\n            if border_enabled {\n                let border_width = BORDER_WIDTH.load(Ordering::SeqCst);\n                let border_offset = BORDER_OFFSET.load(Ordering::SeqCst);\n                (border_width, border_offset)\n            } else {\n                (0, 0)\n            }\n        };\n        let work_area = self.work_area_size;\n        let work_area_offset = self.work_area_offset.or(offset);\n        let window_based_work_area_offset = self.window_based_work_area_offset;\n        let window_based_work_area_offset_limit = self.window_based_work_area_offset_limit;\n        let floating_layer_behaviour = self.floating_layer_behaviour;\n\n        for workspace in self.workspaces_mut() {\n            workspace.globals = WorkspaceGlobals {\n                container_padding,\n                workspace_padding,\n                border_width,\n                border_offset,\n                work_area,\n                work_area_offset,\n                window_based_work_area_offset,\n                window_based_work_area_offset_limit,\n                floating_layer_behaviour,\n            }\n        }\n    }\n\n    /// Updates the `globals` field of workspace with index `workspace_idx`\n    pub fn update_workspace_globals(&mut self, workspace_idx: usize, offset: Option<Rect>) {\n        let container_padding = self\n            .container_padding\n            .or(Some(DEFAULT_CONTAINER_PADDING.load(Ordering::SeqCst)));\n        let workspace_padding = self\n            .workspace_padding\n            .or(Some(DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst)));\n        let (border_width, border_offset) = {\n            let border_enabled = BORDER_ENABLED.load(Ordering::SeqCst);\n            if border_enabled {\n                let border_width = BORDER_WIDTH.load(Ordering::SeqCst);\n                let border_offset = BORDER_OFFSET.load(Ordering::SeqCst);\n                (border_width, border_offset)\n            } else {\n                (0, 0)\n            }\n        };\n        let work_area = self.work_area_size;\n        let work_area_offset = self.work_area_offset.or(offset);\n        let window_based_work_area_offset = self.window_based_work_area_offset;\n        let window_based_work_area_offset_limit = self.window_based_work_area_offset_limit;\n        let floating_layer_behaviour = self.floating_layer_behaviour;\n\n        if let Some(workspace) = self.workspaces_mut().get_mut(workspace_idx) {\n            workspace.globals = WorkspaceGlobals {\n                container_padding,\n                workspace_padding,\n                border_width,\n                border_offset,\n                work_area,\n                work_area_offset,\n                window_based_work_area_offset,\n                window_based_work_area_offset_limit,\n                floating_layer_behaviour,\n            }\n        }\n    }\n\n    pub fn add_container(\n        &mut self,\n        container: Container,\n        workspace_idx: Option<usize>,\n    ) -> eyre::Result<()> {\n        let workspace = if let Some(idx) = workspace_idx {\n            self.workspaces_mut()\n                .get_mut(idx)\n                .ok_or_eyre(format!(\"there is no workspace at index {idx}\"))?\n        } else {\n            self.focused_workspace_mut()\n                .ok_or_eyre(\"there is no workspace\")?\n        };\n\n        workspace.add_container_to_back(container);\n\n        Ok(())\n    }\n\n    /// Adds a container to this `Monitor` using the move direction to calculate if the container\n    /// should be added in front of all containers, in the back or in place of the focused\n    /// container, moving the rest along. The move direction should be from the origin monitor\n    /// towards the target monitor or from the origin workspace towards the target workspace.\n    pub fn add_container_with_direction(\n        &mut self,\n        container: Container,\n        workspace_idx: Option<usize>,\n        direction: OperationDirection,\n    ) -> eyre::Result<()> {\n        let workspace = if let Some(idx) = workspace_idx {\n            self.workspaces_mut()\n                .get_mut(idx)\n                .ok_or_eyre(format!(\"there is no workspace at index {idx}\"))?\n        } else {\n            self.focused_workspace_mut()\n                .ok_or_eyre(\"there is no workspace\")?\n        };\n\n        match direction {\n            OperationDirection::Left => {\n                // insert the container into the workspace on the monitor at the back (or rightmost position)\n                // if we are moving across a boundary to the left (back = right side of the target)\n                match workspace.layout {\n                    Layout::Default(layout) => match layout {\n                        DefaultLayout::RightMainVerticalStack => {\n                            workspace.add_container_to_front(container);\n                        }\n                        DefaultLayout::UltrawideVerticalStack => {\n                            if workspace.containers().len() == 1 {\n                                workspace.insert_container_at_idx(0, container);\n                            } else {\n                                workspace.add_container_to_back(container);\n                            }\n                        }\n                        _ => {\n                            workspace.add_container_to_back(container);\n                        }\n                    },\n                    Layout::Custom(_) => {\n                        workspace.add_container_to_back(container);\n                    }\n                }\n            }\n            OperationDirection::Right => {\n                // insert the container into the workspace on the monitor at the front (or leftmost position)\n                // if we are moving across a boundary to the right (front = left side of the target)\n                match workspace.layout {\n                    Layout::Default(layout) => {\n                        let target_index = layout.leftmost_index(workspace.containers().len());\n\n                        match layout {\n                            DefaultLayout::RightMainVerticalStack\n                            | DefaultLayout::UltrawideVerticalStack => {\n                                if workspace.containers().len() == 1 {\n                                    workspace.add_container_to_back(container);\n                                } else {\n                                    workspace.insert_container_at_idx(target_index, container);\n                                }\n                            }\n                            _ => {\n                                workspace.insert_container_at_idx(target_index, container);\n                            }\n                        }\n                    }\n                    Layout::Custom(_) => {\n                        workspace.add_container_to_front(container);\n                    }\n                }\n            }\n            OperationDirection::Up | OperationDirection::Down => {\n                // insert the container into the workspace on the monitor at the position\n                // where the currently focused container on that workspace is\n                workspace.insert_container_at_idx(workspace.focused_container_idx(), container);\n            }\n        };\n\n        Ok(())\n    }\n\n    pub fn remove_workspace_by_idx(&mut self, idx: usize) -> Option<Workspace> {\n        if idx < self.workspaces().len() {\n            return self.workspaces_mut().remove(idx);\n        }\n\n        if idx == 0 {\n            self.workspaces_mut().push_back(Workspace::default());\n        } else {\n            self.focus_workspace(idx.saturating_sub(1)).ok()?;\n        };\n\n        None\n    }\n\n    pub fn ensure_workspace_count(&mut self, ensure_count: usize) {\n        if self.workspaces().len() < ensure_count {\n            self.workspaces_mut()\n                .resize(ensure_count, Workspace::default());\n        }\n    }\n\n    pub fn remove_workspaces(&mut self) -> VecDeque<Workspace> {\n        self.workspaces_mut().drain(..).collect()\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn move_container_to_workspace(\n        &mut self,\n        target_workspace_idx: usize,\n        follow: bool,\n        direction: Option<OperationDirection>,\n    ) -> eyre::Result<()> {\n        let workspace = self\n            .focused_workspace_mut()\n            .ok_or_eyre(\"there is no workspace\")?;\n\n        if workspace.maximized_window.is_some() {\n            bail!(\"cannot move native maximized window to another monitor or workspace\");\n        }\n\n        let foreground_hwnd = WindowsApi::foreground_window()?;\n        let floating_window_index = workspace\n            .floating_windows()\n            .iter()\n            .position(|w| w.hwnd == foreground_hwnd);\n\n        if let Some(idx) = floating_window_index {\n            if let Some(window) = workspace.floating_windows_mut().remove(idx) {\n                let workspaces = self.workspaces_mut();\n                #[allow(clippy::option_if_let_else)]\n                let target_workspace = match workspaces.get_mut(target_workspace_idx) {\n                    None => {\n                        workspaces.resize(target_workspace_idx + 1, Workspace::default());\n                        workspaces.get_mut(target_workspace_idx).unwrap()\n                    }\n                    Some(workspace) => workspace,\n                };\n\n                target_workspace.floating_windows_mut().push_back(window);\n                target_workspace.layer = WorkspaceLayer::Floating;\n            }\n        } else {\n            let container = workspace\n                .remove_focused_container()\n                .ok_or_eyre(\"there is no container\")?;\n\n            let workspaces = self.workspaces_mut();\n\n            #[allow(clippy::option_if_let_else)]\n            let target_workspace = match workspaces.get_mut(target_workspace_idx) {\n                None => {\n                    workspaces.resize(target_workspace_idx + 1, Workspace::default());\n                    workspaces.get_mut(target_workspace_idx).unwrap()\n                }\n                Some(workspace) => workspace,\n            };\n\n            if target_workspace.monocle_container.is_some() {\n                for container in target_workspace.containers_mut() {\n                    container.restore();\n                }\n\n                for window in target_workspace.floating_windows_mut() {\n                    window.restore();\n                }\n\n                target_workspace.reintegrate_monocle_container()?;\n            }\n\n            target_workspace.layer = WorkspaceLayer::Tiling;\n\n            if let Some(direction) = direction {\n                self.add_container_with_direction(\n                    container,\n                    Some(target_workspace_idx),\n                    direction,\n                )?;\n            } else {\n                target_workspace.add_container_to_back(container);\n            }\n        }\n\n        if follow {\n            self.focus_workspace(target_workspace_idx)?;\n        }\n\n        Ok(())\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn focus_workspace(&mut self, idx: usize) -> eyre::Result<()> {\n        tracing::info!(\"focusing workspace\");\n\n        {\n            let workspaces = self.workspaces_mut();\n\n            if workspaces.get(idx).is_none() {\n                workspaces.resize(idx + 1, Workspace::default());\n            }\n            self.last_focused_workspace = Some(self.workspaces.focused_idx());\n            self.workspaces.focus(idx);\n        }\n\n        // Always set the latest known name when creating the workspace for the first time\n        {\n            let name = { self.workspace_names.get(&idx).cloned() };\n            if name.is_some() {\n                self.workspaces_mut()\n                    .get_mut(idx)\n                    .ok_or_eyre(\"there is no workspace\")?\n                    .name = name;\n            }\n        }\n\n        Ok(())\n    }\n\n    pub fn new_workspace_idx(&self) -> usize {\n        self.workspaces().len()\n    }\n\n    pub fn update_focused_workspace(&mut self, offset: Option<Rect>) -> eyre::Result<()> {\n        let offset = if self.work_area_offset.is_some() {\n            self.work_area_offset\n        } else {\n            offset\n        };\n\n        let focused_workspace_idx = self.focused_workspace_idx();\n        self.update_workspace_globals(focused_workspace_idx, offset);\n        self.focused_workspace_mut()\n            .ok_or_eyre(\"there is no workspace\")?\n            .update()?;\n\n        Ok(())\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_add_container() {\n        let mut m = Monitor::new(\n            0,\n            Rect::default(),\n            Rect::default(),\n            \"TestMonitor\".to_string(),\n            \"TestDevice\".to_string(),\n            \"TestDeviceID\".to_string(),\n            Some(\"TestMonitorID\".to_string()),\n        );\n\n        // Add container to the default workspace\n        m.add_container(Container::default(), Some(0)).unwrap();\n\n        // Should contain a container in the current focused workspace\n        let workspace = m.focused_workspace_mut().unwrap();\n        assert_eq!(workspace.containers().len(), 1);\n    }\n\n    #[test]\n    fn test_remove_workspace_by_idx() {\n        let mut m = Monitor::new(\n            0,\n            Rect::default(),\n            Rect::default(),\n            \"TestMonitor\".to_string(),\n            \"TestDevice\".to_string(),\n            \"TestDeviceID\".to_string(),\n            Some(\"TestMonitorID\".to_string()),\n        );\n\n        let new_workspace_index = m.new_workspace_idx();\n        assert_eq!(new_workspace_index, 1);\n\n        // Create workspace 2\n        m.focus_workspace(new_workspace_index).unwrap();\n\n        // Should have 2 workspaces\n        assert_eq!(m.workspaces().len(), 2);\n\n        // Create workspace 3\n        m.focus_workspace(new_workspace_index + 1).unwrap();\n\n        // Should have 3 workspaces\n        assert_eq!(m.workspaces().len(), 3);\n\n        // Remove workspace 1\n        m.remove_workspace_by_idx(1);\n\n        // Should have only 2 workspaces\n        assert_eq!(m.workspaces().len(), 2);\n    }\n\n    #[test]\n    fn test_remove_workspaces() {\n        let mut m = Monitor::new(\n            0,\n            Rect::default(),\n            Rect::default(),\n            \"TestMonitor\".to_string(),\n            \"TestDevice\".to_string(),\n            \"TestDeviceID\".to_string(),\n            Some(\"TestMonitorID\".to_string()),\n        );\n\n        let new_workspace_index = m.new_workspace_idx();\n        assert_eq!(new_workspace_index, 1);\n\n        // Create workspace 2\n        m.focus_workspace(new_workspace_index).unwrap();\n\n        // Should have 2 workspaces\n        assert_eq!(m.workspaces().len(), 2);\n\n        // Create workspace 3\n        m.focus_workspace(new_workspace_index + 1).unwrap();\n\n        // Should have 3 workspaces\n        assert_eq!(m.workspaces().len(), 3);\n\n        // Remove all workspaces\n        m.remove_workspaces();\n\n        // All workspaces should be removed\n        assert_eq!(m.workspaces().len(), 0);\n    }\n\n    #[test]\n    fn test_remove_nonexistent_workspace() {\n        let mut m = Monitor::new(\n            0,\n            Rect::default(),\n            Rect::default(),\n            \"TestMonitor\".to_string(),\n            \"TestDevice\".to_string(),\n            \"TestDeviceID\".to_string(),\n            Some(\"TestMonitorID\".to_string()),\n        );\n\n        // Try to remove a workspace that doesn't exist\n        let removed_workspace = m.remove_workspace_by_idx(1);\n\n        // Should return None since there is no workspace at index 1\n        assert!(removed_workspace.is_none());\n    }\n\n    #[test]\n    fn test_focus_workspace() {\n        let mut m = Monitor::new(\n            0,\n            Rect::default(),\n            Rect::default(),\n            \"TestMonitor\".to_string(),\n            \"TestDevice\".to_string(),\n            \"TestDeviceID\".to_string(),\n            Some(\"TestMonitorID\".to_string()),\n        );\n\n        let new_workspace_index = m.new_workspace_idx();\n        assert_eq!(new_workspace_index, 1);\n\n        // Focus workspace 2\n        m.focus_workspace(new_workspace_index).unwrap();\n\n        // Should have 2 workspaces\n        assert_eq!(m.workspaces().len(), 2);\n\n        // Should be focused on workspace 2\n        assert_eq!(m.focused_workspace_idx(), 1);\n    }\n\n    #[test]\n    fn test_new_workspace_idx() {\n        let m = Monitor::new(\n            0,\n            Rect::default(),\n            Rect::default(),\n            \"TestMonitor\".to_string(),\n            \"TestDevice\".to_string(),\n            \"TestDeviceID\".to_string(),\n            Some(\"TestMonitorID\".to_string()),\n        );\n\n        let new_workspace_index = m.new_workspace_idx();\n\n        // Should be the last workspace index: 1\n        assert_eq!(new_workspace_index, 1);\n    }\n\n    #[test]\n    fn test_move_container_to_workspace() {\n        let mut m = Monitor::new(\n            0,\n            Rect::default(),\n            Rect::default(),\n            \"TestMonitor\".to_string(),\n            \"TestDevice\".to_string(),\n            \"TestDeviceID\".to_string(),\n            Some(\"TestMonitorID\".to_string()),\n        );\n\n        let new_workspace_index = m.new_workspace_idx();\n        assert_eq!(new_workspace_index, 1);\n\n        {\n            // Create workspace 1 and add 3 containers\n            let workspace = m.focused_workspace_mut().unwrap();\n            for _ in 0..3 {\n                let container = Container::default();\n                workspace.add_container_to_back(container);\n            }\n\n            // Should have 3 containers in workspace 1\n            assert_eq!(m.focused_workspace().unwrap().containers().len(), 3);\n        }\n\n        // Create and focus workspace 2\n        m.focus_workspace(new_workspace_index).unwrap();\n\n        // Focus workspace 1\n        m.focus_workspace(0).unwrap();\n\n        // Move container to workspace 2\n        m.move_container_to_workspace(1, true, None).unwrap();\n\n        // Should be focused on workspace 2\n        assert_eq!(m.focused_workspace_idx(), 1);\n\n        // Workspace 2 should have 1 container now\n        assert_eq!(m.focused_workspace().unwrap().containers().len(), 1);\n\n        // Move to workspace 1\n        m.focus_workspace(0).unwrap();\n\n        // Workspace 1 should have 2 containers\n        assert_eq!(m.focused_workspace().unwrap().containers().len(), 2);\n\n        // Move a another container from workspace 1 to workspace 2 without following\n        m.move_container_to_workspace(1, false, None).unwrap();\n\n        // Should have 1 container\n        assert_eq!(m.focused_workspace().unwrap().containers().len(), 1);\n\n        // Should still be focused on workspace 1\n        assert_eq!(m.focused_workspace_idx(), 0);\n\n        // Switch to workspace 2\n        m.focus_workspace(1).unwrap();\n\n        // Workspace 2 should now have 2 containers\n        assert_eq!(m.focused_workspace().unwrap().containers().len(), 2);\n    }\n\n    #[test]\n    fn test_move_container_to_nonexistent_workspace() {\n        let mut m = Monitor::new(\n            0,\n            Rect::default(),\n            Rect::default(),\n            \"TestMonitor\".to_string(),\n            \"TestDevice\".to_string(),\n            \"TestDeviceID\".to_string(),\n            Some(\"TestMonitorID\".to_string()),\n        );\n\n        {\n            // Create workspace 1 and add 3 containers\n            let workspace = m.focused_workspace_mut().unwrap();\n            for _ in 0..3 {\n                let container = Container::default();\n                workspace.add_container_to_back(container);\n            }\n\n            // Should have 3 containers in workspace 1\n            assert_eq!(m.focused_workspace().unwrap().containers().len(), 3);\n        }\n\n        // Should only have 1 workspace\n        assert_eq!(m.workspaces().len(), 1);\n\n        // Try to move a container to a workspace that doesn't exist\n        m.move_container_to_workspace(8, true, None).unwrap();\n\n        // Should have 9 workspaces now\n        assert_eq!(m.workspaces().len(), 9);\n\n        // Should be focused on workspace 8\n        assert_eq!(m.focused_workspace_idx(), 8);\n\n        // Should have 1 container in workspace 8\n        assert_eq!(m.focused_workspace().unwrap().containers().len(), 1);\n    }\n\n    #[test]\n    fn test_ensure_workspace_count_workspace_contains_two_workspaces() {\n        let mut m = Monitor::new(\n            0,\n            Rect::default(),\n            Rect::default(),\n            \"TestMonitor\".to_string(),\n            \"TestDevice\".to_string(),\n            \"TestDeviceID\".to_string(),\n            Some(\"TestMonitorID\".to_string()),\n        );\n\n        // Create and focus another workspace\n        let new_workspace_index = m.new_workspace_idx();\n        m.focus_workspace(new_workspace_index).unwrap();\n\n        // Should have 2 workspaces now\n        assert_eq!(m.workspaces().len(), 2, \"Monitor should have 2 workspaces\");\n\n        // Ensure the monitor has at least 5 workspaces\n        m.ensure_workspace_count(5);\n\n        // Monitor should have 5 workspaces\n        assert_eq!(m.workspaces().len(), 5, \"Monitor should have 5 workspaces\");\n    }\n\n    #[test]\n    fn test_ensure_workspace_count_only_default_workspace() {\n        let mut m = Monitor::new(\n            0,\n            Rect::default(),\n            Rect::default(),\n            \"TestMonitor\".to_string(),\n            \"TestDevice\".to_string(),\n            \"TestDeviceID\".to_string(),\n            Some(\"TestMonitorID\".to_string()),\n        );\n\n        // Ensure the monitor has at least 5 workspaces\n        m.ensure_workspace_count(5);\n\n        // Monitor should have 5 workspaces\n        assert_eq!(m.workspaces().len(), 5, \"Monitor should have 5 workspaces\");\n\n        // Try to call the ensure workspace count again to ensure it doesn't change\n        m.ensure_workspace_count(3);\n        assert_eq!(m.workspaces().len(), 5, \"Monitor should have 5 workspaces\");\n    }\n}\n"
  },
  {
    "path": "komorebi/src/monitor_reconciliator/hidden.rs",
    "content": "use std::sync::mpsc;\nuse std::time::Duration;\n\nuse windows::Win32::Devices::Display::GUID_DEVINTERFACE_DISPLAY_ADAPTER;\nuse windows::Win32::Devices::Display::GUID_DEVINTERFACE_MONITOR;\nuse windows::Win32::Devices::Display::GUID_DEVINTERFACE_VIDEO_OUTPUT_ARRIVAL;\nuse windows::Win32::Foundation::HWND;\nuse windows::Win32::Foundation::LPARAM;\nuse windows::Win32::Foundation::LRESULT;\nuse windows::Win32::Foundation::WPARAM;\nuse windows::Win32::System::Power::POWERBROADCAST_SETTING;\nuse windows::Win32::System::SystemServices::GUID_LIDSWITCH_STATE_CHANGE;\nuse windows::Win32::UI::WindowsAndMessaging::CS_HREDRAW;\nuse windows::Win32::UI::WindowsAndMessaging::CS_VREDRAW;\nuse windows::Win32::UI::WindowsAndMessaging::DBT_CONFIGCHANGED;\nuse windows::Win32::UI::WindowsAndMessaging::DBT_DEVICEARRIVAL;\nuse windows::Win32::UI::WindowsAndMessaging::DBT_DEVICEREMOVECOMPLETE;\nuse windows::Win32::UI::WindowsAndMessaging::DBT_DEVNODES_CHANGED;\nuse windows::Win32::UI::WindowsAndMessaging::DBT_DEVTYP_DEVICEINTERFACE;\nuse windows::Win32::UI::WindowsAndMessaging::DEV_BROADCAST_DEVICEINTERFACE_W;\nuse windows::Win32::UI::WindowsAndMessaging::DefWindowProcW;\nuse windows::Win32::UI::WindowsAndMessaging::DispatchMessageW;\nuse windows::Win32::UI::WindowsAndMessaging::GetMessageW;\nuse windows::Win32::UI::WindowsAndMessaging::MSG;\nuse windows::Win32::UI::WindowsAndMessaging::PBT_APMRESUMEAUTOMATIC;\nuse windows::Win32::UI::WindowsAndMessaging::PBT_APMRESUMESUSPEND;\nuse windows::Win32::UI::WindowsAndMessaging::PBT_APMSUSPEND;\nuse windows::Win32::UI::WindowsAndMessaging::PBT_POWERSETTINGCHANGE;\nuse windows::Win32::UI::WindowsAndMessaging::REGISTER_NOTIFICATION_FLAGS;\nuse windows::Win32::UI::WindowsAndMessaging::SPI_SETWORKAREA;\nuse windows::Win32::UI::WindowsAndMessaging::TranslateMessage;\nuse windows::Win32::UI::WindowsAndMessaging::WM_DEVICECHANGE;\nuse windows::Win32::UI::WindowsAndMessaging::WM_DISPLAYCHANGE;\nuse windows::Win32::UI::WindowsAndMessaging::WM_POWERBROADCAST;\nuse windows::Win32::UI::WindowsAndMessaging::WM_SETTINGCHANGE;\nuse windows::Win32::UI::WindowsAndMessaging::WM_WTSSESSION_CHANGE;\nuse windows::Win32::UI::WindowsAndMessaging::WNDCLASSW;\nuse windows::Win32::UI::WindowsAndMessaging::WTS_SESSION_LOCK;\nuse windows::Win32::UI::WindowsAndMessaging::WTS_SESSION_UNLOCK;\nuse windows::core::PCWSTR;\n\nuse crate::WindowsApi;\nuse crate::monitor_reconciliator;\nuse crate::windows_api;\n\n// This is a hidden window specifically spawned to listen to system-wide events related to monitors\n#[derive(Debug, Clone, Copy)]\npub struct Hidden {\n    pub hwnd: isize,\n}\n\nimpl From<isize> for Hidden {\n    fn from(hwnd: isize) -> Self {\n        Self { hwnd }\n    }\n}\n\nimpl Hidden {\n    pub const fn hwnd(self) -> HWND {\n        HWND(windows_api::as_ptr!(self.hwnd))\n    }\n\n    pub fn create(name: &str) -> color_eyre::Result<Self> {\n        let name: Vec<u16> = format!(\"{name}\\0\").encode_utf16().collect();\n        let class_name = PCWSTR(name.as_ptr());\n\n        let h_module = WindowsApi::module_handle_w()?;\n        let window_class = WNDCLASSW {\n            hInstance: h_module.into(),\n            lpszClassName: class_name,\n            style: CS_HREDRAW | CS_VREDRAW,\n            lpfnWndProc: Some(Self::callback),\n            hbrBackground: WindowsApi::create_solid_brush(0),\n            ..Default::default()\n        };\n\n        let _ = WindowsApi::register_class_w(&window_class)?;\n\n        let (hwnd_sender, hwnd_receiver) = mpsc::channel();\n\n        let instance = h_module.0 as isize;\n        std::thread::spawn(move || -> color_eyre::Result<()> {\n            let hwnd = WindowsApi::create_hidden_window(PCWSTR(name.as_ptr()), instance)?;\n            hwnd_sender.send(hwnd)?;\n\n            let mut msg: MSG = MSG::default();\n\n            loop {\n                unsafe {\n                    if !GetMessageW(&mut msg, None, 0, 0).as_bool() {\n                        tracing::debug!(\"hidden window event processing thread shutdown\");\n                        break;\n                    };\n                    // TODO: error handling\n                    let _ = TranslateMessage(&msg);\n                    DispatchMessageW(&msg);\n                }\n\n                std::thread::sleep(Duration::from_millis(10))\n            }\n\n            Ok(())\n        });\n\n        let hwnd = hwnd_receiver.recv()?;\n\n        // Register Session Lock/Unlock events\n        WindowsApi::wts_register_session_notification(hwnd)?;\n\n        // Register Laptop lid open/close events\n        WindowsApi::register_power_setting_notification(\n            hwnd,\n            &GUID_LIDSWITCH_STATE_CHANGE,\n            REGISTER_NOTIFICATION_FLAGS(0),\n        )?;\n\n        // Register device interface events for multiple display related devices. Some of this\n        // device interfaces might not be needed but it doesn't hurt to have them in case some user\n        // uses some output device as monitor that falls into one of these device interface class\n        // GUID.\n        let monitor_filter = DEV_BROADCAST_DEVICEINTERFACE_W {\n            dbcc_size: std::mem::size_of::<DEV_BROADCAST_DEVICEINTERFACE_W>() as u32,\n            dbcc_devicetype: DBT_DEVTYP_DEVICEINTERFACE.0,\n            dbcc_reserved: 0,\n            dbcc_classguid: GUID_DEVINTERFACE_MONITOR,\n            dbcc_name: [0; 1],\n        };\n        let display_adapter_filter = DEV_BROADCAST_DEVICEINTERFACE_W {\n            dbcc_size: std::mem::size_of::<DEV_BROADCAST_DEVICEINTERFACE_W>() as u32,\n            dbcc_devicetype: DBT_DEVTYP_DEVICEINTERFACE.0,\n            dbcc_reserved: 0,\n            dbcc_classguid: GUID_DEVINTERFACE_DISPLAY_ADAPTER,\n            dbcc_name: [0; 1],\n        };\n        let video_output_filter = DEV_BROADCAST_DEVICEINTERFACE_W {\n            dbcc_size: std::mem::size_of::<DEV_BROADCAST_DEVICEINTERFACE_W>() as u32,\n            dbcc_devicetype: DBT_DEVTYP_DEVICEINTERFACE.0,\n            dbcc_reserved: 0,\n            dbcc_classguid: GUID_DEVINTERFACE_VIDEO_OUTPUT_ARRIVAL,\n            dbcc_name: [0; 1],\n        };\n        WindowsApi::register_device_notification(\n            hwnd,\n            monitor_filter,\n            REGISTER_NOTIFICATION_FLAGS(0),\n        )?;\n        WindowsApi::register_device_notification(\n            hwnd,\n            display_adapter_filter,\n            REGISTER_NOTIFICATION_FLAGS(0),\n        )?;\n        WindowsApi::register_device_notification(\n            hwnd,\n            video_output_filter,\n            REGISTER_NOTIFICATION_FLAGS(0),\n        )?;\n\n        Ok(Self { hwnd })\n    }\n\n    pub extern \"system\" fn callback(\n        window: HWND,\n        message: u32,\n        wparam: WPARAM,\n        lparam: LPARAM,\n    ) -> LRESULT {\n        unsafe {\n            match message {\n                WM_POWERBROADCAST => {\n                    match wparam.0 as u32 {\n                        // Automatic: System resumed itself from sleep or hibernation\n                        // Suspend: User resumed system from sleep or hibernation\n                        PBT_APMRESUMEAUTOMATIC | PBT_APMRESUMESUSPEND => {\n                            tracing::debug!(\n                                \"WM_POWERBROADCAST event received - resume from suspend\"\n                            );\n                            monitor_reconciliator::send_notification(\n                                monitor_reconciliator::MonitorNotification::ResumingFromSuspendedState,\n                            );\n                            LRESULT(0)\n                        }\n                        // Computer is entering a suspended state\n                        PBT_APMSUSPEND => {\n                            tracing::debug!(\n                                \"WM_POWERBROADCAST event received - entering suspended state\"\n                            );\n                            monitor_reconciliator::send_notification(\n                                monitor_reconciliator::MonitorNotification::EnteringSuspendedState,\n                            );\n                            LRESULT(0)\n                        }\n                        // Monitor change power status\n                        PBT_POWERSETTINGCHANGE => {\n                            if let POWERBROADCAST_SETTING {\n                                PowerSetting: GUID_LIDSWITCH_STATE_CHANGE,\n                                DataLength: _,\n                                Data: [0],\n                            } = *(lparam.0 as *const POWERBROADCAST_SETTING)\n                            {\n                                tracing::debug!(\n                                    \"WM_POWERBROADCAST event received - laptop lid closed\"\n                                );\n                                monitor_reconciliator::send_notification(\n                                    monitor_reconciliator::MonitorNotification::DisplayConnectionChange,\n                                );\n                            } else if let POWERBROADCAST_SETTING {\n                                PowerSetting: GUID_LIDSWITCH_STATE_CHANGE,\n                                DataLength: _,\n                                Data: [1],\n                            } = *(lparam.0 as *const POWERBROADCAST_SETTING)\n                            {\n                                tracing::debug!(\n                                    \"WM_POWERBROADCAST event received - laptop lid opened\"\n                                );\n                                monitor_reconciliator::send_notification(\n                                    monitor_reconciliator::MonitorNotification::DisplayConnectionChange,\n                                );\n                            }\n                            LRESULT(0)\n                        }\n                        _ => LRESULT(0),\n                    }\n                }\n                WM_WTSSESSION_CHANGE => {\n                    match wparam.0 as u32 {\n                        WTS_SESSION_LOCK => {\n                            tracing::debug!(\n                                \"WM_WTSSESSION_CHANGE event received with WTS_SESSION_LOCK - screen locked\"\n                            );\n\n                            monitor_reconciliator::send_notification(\n                                monitor_reconciliator::MonitorNotification::SessionLocked,\n                            );\n                        }\n                        WTS_SESSION_UNLOCK => {\n                            tracing::debug!(\n                                \"WM_WTSSESSION_CHANGE event received with WTS_SESSION_UNLOCK - screen unlocked\"\n                            );\n\n                            monitor_reconciliator::send_notification(\n                                monitor_reconciliator::MonitorNotification::SessionUnlocked,\n                            );\n                        }\n                        _ => {}\n                    }\n\n                    LRESULT(0)\n                }\n                // This event gets sent when:\n                // - The scaling factor on a display changes\n                // - The resolution on a display changes\n                // - A monitor is added\n                // - A monitor is removed\n                // Since WM_DEVICECHANGE also notifies on monitor changes, we only handle scaling\n                // and resolution changes here\n                WM_DISPLAYCHANGE => {\n                    tracing::debug!(\n                        \"WM_DISPLAYCHANGE event received with wparam: {}- work area or display resolution changed\",\n                        wparam.0\n                    );\n\n                    monitor_reconciliator::send_notification(\n                        monitor_reconciliator::MonitorNotification::ResolutionScalingChanged,\n                    );\n                    LRESULT(0)\n                }\n                // Unfortunately this is the event sent with ButteryTaskbar which I use a lot\n                // Original idea from https://stackoverflow.com/a/33762334\n                WM_SETTINGCHANGE => {\n                    #[allow(clippy::cast_possible_truncation)]\n                    if wparam.0 as u32 == SPI_SETWORKAREA.0 {\n                        tracing::debug!(\n                            \"WM_SETTINGCHANGE event received with SPI_SETWORKAREA - work area changed (probably butterytaskbar or something similar)\"\n                        );\n\n                        monitor_reconciliator::send_notification(\n                            monitor_reconciliator::MonitorNotification::WorkAreaChanged,\n                        );\n                    }\n                    LRESULT(0)\n                }\n                // This event + wparam combo is sent 4 times when a monitor is added based on my testing on win11\n                // Original idea from https://stackoverflow.com/a/33762334\n                WM_DEVICECHANGE => {\n                    #[allow(clippy::cast_possible_truncation)]\n                    let event = wparam.0 as u32;\n                    if event == DBT_DEVNODES_CHANGED\n                        || event == DBT_CONFIGCHANGED\n                        || event == DBT_DEVICEARRIVAL\n                        || event == DBT_DEVICEREMOVECOMPLETE\n                    {\n                        tracing::debug!(\n                            \"WM_DEVICECHANGE event received with one of [DBT_DEVNODES_CHANGED, DBT_CONFIGCHANGED, DBT_DEVICEARRIVAL, DBT_DEVICEREMOVECOMPLETE] - display added or removed\"\n                        );\n                        monitor_reconciliator::send_notification(\n                            monitor_reconciliator::MonitorNotification::DisplayConnectionChange,\n                        );\n                    }\n\n                    LRESULT(0)\n                }\n                _ => DefWindowProcW(window, message, wparam, lparam),\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "komorebi/src/monitor_reconciliator/mod.rs",
    "content": "#![deny(clippy::unwrap_used, clippy::expect_used)]\n\nuse crate::DISPLAY_INDEX_PREFERENCES;\nuse crate::DUPLICATE_MONITOR_SERIAL_IDS;\nuse crate::Notification;\nuse crate::NotificationEvent;\nuse crate::WORKSPACE_MATCHING_RULES;\nuse crate::WindowManager;\nuse crate::WindowsApi;\nuse crate::border_manager;\nuse crate::config_generation::WorkspaceMatchingRule;\nuse crate::core::Rect;\nuse crate::monitor;\nuse crate::monitor::Monitor;\nuse crate::monitor_reconciliator::hidden::Hidden;\nuse crate::notify_subscribers;\nuse crate::state::State;\nuse crossbeam_channel::Receiver;\nuse crossbeam_channel::Sender;\nuse crossbeam_utils::atomic::AtomicConsume;\nuse parking_lot::Mutex;\nuse serde::Deserialize;\nuse serde::Serialize;\nuse std::collections::HashMap;\nuse std::sync::Arc;\nuse std::sync::OnceLock;\nuse std::sync::atomic::AtomicBool;\nuse std::sync::atomic::AtomicI64;\nuse std::sync::atomic::Ordering;\n\npub mod hidden;\n\n#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n#[serde(tag = \"type\", content = \"content\")]\npub enum MonitorNotification {\n    ResolutionScalingChanged,\n    WorkAreaChanged,\n    DisplayConnectionChange,\n    EnteringSuspendedState,\n    ResumingFromSuspendedState,\n    SessionLocked,\n    SessionUnlocked,\n}\n\nstatic ACTIVE: AtomicBool = AtomicBool::new(true);\n\n/// Timestamp (epoch millis) of the last DisplayConnectionChange notification.\n/// Used to suppress OS-initiated window minimizes during transient display events.\nstatic LAST_DISPLAY_CHANGE_TIMESTAMP: AtomicI64 = AtomicI64::new(0);\n\nstatic CHANNEL: OnceLock<(Sender<MonitorNotification>, Receiver<MonitorNotification>)> =\n    OnceLock::new();\n\nstatic MONITOR_CACHE: OnceLock<Mutex<HashMap<String, Monitor>>> = OnceLock::new();\n\npub fn channel() -> &'static (Sender<MonitorNotification>, Receiver<MonitorNotification>) {\n    CHANNEL.get_or_init(|| crossbeam_channel::bounded(20))\n}\n\nfn event_tx() -> Sender<MonitorNotification> {\n    channel().0.clone()\n}\n\nfn event_rx() -> Receiver<MonitorNotification> {\n    channel().1.clone()\n}\n\npub fn send_notification(notification: MonitorNotification) {\n    if matches!(\n        notification,\n        MonitorNotification::DisplayConnectionChange\n            | MonitorNotification::ResumingFromSuspendedState\n            | MonitorNotification::SessionUnlocked\n    ) {\n        let now = std::time::SystemTime::now()\n            .duration_since(std::time::UNIX_EPOCH)\n            .unwrap_or_default()\n            .as_millis() as i64;\n        LAST_DISPLAY_CHANGE_TIMESTAMP.store(now, Ordering::SeqCst);\n    }\n\n    if event_tx().try_send(notification).is_err() {\n        tracing::warn!(\"channel is full; dropping notification\")\n    }\n}\n\n/// Returns true if a display connection change event was received within the\n/// last `grace_period` duration. This is used by the event processor to avoid\n/// treating OS-initiated minimizes (caused by transient monitor disconnects)\n/// as user-initiated minimizes.\npub fn display_change_in_progress(grace_period: std::time::Duration) -> bool {\n    let last = LAST_DISPLAY_CHANGE_TIMESTAMP.load(Ordering::SeqCst);\n    if last == 0 {\n        return false;\n    }\n    let now = std::time::SystemTime::now()\n        .duration_since(std::time::UNIX_EPOCH)\n        .unwrap_or_default()\n        .as_millis() as i64;\n    (now - last) < grace_period.as_millis() as i64\n}\n\npub fn insert_in_monitor_cache(serial_or_device_id: &str, monitor: Monitor) {\n    let dip = DISPLAY_INDEX_PREFERENCES.read();\n    let mut dip_ids = dip.values();\n    let preferred_id = if dip_ids.any(|id| id.eq(&monitor.device_id)) {\n        monitor.device_id.clone()\n    } else if dip_ids.any(|id| Some(id) == monitor.serial_number_id.as_ref()) {\n        monitor.serial_number_id.clone().unwrap_or_default()\n    } else {\n        serial_or_device_id.to_string()\n    };\n    let mut monitor_cache = MONITOR_CACHE\n        .get_or_init(|| Mutex::new(HashMap::new()))\n        .lock();\n\n    monitor_cache.insert(preferred_id, monitor);\n}\n\npub fn attached_display_devices<F, I>(display_provider: F) -> color_eyre::Result<Vec<Monitor>>\nwhere\n    F: Fn() -> I + Copy,\n    I: Iterator<Item = Result<win32_display_data::Device, win32_display_data::Error>>,\n{\n    let mut attempts = 0;\n\n    let (displays, errors) = loop {\n        let (displays, errors): (Vec<_>, Vec<_>) = display_provider().partition(Result::is_ok);\n\n        if errors.is_empty() {\n            break (displays, errors);\n        }\n\n        for err in &errors {\n            if let Err(e) = err {\n                tracing::warn!(\n                    \"enumerating display in reconciliator (attempt {}): {:?}\",\n                    attempts + 1,\n                    e\n                );\n            }\n        }\n\n        if attempts < 5 {\n            attempts += 1;\n            std::thread::sleep(std::time::Duration::from_millis(150));\n            continue;\n        }\n\n        break (displays, errors);\n    };\n\n    if !errors.is_empty() {\n        return Err(color_eyre::eyre::eyre!(\n            \"could not successfully enumerate all displays\"\n        ));\n    }\n\n    let all_displays = displays.into_iter().map(Result::unwrap).collect::<Vec<_>>();\n\n    let mut serial_id_map = HashMap::new();\n\n    for d in &all_displays {\n        if let Some(id) = &d.serial_number_id {\n            *serial_id_map.entry(id.clone()).or_insert(0) += 1;\n        }\n    }\n\n    for d in &all_displays {\n        if let Some(id) = &d.serial_number_id\n            && serial_id_map.get(id).copied().unwrap_or_default() > 1\n        {\n            let mut dupes = DUPLICATE_MONITOR_SERIAL_IDS.write();\n            if !dupes.contains(id) {\n                (*dupes).push(id.clone());\n            }\n        }\n    }\n\n    Ok(all_displays\n        .into_iter()\n        .map(|mut display| {\n            let path = display.device_path;\n\n            let (device, device_id) = if path.is_empty() {\n                (String::from(\"UNKNOWN\"), String::from(\"UNKNOWN\"))\n            } else {\n                let mut split: Vec<_> = path.split('#').collect();\n                split.remove(0);\n                split.remove(split.len() - 1);\n                let device = split[0].to_string();\n                let device_id = split.join(\"-\");\n                (device, device_id)\n            };\n\n            let name = display.device_name.trim_start_matches(r\"\\\\.\\\").to_string();\n            let name = name.split('\\\\').collect::<Vec<_>>()[0].to_string();\n\n            if let Some(id) = &display.serial_number_id {\n                let dupes = DUPLICATE_MONITOR_SERIAL_IDS.read();\n                if dupes.contains(id) {\n                    display.serial_number_id = None;\n                }\n            }\n\n            monitor::new(\n                display.hmonitor,\n                display.size.into(),\n                display.work_area_size.into(),\n                name,\n                device,\n                device_id,\n                display.serial_number_id,\n            )\n        })\n        .collect::<Vec<_>>())\n}\n\npub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result<()> {\n    #[allow(clippy::expect_used)]\n    Hidden::create(\"komorebi-hidden\")?;\n\n    tracing::info!(\"created hidden window to listen for monitor-related events\");\n\n    std::thread::spawn(move || {\n        loop {\n            match handle_notifications(wm.clone(), win32_display_data::connected_displays_all) {\n                Ok(()) => {\n                    tracing::warn!(\"restarting finished thread\");\n                }\n                Err(error) => {\n                    if cfg!(debug_assertions) {\n                        tracing::error!(\"restarting failed thread: {:?}\", error)\n                    } else {\n                        tracing::error!(\"restarting failed thread: {}\", error)\n                    }\n                }\n            }\n        }\n    });\n\n    Ok(())\n}\n\npub fn handle_notifications<F, I>(\n    wm: Arc<Mutex<WindowManager>>,\n    display_provider: F,\n) -> color_eyre::Result<()>\nwhere\n    F: Fn() -> I + Copy,\n    I: Iterator<Item = Result<win32_display_data::Device, win32_display_data::Error>>,\n{\n    tracing::info!(\"listening\");\n\n    let receiver = event_rx();\n\n    'receiver: for notification in receiver {\n        if !ACTIVE.load_consume()\n            && matches!(\n                notification,\n                MonitorNotification::ResumingFromSuspendedState\n                    | MonitorNotification::SessionUnlocked\n            )\n        {\n            tracing::debug!(\n                \"reactivating reconciliator - system has resumed from suspended state or session has been unlocked\"\n            );\n\n            ACTIVE.store(true, Ordering::SeqCst);\n            border_manager::send_notification(None);\n        }\n\n        // Keep reference to Arc for potential re-locking\n        let wm_arc = Arc::clone(&wm);\n        let mut wm = wm.lock();\n\n        let initial_state = State::from(wm.as_ref());\n\n        match notification {\n            MonitorNotification::EnteringSuspendedState | MonitorNotification::SessionLocked => {\n                tracing::debug!(\n                    \"deactivating reconciliator until system resumes from suspended state or session is unlocked\"\n                );\n                ACTIVE.store(false, Ordering::SeqCst);\n            }\n            MonitorNotification::WorkAreaChanged => {\n                tracing::debug!(\"handling work area changed notification\");\n                let offset = wm.work_area_offset;\n                for monitor in wm.monitors_mut() {\n                    let mut should_update = false;\n\n                    // Update work areas as necessary\n                    if let Ok(reference) = WindowsApi::monitor(monitor.id)\n                        && reference.work_area_size != monitor.work_area_size\n                    {\n                        monitor.work_area_size = Rect {\n                            left: reference.work_area_size.left,\n                            top: reference.work_area_size.top,\n                            right: reference.work_area_size.right,\n                            bottom: reference.work_area_size.bottom,\n                        };\n\n                        should_update = true;\n                    }\n\n                    if should_update {\n                        tracing::info!(\"updated work area for {}\", monitor.device_id);\n                        monitor.update_focused_workspace(offset)?;\n                        border_manager::send_notification(None);\n                    } else {\n                        tracing::debug!(\n                            \"work areas match, reconciliation not required for {}\",\n                            monitor.device_id\n                        );\n                    }\n                }\n            }\n            MonitorNotification::ResolutionScalingChanged => {\n                tracing::debug!(\"handling resolution/scaling changed notification\");\n                let offset = wm.work_area_offset;\n                for monitor in wm.monitors_mut() {\n                    let mut should_update = false;\n\n                    // Update sizes and work areas as necessary\n                    if let Ok(reference) = WindowsApi::monitor(monitor.id) {\n                        if reference.work_area_size != monitor.work_area_size {\n                            monitor.work_area_size = Rect {\n                                left: reference.work_area_size.left,\n                                top: reference.work_area_size.top,\n                                right: reference.work_area_size.right,\n                                bottom: reference.work_area_size.bottom,\n                            };\n\n                            should_update = true;\n                        }\n\n                        if reference.size != monitor.size {\n                            monitor.size = Rect {\n                                left: reference.size.left,\n                                top: reference.size.top,\n                                right: reference.size.right,\n                                bottom: reference.size.bottom,\n                            };\n\n                            should_update = true;\n                        }\n                    }\n\n                    if should_update {\n                        tracing::info!(\n                            \"updated monitor resolution/scaling for {}\",\n                            monitor.device_id\n                        );\n\n                        monitor.update_focused_workspace(offset)?;\n                        border_manager::send_notification(None);\n                    } else {\n                        tracing::debug!(\n                            \"resolutions match, reconciliation not required for {}\",\n                            monitor.device_id\n                        );\n                    }\n                }\n            }\n            // this is handled above if the reconciliator is paused but we should still check if\n            // there were any changes to the connected monitors while the system was\n            // suspended/locked.\n            MonitorNotification::ResumingFromSuspendedState\n            | MonitorNotification::SessionUnlocked\n            | MonitorNotification::DisplayConnectionChange => {\n                tracing::debug!(\"handling display connection change notification\");\n                let mut monitor_cache = MONITOR_CACHE\n                    .get_or_init(|| Mutex::new(HashMap::new()))\n                    .lock();\n\n                let initial_monitor_count = wm.monitors().len();\n\n                // Get the currently attached display devices\n                let attached_devices = attached_display_devices(display_provider)?;\n\n                // Make sure that in our state any attached displays have the latest Win32 data\n                for monitor in wm.monitors_mut() {\n                    for attached in &attached_devices {\n                        let serial_number_ids_match = if let (Some(attached_snid), Some(m_snid)) =\n                            (&attached.serial_number_id, &monitor.serial_number_id)\n                        {\n                            attached_snid.eq(m_snid)\n                        } else {\n                            false\n                        };\n\n                        if serial_number_ids_match || attached.device_id.eq(&monitor.device_id) {\n                            monitor.id = attached.id;\n                            monitor.device = attached.device.clone();\n                            monitor.device_id = attached.device_id.clone();\n                            monitor.serial_number_id = attached.serial_number_id.clone();\n                            monitor.name = attached.name.clone();\n                            monitor.size = attached.size;\n                            monitor.work_area_size = attached.work_area_size;\n                        }\n                    }\n                }\n\n                if initial_monitor_count == attached_devices.len() {\n                    tracing::debug!(\"monitor counts match, reconciliation not required\");\n                    drop(wm);\n                    continue 'receiver;\n                }\n\n                if attached_devices.is_empty() {\n                    tracing::debug!(\n                        \"no devices found, skipping reconciliation to avoid breaking state\"\n                    );\n                    drop(wm);\n                    continue 'receiver;\n                }\n\n                // Handle potential monitor removal with verification\n                let attached_devices = if initial_monitor_count > attached_devices.len() {\n                    tracing::info!(\n                        \"potential monitor removal detected ({initial_monitor_count} vs {}), verifying in 3s\",\n                        attached_devices.len()\n                    );\n\n                    // Release locks before waiting\n                    drop(wm);\n                    drop(monitor_cache);\n\n                    // Wait 3 seconds for display state to stabilize\n                    std::thread::sleep(std::time::Duration::from_secs(3));\n\n                    // Re-query the Win32 display APIs\n                    let re_queried_devices = match attached_display_devices(display_provider) {\n                        Ok(devices) => devices,\n                        Err(e) => {\n                            tracing::error!(\"failed to re-query display devices: {}\", e);\n                            continue 'receiver;\n                        }\n                    };\n\n                    tracing::debug!(\n                        \"after verification: wm had {} monitors, initial query found {}, re-query found {}\",\n                        initial_monitor_count,\n                        attached_devices.len(),\n                        re_queried_devices.len()\n                    );\n\n                    // If monitors are back, the removal was transient (spurious event)\n                    // Still try to restore state since windows might have been minimized\n                    if re_queried_devices.len() >= initial_monitor_count {\n                        tracing::info!(\n                            \"monitor removal was transient (spurious event), attempting state restoration. Initial: {}, Re-queried: {}\",\n                            initial_monitor_count,\n                            re_queried_devices.len()\n                        );\n\n                        // Re-acquire locks for state restoration\n                        wm = wm_arc.lock();\n\n                        // Update Win32 data for all monitors\n                        for monitor in wm.monitors_mut() {\n                            for attached in &re_queried_devices {\n                                let serial_number_ids_match =\n                                    if let (Some(attached_snid), Some(m_snid)) =\n                                        (&attached.serial_number_id, &monitor.serial_number_id)\n                                    {\n                                        attached_snid.eq(m_snid)\n                                    } else {\n                                        false\n                                    };\n\n                                if serial_number_ids_match\n                                    || attached.device_id.eq(&monitor.device_id)\n                                {\n                                    monitor.id = attached.id;\n                                    monitor.device = attached.device.clone();\n                                    monitor.device_id = attached.device_id.clone();\n                                    monitor.serial_number_id = attached.serial_number_id.clone();\n                                    monitor.name = attached.name.clone();\n                                    monitor.size = attached.size;\n                                    monitor.work_area_size = attached.work_area_size;\n                                }\n                            }\n                        }\n\n                        // Try to restore windows that might have been minimized\n                        let offset = wm.work_area_offset;\n                        for monitor in wm.monitors_mut() {\n                            let focused_workspace_idx = monitor.focused_workspace_idx();\n\n                            for (idx, workspace) in monitor.workspaces_mut().iter_mut().enumerate()\n                            {\n                                let is_focused_workspace = idx == focused_workspace_idx;\n\n                                if is_focused_workspace {\n                                    // Restore containers\n                                    for container in workspace.containers_mut() {\n                                        if let Some(window) = container.focused_window()\n                                            && WindowsApi::is_window(window.hwnd)\n                                        {\n                                            tracing::debug!(\n                                                \"restoring window after transient removal: {}\",\n                                                window.hwnd\n                                            );\n                                            WindowsApi::restore_window(window.hwnd);\n                                        } else if let Some(window) = container.focused_window() {\n                                            tracing::debug!(\n                                                \"skipping restore of invalid window: {}\",\n                                                window.hwnd\n                                            );\n                                        }\n                                    }\n\n                                    // Restore maximized window\n                                    if let Some(window) = &workspace.maximized_window\n                                        && WindowsApi::is_window(window.hwnd)\n                                    {\n                                        WindowsApi::restore_window(window.hwnd);\n                                    }\n\n                                    // Restore monocle container\n                                    if let Some(container) = &workspace.monocle_container\n                                        && let Some(window) = container.focused_window()\n                                        && WindowsApi::is_window(window.hwnd)\n                                    {\n                                        WindowsApi::restore_window(window.hwnd);\n                                    }\n\n                                    // Restore floating windows\n                                    for window in workspace.floating_windows() {\n                                        if WindowsApi::is_window(window.hwnd) {\n                                            WindowsApi::restore_window(window.hwnd);\n                                        }\n                                    }\n                                }\n                            }\n\n                            monitor.update_focused_workspace(offset)?;\n                        }\n\n                        border_manager::send_notification(None);\n                        continue 'receiver;\n                    }\n\n                    // If monitors are still missing, proceed with actual removal logic\n                    tracing::info!(\n                        \"verified monitor removal ({initial_monitor_count} vs {}), removing disconnected monitors\",\n                        re_queried_devices.len()\n                    );\n\n                    // Re-acquire locks for removal processing\n                    wm = wm_arc.lock();\n                    monitor_cache = MONITOR_CACHE\n                        .get_or_init(|| Mutex::new(HashMap::new()))\n                        .lock();\n\n                    // Make sure that in our state any attached displays have the latest Win32 data\n                    // We must do this again because we dropped the lock and are working with new data\n                    for monitor in wm.monitors_mut() {\n                        for attached in &re_queried_devices {\n                            let serial_number_ids_match =\n                                if let (Some(attached_snid), Some(m_snid)) =\n                                    (&attached.serial_number_id, &monitor.serial_number_id)\n                                {\n                                    attached_snid.eq(m_snid)\n                                } else {\n                                    false\n                                };\n\n                            if serial_number_ids_match || attached.device_id.eq(&monitor.device_id)\n                            {\n                                monitor.id = attached.id;\n                                monitor.device = attached.device.clone();\n                                monitor.device_id = attached.device_id.clone();\n                                monitor.serial_number_id = attached.serial_number_id.clone();\n                                monitor.name = attached.name.clone();\n                                monitor.size = attached.size;\n                                monitor.work_area_size = attached.work_area_size;\n                            }\n                        }\n                    }\n\n                    // Use re-queried devices for remaining logic\n                    re_queried_devices\n                } else {\n                    attached_devices\n                };\n\n                if initial_monitor_count > attached_devices.len() {\n                    tracing::info!(\"removing disconnected monitors\");\n\n                    // Windows to remove from `known_hwnds`\n                    let mut windows_to_remove = Vec::new();\n\n                    // Collect the ids in our state which aren't in the current attached display ids\n                    // These are monitors that have been removed\n                    let mut newly_removed_displays = vec![];\n\n                    for (m_idx, m) in wm.monitors().iter().enumerate() {\n                        if !attached_devices.iter().any(|attached| {\n                            attached.serial_number_id.eq(&m.serial_number_id)\n                                || attached.device_id.eq(&m.device_id)\n                        }) {\n                            let id = m\n                                .serial_number_id\n                                .as_ref()\n                                .map_or(m.device_id.clone(), |sn| sn.clone());\n\n                            newly_removed_displays.push(id.clone());\n\n                            let focused_workspace_idx = m.focused_workspace_idx();\n\n                            for (idx, workspace) in m.workspaces().iter().enumerate() {\n                                let is_focused_workspace = idx == focused_workspace_idx;\n                                let focused_container_idx = workspace.focused_container_idx();\n                                for (c_idx, container) in workspace.containers().iter().enumerate()\n                                {\n                                    let focused_window_idx = container.focused_window_idx();\n                                    for (w_idx, window) in container.windows().iter().enumerate() {\n                                        windows_to_remove.push(window.hwnd);\n                                        if is_focused_workspace\n                                            && c_idx == focused_container_idx\n                                            && w_idx == focused_window_idx\n                                        {\n                                            // Minimize the focused window since Windows might try\n                                            // to move it to another monitor if it was focused.\n                                            if window.is_focused() {\n                                                window.minimize();\n                                            }\n                                        }\n                                    }\n                                }\n\n                                if let Some(maximized) = &workspace.maximized_window {\n                                    windows_to_remove.push(maximized.hwnd);\n                                    // Minimize the focused window since Windows might try\n                                    // to move it to another monitor if it was focused.\n                                    if maximized.is_focused() {\n                                        maximized.minimize();\n                                    }\n                                }\n\n                                if let Some(container) = &workspace.monocle_container {\n                                    for window in container.windows() {\n                                        windows_to_remove.push(window.hwnd);\n                                    }\n                                    if let Some(window) = container.focused_window() {\n                                        // Minimize the focused window since Windows might try\n                                        // to move it to another monitor if it was focused.\n                                        if window.is_focused() {\n                                            window.minimize();\n                                        }\n                                    }\n                                }\n\n                                for window in workspace.floating_windows() {\n                                    windows_to_remove.push(window.hwnd);\n                                    // Minimize the focused window since Windows might try\n                                    // to move it to another monitor if it was focused.\n                                    if window.is_focused() {\n                                        window.minimize();\n                                    }\n                                }\n                            }\n\n                            // Remove any workspace_rules for this specific monitor\n                            let mut workspace_rules = WORKSPACE_MATCHING_RULES.lock();\n                            let mut rules_to_remove = Vec::new();\n                            for (i, rule) in workspace_rules.iter().enumerate().rev() {\n                                if rule.monitor_index == m_idx {\n                                    rules_to_remove.push(i);\n                                }\n                            }\n                            for i in rules_to_remove {\n                                workspace_rules.remove(i);\n                            }\n\n                            // Let's add their state to the cache for later, make sure to use what\n                            // the user set as preference as the id.\n                            let dip = DISPLAY_INDEX_PREFERENCES.read();\n                            let mut dip_ids = dip.values();\n                            let preferred_id = if dip_ids.any(|id| id.eq(&m.device_id)) {\n                                m.device_id.clone()\n                            } else if dip_ids.any(|id| Some(id) == m.serial_number_id.as_ref()) {\n                                m.serial_number_id.clone().unwrap_or_default()\n                            } else {\n                                id\n                            };\n                            monitor_cache.insert(preferred_id, m.clone());\n                        }\n                    }\n\n                    // Update known_hwnds\n                    wm.known_hwnds.retain(|i, _| !windows_to_remove.contains(i));\n\n                    if !newly_removed_displays.is_empty() {\n                        // After we have cached them, remove them from our state\n                        wm.monitors_mut().retain(|m| {\n                            !newly_removed_displays.iter().any(|id| {\n                                m.serial_number_id.as_ref().is_some_and(|sn| sn == id)\n                                    || m.device_id.eq(id)\n                            })\n                        });\n                    }\n\n                    let post_removal_monitor_count = wm.monitors().len();\n                    let focused_monitor_idx = wm.focused_monitor_idx();\n                    if focused_monitor_idx >= post_removal_monitor_count {\n                        wm.focus_monitor(0)?;\n                    }\n\n                    let offset = wm.work_area_offset;\n\n                    for monitor in wm.monitors_mut() {\n                        // If we have lost a monitor, update everything to filter out any jank\n                        if initial_monitor_count != post_removal_monitor_count {\n                            monitor.update_focused_workspace(offset)?;\n                        }\n                    }\n                }\n\n                let post_removal_monitor_count = wm.monitors().len();\n\n                // This is the list of device ids after we have removed detached displays. We can\n                // keep this with just the device_ids without the serial numbers since this is used\n                // only to check which one is the newly added monitor below if there is a new\n                // monitor. Everything done after with said new monitor will again consider both\n                // serial number and device ids.\n                let post_removal_device_ids = wm\n                    .monitors()\n                    .iter()\n                    .map(|m| &m.device_id)\n                    .cloned()\n                    .collect::<Vec<_>>();\n\n                // Check for and add any new monitors that may have been plugged in\n                // Monitor and display index preferences get applied in this function\n                WindowsApi::load_monitor_information(&mut wm)?;\n\n                let post_addition_monitor_count = wm.monitors().len();\n\n                if post_addition_monitor_count > post_removal_monitor_count {\n                    tracing::info!(\n                        \"monitor count mismatch ({post_removal_monitor_count} vs {post_addition_monitor_count}), adding connected monitors\",\n                    );\n\n                    let known_hwnds = wm.known_hwnds.clone();\n                    let offset = wm.work_area_offset;\n                    let mouse_follows_focus = wm.mouse_follows_focus;\n                    let focused_monitor_idx = wm.focused_monitor_idx();\n                    let focused_workspace_idx = wm.focused_workspace_idx()?;\n\n                    // Look in the updated state for new monitors\n                    for (i, m) in wm.monitors_mut().iter_mut().enumerate() {\n                        let device_id = &m.device_id;\n                        // We identify a new monitor when we encounter a new device id\n                        if !post_removal_device_ids.contains(device_id) {\n                            let mut cache_hit = false;\n                            let mut cached_id = String::new();\n                            // Check if that device id exists in the cache for this session\n                            if let Some((id, cached)) = monitor_cache.get_key_value(device_id).or(m\n                                .serial_number_id\n                                .as_ref()\n                                .and_then(|sn| monitor_cache.get_key_value(sn)))\n                            {\n                                cache_hit = true;\n                                cached_id = id.clone();\n\n                                tracing::info!(\n                                    \"found monitor and workspace configuration for {id} in the monitor cache, applying\"\n                                );\n\n                                // If it does, update the cached monitor info with the new one and\n                                // load the cached monitor removing any window that has since been\n                                // closed or moved to another workspace\n                                *m = Monitor {\n                                    // Data that should be the one just read from `win32-display-data`\n                                    id: m.id,\n                                    name: m.name.clone(),\n                                    device: m.device.clone(),\n                                    device_id: m.device_id.clone(),\n                                    serial_number_id: m.serial_number_id.clone(),\n                                    size: m.size,\n                                    work_area_size: m.work_area_size,\n\n                                    // The rest should come from the cached monitor\n                                    work_area_offset: cached.work_area_offset,\n                                    window_based_work_area_offset: cached\n                                        .window_based_work_area_offset,\n                                    window_based_work_area_offset_limit: cached\n                                        .window_based_work_area_offset_limit,\n                                    workspaces: cached.workspaces.clone(),\n                                    last_focused_workspace: cached.last_focused_workspace,\n                                    workspace_names: cached.workspace_names.clone(),\n                                    container_padding: cached.container_padding,\n                                    workspace_padding: cached.workspace_padding,\n                                    wallpaper: cached.wallpaper.clone(),\n                                    floating_layer_behaviour: cached.floating_layer_behaviour,\n                                };\n\n                                let focused_workspace_idx = m.focused_workspace_idx();\n\n                                for (j, workspace) in m.workspaces_mut().iter_mut().enumerate() {\n                                    // If this is the focused workspace we need to show (restore) all\n                                    // windows that were visible since they were probably minimized by\n                                    // Windows.\n                                    let is_focused_workspace = j == focused_workspace_idx;\n                                    let focused_container_idx = workspace.focused_container_idx();\n\n                                    let mut empty_containers = Vec::new();\n                                    for (idx, container) in\n                                        workspace.containers_mut().iter_mut().enumerate()\n                                    {\n                                        container.windows_mut().retain(|window| {\n                                            window.exe().is_ok()\n                                                && !known_hwnds.contains_key(&window.hwnd)\n                                        });\n\n                                        if container.windows().is_empty() {\n                                            empty_containers.push(idx);\n                                        }\n\n                                        if is_focused_workspace {\n                                            if let Some(window) = container.focused_window()\n                                                && WindowsApi::is_window(window.hwnd)\n                                            {\n                                                tracing::debug!(\n                                                    \"restoring window: {}\",\n                                                    window.hwnd\n                                                );\n                                                WindowsApi::restore_window(window.hwnd);\n                                            } else {\n                                                // If the focused window was moved or removed by\n                                                // the user after the disconnect then focus the\n                                                // first window and show that one\n                                                container.focus_window(0);\n\n                                                if let Some(window) = container.focused_window()\n                                                    && WindowsApi::is_window(window.hwnd)\n                                                {\n                                                    WindowsApi::restore_window(window.hwnd);\n                                                }\n                                            }\n                                        }\n                                    }\n\n                                    // Remove empty containers\n                                    for empty_idx in empty_containers {\n                                        if empty_idx == focused_container_idx {\n                                            workspace.remove_container(empty_idx);\n                                        } else {\n                                            workspace.remove_container_by_idx(empty_idx);\n                                        }\n                                    }\n\n                                    if let Some(window) = &workspace.maximized_window {\n                                        if window.exe().is_err()\n                                            || known_hwnds.contains_key(&window.hwnd)\n                                        {\n                                            workspace.maximized_window = None;\n                                        } else if is_focused_workspace\n                                            && WindowsApi::is_window(window.hwnd)\n                                        {\n                                            WindowsApi::restore_window(window.hwnd);\n                                        }\n                                    }\n\n                                    if let Some(container) = &mut workspace.monocle_container {\n                                        container.windows_mut().retain(|window| {\n                                            window.exe().is_ok()\n                                                && !known_hwnds.contains_key(&window.hwnd)\n                                        });\n\n                                        if container.windows().is_empty() {\n                                            workspace.monocle_container = None;\n                                        } else if is_focused_workspace {\n                                            if let Some(window) = container.focused_window()\n                                                && WindowsApi::is_window(window.hwnd)\n                                            {\n                                                WindowsApi::restore_window(window.hwnd);\n                                            } else {\n                                                // If the focused window was moved or removed by\n                                                // the user after the disconnect then focus the\n                                                // first window and show that one\n                                                container.focus_window(0);\n\n                                                if let Some(window) = container.focused_window()\n                                                    && WindowsApi::is_window(window.hwnd)\n                                                {\n                                                    WindowsApi::restore_window(window.hwnd);\n                                                }\n                                            }\n                                        }\n                                    }\n\n                                    workspace.floating_windows_mut().retain(|window| {\n                                        window.exe().is_ok()\n                                            && !known_hwnds.contains_key(&window.hwnd)\n                                    });\n\n                                    if is_focused_workspace {\n                                        for window in workspace.floating_windows() {\n                                            if WindowsApi::is_window(window.hwnd) {\n                                                WindowsApi::restore_window(window.hwnd);\n                                            }\n                                        }\n                                    }\n\n                                    // Apply workspace rules\n                                    let mut workspace_matching_rules =\n                                        WORKSPACE_MATCHING_RULES.lock();\n                                    if let Some(rules) = workspace\n                                        .workspace_config\n                                        .as_ref()\n                                        .and_then(|c| c.workspace_rules.as_ref())\n                                    {\n                                        for r in rules {\n                                            workspace_matching_rules.push(WorkspaceMatchingRule {\n                                                monitor_index: i,\n                                                workspace_index: j,\n                                                matching_rule: r.clone(),\n                                                initial_only: false,\n                                            });\n                                        }\n                                    }\n\n                                    if let Some(rules) = workspace\n                                        .workspace_config\n                                        .as_ref()\n                                        .and_then(|c| c.initial_workspace_rules.as_ref())\n                                    {\n                                        for r in rules {\n                                            workspace_matching_rules.push(WorkspaceMatchingRule {\n                                                monitor_index: i,\n                                                workspace_index: j,\n                                                matching_rule: r.clone(),\n                                                initial_only: true,\n                                            });\n                                        }\n                                    }\n                                }\n\n                                // Restore windows from new monitor and update the focused\n                                // workspace\n                                m.load_focused_workspace(mouse_follows_focus)?;\n                                m.update_focused_workspace(offset)?;\n                            }\n\n                            // Entries in the cache should only be used once; remove the entry there was a cache hit\n                            if cache_hit && !cached_id.is_empty() {\n                                monitor_cache.remove(&cached_id);\n                            }\n                        }\n                    }\n\n                    // Refocus the previously focused monitor since the code above might\n                    // steal the focus away.\n                    wm.focus_monitor(focused_monitor_idx)?;\n                    wm.focus_workspace(focused_workspace_idx)?;\n                }\n\n                let final_count = wm.monitors().len();\n\n                if post_removal_monitor_count != final_count {\n                    wm.retile_all(true)?;\n                    // Second retile to fix DPI/resolution related jank\n                    wm.retile_all(true)?;\n                    // Border updates to fix DPI/resolution related jank\n                    border_manager::send_notification(None);\n                }\n            }\n        }\n\n        notify_subscribers(\n            Notification {\n                event: NotificationEvent::Monitor(notification),\n                state: wm.as_ref().into(),\n            },\n            initial_state.has_been_modified(&wm),\n        )?;\n    }\n\n    Ok(())\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::window_manager_event::WindowManagerEvent;\n    use crossbeam_channel::Sender;\n    use crossbeam_channel::bounded;\n    use std::path::PathBuf;\n    use uuid::Uuid;\n    use windows::Win32::Devices::Display::DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY;\n    // NOTE: Using RECT instead of RECT since I get a mismatched type error. Can be updated if\n    // needed.\n    use windows::Win32::Foundation::RECT;\n\n    // Creating a Mock Display Provider\n    #[derive(Clone)]\n    struct MockDevice {\n        hmonitor: isize,\n        device_path: String,\n        device_name: String,\n        device_description: String,\n        serial_number_id: Option<String>,\n        size: RECT,\n        work_area_size: RECT,\n        device_key: String,\n        output_technology: Option<DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY>,\n    }\n\n    impl From<MockDevice> for win32_display_data::Device {\n        fn from(mock: MockDevice) -> Self {\n            win32_display_data::Device {\n                hmonitor: mock.hmonitor,\n                device_path: mock.device_path,\n                device_name: mock.device_name,\n                device_description: mock.device_description,\n                serial_number_id: mock.serial_number_id,\n                size: mock.size,\n                work_area_size: mock.work_area_size,\n                device_key: mock.device_key,\n                output_technology: mock.output_technology,\n            }\n        }\n    }\n\n    // Creating a Window Manager Instance\n    struct TestContext {\n        socket_path: Option<PathBuf>,\n    }\n\n    impl Drop for TestContext {\n        fn drop(&mut self) {\n            if let Some(socket_path) = &self.socket_path {\n                // Clean up the socket file\n                if let Err(e) = std::fs::remove_file(socket_path) {\n                    tracing::warn!(\"Failed to remove socket file: {}\", e);\n                }\n            }\n        }\n    }\n\n    fn setup_window_manager() -> (WindowManager, TestContext) {\n        let (_sender, receiver): (Sender<WindowManagerEvent>, Receiver<WindowManagerEvent>) =\n            bounded(1);\n\n        // Temporary socket path for testing\n        let socket_name = format!(\"komorebi-test-{}.sock\", Uuid::new_v4());\n        let socket_path = PathBuf::from(socket_name);\n\n        // Create a new WindowManager instance\n        let wm = match WindowManager::new(receiver, Some(socket_path.clone())) {\n            Ok(manager) => manager,\n            Err(e) => {\n                panic!(\"Failed to create WindowManager: {e}\");\n            }\n        };\n\n        (\n            wm,\n            TestContext {\n                socket_path: Some(socket_path),\n            },\n        )\n    }\n\n    #[test]\n    fn test_send_notification() {\n        // Create a monitor notification\n        let notification = MonitorNotification::ResolutionScalingChanged;\n\n        // Use the send_notification function to send the notification\n        send_notification(notification);\n\n        // Receive the notification from the channel\n        let received = event_rx().try_recv();\n\n        // Check if we received the notification and if it matches what we sent\n        match received {\n            Ok(notification) => {\n                assert_eq!(notification, MonitorNotification::ResolutionScalingChanged);\n            }\n            Err(e) => panic!(\"Failed to receive MonitorNotification: {e}\"),\n        }\n    }\n\n    #[test]\n    fn test_channel_bounded_capacity() {\n        let (_, receiver) = channel();\n\n        // Fill the channel to its capacity (20 messages)\n        for _ in 0..20 {\n            send_notification(MonitorNotification::WorkAreaChanged);\n        }\n\n        // Attempt to send another message (should be dropped)\n        send_notification(MonitorNotification::ResolutionScalingChanged);\n\n        // Verify the channel contains only the first 20 messages\n        for _ in 0..20 {\n            let notification = match receiver.try_recv() {\n                Ok(notification) => notification,\n                Err(e) => panic!(\"Failed to receive MonitorNotification: {e}\"),\n            };\n            assert_eq!(\n                notification,\n                MonitorNotification::WorkAreaChanged,\n                \"Unexpected notification in the channel\"\n            );\n        }\n\n        // Verify that no additional messages are in the channel\n        assert!(\n            receiver.try_recv().is_err(),\n            \"Channel should be empty after consuming all messages\"\n        );\n    }\n\n    #[test]\n    fn test_insert_in_monitor_cache() {\n        let m = monitor::new(\n            0,\n            Rect::default(),\n            Rect::default(),\n            \"Test Monitor\".to_string(),\n            \"Test Device\".to_string(),\n            \"Test Device ID\".to_string(),\n            Some(\"TestMonitorID\".to_string()),\n        );\n\n        // Insert the monitor into the cache\n        insert_in_monitor_cache(\"TestMonitorID\", m.clone());\n\n        // Retrieve the monitor from the cache\n        let cache = MONITOR_CACHE\n            .get_or_init(|| Mutex::new(HashMap::new()))\n            .lock();\n        let retrieved_monitor = cache.get(\"TestMonitorID\");\n\n        // Check that the monitor was inserted correctly and matches the expected value\n        assert_eq!(retrieved_monitor, Some(&m));\n    }\n\n    #[test]\n    fn test_insert_two_monitors_cache() {\n        let m1 = monitor::new(\n            0,\n            Rect::default(),\n            Rect::default(),\n            \"Test Monitor\".to_string(),\n            \"Test Device\".to_string(),\n            \"Test Device ID\".to_string(),\n            Some(\"TestMonitorID\".to_string()),\n        );\n\n        let m2 = monitor::new(\n            0,\n            Rect::default(),\n            Rect::default(),\n            \"Test Monitor 2\".to_string(),\n            \"Test Device 2\".to_string(),\n            \"Test Device ID 2\".to_string(),\n            Some(\"TestMonitorID2\".to_string()),\n        );\n\n        // Insert the first monitor into the cache\n        insert_in_monitor_cache(\"TestMonitorID\", m1.clone());\n\n        // Insert the second monitor into the cache\n        insert_in_monitor_cache(\"TestMonitorID2\", m2.clone());\n\n        // Retrieve the cache to check if the first and second monitors are present\n        let cache = MONITOR_CACHE\n            .get_or_init(|| Mutex::new(HashMap::new()))\n            .lock();\n\n        // Check if Monitor 1 was found in the cache\n        assert_eq!(\n            cache.get(\"TestMonitorID\"),\n            Some(&m1),\n            \"Monitor cache should contain monitor 1\"\n        );\n\n        // Check if Monitor 2 was found in the cache\n        assert_eq!(\n            cache.get(\"TestMonitorID2\"),\n            Some(&m2),\n            \"Monitor cache should contain monitor 2\"\n        );\n    }\n\n    #[test]\n    fn test_listen_for_notifications() {\n        // Create a WindowManager instance for testing\n        let (wm, _test_context) = setup_window_manager();\n\n        // Start the notification listener\n        let result = listen_for_notifications(Arc::new(Mutex::new(wm)));\n\n        // Check if the listener started successfully\n        assert!(result.is_ok(), \"Failed to start notification listener\");\n\n        // Test sending a notification\n        send_notification(MonitorNotification::DisplayConnectionChange);\n\n        // Receive the notification from the channel\n        let received = event_rx().try_recv();\n\n        // Check if we received the notification and if it matches what we sent\n        match received {\n            Ok(notification) => {\n                assert_eq!(notification, MonitorNotification::DisplayConnectionChange);\n            }\n            Err(e) => panic!(\"Failed to receive MonitorNotification: {e}\"),\n        }\n    }\n\n    #[test]\n    fn test_attached_display_devices() {\n        // Define mock display data\n        let mock_monitor = MockDevice {\n            hmonitor: 1,\n            device_path: String::from(\n                \"\\\\\\\\?\\\\DISPLAY#ABC123#4&123456&0&UID0#{saucepackets-4321-5678-2468-abc123456789}\",\n            ),\n            device_name: String::from(\"\\\\\\\\.\\\\DISPLAY1\"),\n            device_description: String::from(\"Display description\"),\n            serial_number_id: Some(String::from(\"SaucePackets123\")),\n            device_key: String::from(\"Mock Key\"),\n            size: RECT {\n                left: 0,\n                top: 0,\n                right: 1920,\n                bottom: 1080,\n            },\n            work_area_size: RECT {\n                left: 0,\n                top: 0,\n                right: 1920,\n                bottom: 1080,\n            },\n            output_technology: Some(DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY(0)),\n        };\n\n        // Create a closure to simulate the display provider\n        let display_provider = || {\n            vec![Ok::<win32_display_data::Device, win32_display_data::Error>(\n                win32_display_data::Device::from(mock_monitor.clone()),\n            )]\n            .into_iter()\n        };\n\n        // Should contain the mock monitor\n        let result = attached_display_devices(display_provider).ok();\n        if let Some(monitors) = result {\n            // Check Number of monitors\n            assert_eq!(monitors.len(), 1, \"Expected one monitor\");\n\n            // hmonitor\n            assert_eq!(monitors[0].id, 1);\n\n            // device name\n            assert_eq!(monitors[0].name, String::from(\"DISPLAY1\"));\n\n            // Device\n            assert_eq!(monitors[0].device, String::from(\"ABC123\"));\n\n            // Device ID\n            assert_eq!(\n                monitors[0].device_id,\n                String::from(\"ABC123-4&123456&0&UID0\")\n            );\n\n            // Check monitor serial number id\n            assert_eq!(\n                monitors[0].serial_number_id,\n                Some(String::from(\"SaucePackets123\")),\n            );\n        } else {\n            panic!(\"No monitors found\");\n        }\n    }\n}\n"
  },
  {
    "path": "komorebi/src/process_command.rs",
    "content": "use color_eyre::eyre;\nuse color_eyre::eyre::OptionExt;\nuse color_eyre::eyre::WrapErr;\nuse komorebi_themes::colour::Rgb;\nuse miow::pipe::connect;\nuse net2::TcpStreamExt;\nuse parking_lot::Mutex;\nuse std::collections::HashMap;\nuse std::fs::File;\nuse std::fs::OpenOptions;\nuse std::io::BufRead;\nuse std::io::BufReader;\nuse std::io::Read;\nuse std::net::TcpListener;\nuse std::net::TcpStream;\nuse std::num::NonZeroUsize;\nuse std::str::FromStr;\nuse std::sync::Arc;\nuse std::sync::atomic::Ordering;\nuse std::time::Duration;\nuse uds_windows::UnixStream;\n\nuse crate::CUSTOM_FFM;\nuse crate::DATA_DIR;\nuse crate::DISPLAY_INDEX_PREFERENCES;\nuse crate::FLOATING_APPLICATIONS;\nuse crate::HIDING_BEHAVIOUR;\nuse crate::IGNORE_IDENTIFIERS;\nuse crate::INITIAL_CONFIGURATION_LOADED;\nuse crate::LAYERED_WHITELIST;\nuse crate::MANAGE_IDENTIFIERS;\nuse crate::MONITOR_INDEX_PREFERENCES;\nuse crate::NO_TITLEBAR;\nuse crate::Notification;\nuse crate::NotificationEvent;\nuse crate::OBJECT_NAME_CHANGE_ON_LAUNCH;\nuse crate::REMOVE_TITLEBARS;\nuse crate::SESSION_FLOATING_APPLICATIONS;\nuse crate::SUBSCRIPTION_PIPES;\nuse crate::SUBSCRIPTION_SOCKET_OPTIONS;\nuse crate::SUBSCRIPTION_SOCKETS;\nuse crate::TCP_CONNECTIONS;\nuse crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;\nuse crate::WINDOWS_11;\nuse crate::WORKSPACE_MATCHING_RULES;\nuse crate::animation::ANIMATION_DURATION_GLOBAL;\nuse crate::animation::ANIMATION_DURATION_PER_ANIMATION;\nuse crate::animation::ANIMATION_ENABLED_GLOBAL;\nuse crate::animation::ANIMATION_ENABLED_PER_ANIMATION;\nuse crate::animation::ANIMATION_FPS;\nuse crate::animation::ANIMATION_STYLE_GLOBAL;\nuse crate::animation::ANIMATION_STYLE_PER_ANIMATION;\nuse crate::border_manager;\nuse crate::border_manager::IMPLEMENTATION;\nuse crate::border_manager::STYLE;\nuse crate::build;\nuse crate::config_generation::WorkspaceMatchingRule;\nuse crate::core::ApplicationIdentifier;\nuse crate::core::Axis;\nuse crate::core::BorderImplementation;\nuse crate::core::FocusFollowsMouseImplementation;\nuse crate::core::Layout;\nuse crate::core::LayoutOptions;\nuse crate::core::MoveBehaviour;\nuse crate::core::OperationDirection;\nuse crate::core::Rect;\nuse crate::core::ScrollingLayoutOptions;\nuse crate::core::Sizing;\nuse crate::core::SocketMessage;\nuse crate::core::StateQuery;\nuse crate::core::WindowContainerBehaviour;\nuse crate::core::WindowKind;\nuse crate::core::config_generation::IdWithIdentifier;\nuse crate::core::config_generation::MatchingRule;\nuse crate::core::config_generation::MatchingStrategy;\nuse crate::current_virtual_desktop;\nuse crate::monitor::MonitorInformation;\nuse crate::notify_subscribers;\nuse crate::stackbar_manager;\nuse crate::stackbar_manager::STACKBAR_FONT_FAMILY;\nuse crate::stackbar_manager::STACKBAR_FONT_SIZE;\nuse crate::state;\nuse crate::state::GlobalState;\nuse crate::state::State;\nuse crate::static_config::StaticConfig;\nuse crate::theme_manager;\nuse crate::transparency_manager;\nuse crate::window::RuleDebug;\nuse crate::window::Window;\nuse crate::window_manager::WindowManager;\nuse crate::windows_api::WindowsApi;\nuse crate::winevent_listener;\nuse crate::workspace::WorkspaceLayer;\nuse crate::workspace::WorkspaceWindowLocation;\nuse stackbar_manager::STACKBAR_FOCUSED_TEXT_COLOUR;\nuse stackbar_manager::STACKBAR_LABEL;\nuse stackbar_manager::STACKBAR_MODE;\nuse stackbar_manager::STACKBAR_TAB_BACKGROUND_COLOUR;\nuse stackbar_manager::STACKBAR_TAB_HEIGHT;\nuse stackbar_manager::STACKBAR_TAB_WIDTH;\nuse stackbar_manager::STACKBAR_UNFOCUSED_TEXT_COLOUR;\n\n#[tracing::instrument]\npub fn listen_for_commands(wm: Arc<Mutex<WindowManager>>) {\n    std::thread::spawn(move || {\n        loop {\n            let wm = wm.clone();\n\n            let _ = std::thread::spawn(move || {\n                let listener = wm\n                    .lock()\n                    .command_listener\n                    .try_clone()\n                    .expect(\"could not clone unix listener\");\n\n                tracing::info!(\"listening on komorebi.sock\");\n                for client in listener.incoming() {\n                    match client {\n                        Ok(stream) => {\n                            let wm_clone = wm.clone();\n                            std::thread::spawn(move || {\n                                match stream.set_read_timeout(Some(Duration::from_secs(1))) {\n                                    Ok(()) => {}\n                                    Err(error) => tracing::error!(\"{}\", error),\n                                }\n                                match read_commands_uds(&wm_clone, stream) {\n                                    Ok(()) => {}\n                                    Err(error) => tracing::error!(\"{}\", error),\n                                }\n                            });\n                        }\n                        Err(error) => {\n                            tracing::error!(\"{}\", error);\n                            break;\n                        }\n                    }\n                }\n            })\n            .join();\n\n            tracing::error!(\"restarting failed thread\");\n        }\n    });\n}\n\n#[tracing::instrument]\npub fn listen_for_commands_tcp(wm: Arc<Mutex<WindowManager>>, port: usize) {\n    let listener =\n        TcpListener::bind(format!(\"0.0.0.0:{port}\")).expect(\"could not start tcp server\");\n\n    std::thread::spawn(move || {\n        tracing::info!(\"listening on 0.0.0.0:43663\");\n        for client in listener.incoming() {\n            match client {\n                Ok(mut stream) => {\n                    stream\n                        .set_keepalive(Some(Duration::from_secs(30)))\n                        .expect(\"TCP keepalive should be set\");\n\n                    let addr = stream\n                        .peer_addr()\n                        .expect(\"incoming connection should have an address\")\n                        .to_string();\n\n                    let mut connections = TCP_CONNECTIONS.lock();\n\n                    connections.insert(\n                        addr.clone(),\n                        stream.try_clone().expect(\"stream should be cloneable\"),\n                    );\n\n                    tracing::info!(\"listening for incoming tcp messages from {}\", &addr);\n\n                    match read_commands_tcp(&wm, &mut stream, &addr) {\n                        Ok(()) => {}\n                        Err(error) => tracing::error!(\"{}\", error),\n                    }\n                }\n                Err(error) => {\n                    tracing::error!(\"{}\", error);\n                    break;\n                }\n            }\n        }\n    });\n}\n\nimpl WindowManager {\n    // TODO(raggi): wrap reply in a newtype that can decorate a human friendly\n    // name for the peer, such as getting the pid of the komorebic process for\n    // the UDS or the IP:port for TCP.\n    #[tracing::instrument(skip(self, reply))]\n    pub fn process_command(\n        &mut self,\n        message: SocketMessage,\n        mut reply: impl std::io::Write,\n    ) -> eyre::Result<()> {\n        if let Some(virtual_desktop_id) = &self.virtual_desktop_id\n            && let Some(id) = current_virtual_desktop()\n            && id != *virtual_desktop_id\n        {\n            tracing::info!(\n                \"ignoring events and commands while not on virtual desktop {:?}\",\n                virtual_desktop_id\n            );\n            return Ok(());\n        }\n\n        #[allow(clippy::useless_asref)]\n        // We don't have From implemented for &mut WindowManager\n        let initial_state = State::from(self.as_ref());\n\n        let mut force_update_borders = false;\n        match message {\n            SocketMessage::Promote => self.promote_container_to_front()?,\n            SocketMessage::PromoteSwap => self.promote_container_swap()?,\n            SocketMessage::PromoteFocus => self.promote_focus_to_front()?,\n            SocketMessage::PromoteWindow(direction) => {\n                self.focus_container_in_direction(direction)?;\n                self.promote_container_to_front()?\n            }\n            SocketMessage::EagerFocus(ref exe) => {\n                let focused_monitor_idx = self.focused_monitor_idx();\n\n                let mut window_location = None;\n                let mut monitor_to_focus = None;\n                let mut needs_workspace_loading = false;\n\n                'search: for (monitor_idx, monitor) in self.monitors_mut().iter_mut().enumerate() {\n                    for (workspace_idx, workspace) in monitor.workspaces().iter().enumerate() {\n                        if let Some(location) = workspace.location_from_exe(exe) {\n                            window_location = Some(location);\n\n                            if monitor_idx != focused_monitor_idx {\n                                monitor_to_focus = Some(monitor_idx);\n                            }\n\n                            // Focus workspace if it is not already the focused one, without\n                            // loading it so that we don't give focus to the wrong window, we will\n                            // load it later after focusing the wanted window\n                            let focused_ws_idx = monitor.focused_workspace_idx();\n                            if focused_ws_idx != workspace_idx {\n                                monitor.last_focused_workspace = Option::from(focused_ws_idx);\n                                monitor.focus_workspace(workspace_idx)?;\n                                needs_workspace_loading = true;\n                            }\n\n                            break 'search;\n                        }\n                    }\n                }\n\n                if let Some(monitor_idx) = monitor_to_focus {\n                    self.focus_monitor(monitor_idx)?;\n                }\n\n                if let Some(location) = window_location {\n                    match location {\n                        WorkspaceWindowLocation::Monocle(window_idx) => {\n                            self.focus_container_window(window_idx)?;\n                        }\n                        WorkspaceWindowLocation::Maximized => {\n                            if let Some(window) =\n                                &mut self.focused_workspace_mut()?.maximized_window\n                            {\n                                window.focus(self.mouse_follows_focus)?;\n                            }\n                        }\n                        WorkspaceWindowLocation::Container(container_idx, window_idx) => {\n                            let focused_container_idx = self.focused_container_idx()?;\n                            if container_idx != focused_container_idx {\n                                self.focused_workspace_mut()?.focus_container(container_idx);\n                            }\n\n                            self.focus_container_window(window_idx)?;\n                        }\n                        WorkspaceWindowLocation::Floating(window_idx) => {\n                            if let Some(window) = self\n                                .focused_workspace_mut()?\n                                .floating_windows_mut()\n                                .get_mut(window_idx)\n                            {\n                                window.focus(self.mouse_follows_focus)?;\n                            }\n                        }\n                    }\n\n                    if needs_workspace_loading {\n                        let mouse_follows_focus = self.mouse_follows_focus;\n                        if let Some(monitor) = self.focused_monitor_mut() {\n                            monitor.load_focused_workspace(mouse_follows_focus)?;\n                        }\n                    }\n                }\n            }\n            SocketMessage::FocusWindow(direction) => {\n                let focused_workspace = self.focused_workspace()?;\n                match focused_workspace.layer {\n                    WorkspaceLayer::Tiling => {\n                        self.focus_container_in_direction(direction)?;\n                    }\n                    WorkspaceLayer::Floating => {\n                        self.focus_floating_window_in_direction(direction)?;\n                    }\n                }\n            }\n            SocketMessage::PreselectDirection(direction) => {\n                let focused_workspace = self.focused_workspace()?;\n                let mut update = false;\n\n                if focused_workspace.preselected_container_idx.is_some() {\n                    tracing::warn!(\n                        \"ignoring command as this workspace already has a direction preselect set\"\n                    );\n                } else if matches!(focused_workspace.layer, WorkspaceLayer::Tiling) {\n                    self.preselect_container_in_direction(direction)?;\n                    update = true;\n                }\n\n                if update {\n                    self.focused_workspace_mut()?.update()?;\n                }\n            }\n            SocketMessage::CancelPreselect => {\n                let focused_workspace = self.focused_workspace_mut()?;\n                focused_workspace.cancel_preselect();\n                focused_workspace.update()?;\n            }\n            SocketMessage::MoveWindow(direction) => {\n                let focused_workspace = self.focused_workspace()?;\n                match focused_workspace.layer {\n                    WorkspaceLayer::Tiling => {\n                        self.move_container_in_direction(direction)?;\n                    }\n                    WorkspaceLayer::Floating => {\n                        self.move_floating_window_in_direction(direction)?;\n                    }\n                }\n            }\n            SocketMessage::CycleFocusWindow(direction) => {\n                let focused_workspace = self.focused_workspace()?;\n                match focused_workspace.layer {\n                    WorkspaceLayer::Tiling => {\n                        self.focus_container_in_cycle_direction(direction)?;\n                    }\n                    WorkspaceLayer::Floating => {\n                        self.focus_floating_window_in_cycle_direction(direction)?;\n                    }\n                }\n            }\n            SocketMessage::CycleMoveWindow(direction) => {\n                self.move_container_in_cycle_direction(direction)?;\n            }\n            SocketMessage::StackWindow(direction) => self.add_window_to_container(direction)?,\n            SocketMessage::UnstackWindow => self.remove_window_from_container()?,\n            SocketMessage::StackAll => self.stack_all()?,\n            SocketMessage::UnstackAll => self.unstack_all(true)?,\n            SocketMessage::CycleStack(direction) => {\n                self.cycle_container_window_in_direction(direction)?;\n            }\n            SocketMessage::CycleStackIndex(direction) => {\n                self.cycle_container_window_index_in_direction(direction)?;\n            }\n            SocketMessage::FocusStackWindow(idx) => {\n                // In case you are using this command on a bar on a monitor\n                // different from the currently focused one, you'd want that\n                // monitor to be focused so that the FocusStackWindow happens\n                // on the monitor with the bar you just pressed.\n                if let Some(monitor_idx) = self.monitor_idx_from_current_pos() {\n                    self.focus_monitor(monitor_idx)?;\n                }\n                self.focus_container_window(idx)?;\n            }\n            SocketMessage::ForceFocus => {\n                let focused_window = self.focused_window()?;\n                let focused_window_rect = WindowsApi::window_rect(focused_window.hwnd)?;\n                WindowsApi::center_cursor_in_rect(&focused_window_rect)?;\n                WindowsApi::left_click();\n            }\n            SocketMessage::Close => {\n                Window::from(WindowsApi::foreground_window()?).close()?;\n            }\n            SocketMessage::Minimize => {\n                Window::from(WindowsApi::foreground_window()?).minimize();\n            }\n            SocketMessage::LockMonitorWorkspaceContainer(\n                monitor_idx,\n                workspace_idx,\n                container_idx,\n            ) => {\n                let monitor = self\n                    .monitors_mut()\n                    .get_mut(monitor_idx)\n                    .ok_or_eyre(\"no monitor at the given index\")?;\n\n                let workspace = monitor\n                    .workspaces_mut()\n                    .get_mut(workspace_idx)\n                    .ok_or_eyre(\"no workspace at the given index\")?;\n\n                if let Some(container) = workspace.containers_mut().get_mut(container_idx) {\n                    container.locked = true;\n                }\n            }\n            SocketMessage::UnlockMonitorWorkspaceContainer(\n                monitor_idx,\n                workspace_idx,\n                container_idx,\n            ) => {\n                let monitor = self\n                    .monitors_mut()\n                    .get_mut(monitor_idx)\n                    .ok_or_eyre(\"no monitor at the given index\")?;\n\n                let workspace = monitor\n                    .workspaces_mut()\n                    .get_mut(workspace_idx)\n                    .ok_or_eyre(\"no workspace at the given index\")?;\n\n                if let Some(container) = workspace.containers_mut().get_mut(container_idx) {\n                    container.locked = false;\n                }\n            }\n            SocketMessage::ToggleLock => self.toggle_lock()?,\n            SocketMessage::ToggleFloat => self.toggle_float(false)?,\n            SocketMessage::ToggleMonocle => self.toggle_monocle()?,\n            SocketMessage::ToggleMaximize => self.toggle_maximize()?,\n            SocketMessage::ContainerPadding(monitor_idx, workspace_idx, size) => {\n                self.set_container_padding(monitor_idx, workspace_idx, size)?;\n            }\n            SocketMessage::NamedWorkspaceContainerPadding(ref workspace, size) => {\n                if let Some((monitor_idx, workspace_idx)) =\n                    self.monitor_workspace_index_by_name(workspace)\n                {\n                    self.set_container_padding(monitor_idx, workspace_idx, size)?;\n                }\n            }\n            SocketMessage::WorkspacePadding(monitor_idx, workspace_idx, size) => {\n                self.set_workspace_padding(monitor_idx, workspace_idx, size)?;\n            }\n            SocketMessage::NamedWorkspacePadding(ref workspace, size) => {\n                if let Some((monitor_idx, workspace_idx)) =\n                    self.monitor_workspace_index_by_name(workspace)\n                {\n                    self.set_workspace_padding(monitor_idx, workspace_idx, size)?;\n                }\n            }\n            SocketMessage::InitialWorkspaceRule(identifier, ref id, monitor_idx, workspace_idx) => {\n                let mut workspace_rules = WORKSPACE_MATCHING_RULES.lock();\n                let workspace_matching_rule = WorkspaceMatchingRule {\n                    monitor_index: monitor_idx,\n                    workspace_index: workspace_idx,\n                    matching_rule: MatchingRule::Simple(IdWithIdentifier {\n                        kind: identifier,\n                        id: id.to_string(),\n                        matching_strategy: Some(MatchingStrategy::Legacy),\n                    }),\n                    initial_only: true,\n                };\n\n                if !workspace_rules.contains(&workspace_matching_rule) {\n                    workspace_rules.push(workspace_matching_rule);\n                }\n            }\n            SocketMessage::InitialNamedWorkspaceRule(identifier, ref id, ref workspace) => {\n                if let Some((monitor_idx, workspace_idx)) =\n                    self.monitor_workspace_index_by_name(workspace)\n                {\n                    let mut workspace_rules = WORKSPACE_MATCHING_RULES.lock();\n                    let workspace_matching_rule = WorkspaceMatchingRule {\n                        monitor_index: monitor_idx,\n                        workspace_index: workspace_idx,\n                        matching_rule: MatchingRule::Simple(IdWithIdentifier {\n                            kind: identifier,\n                            id: id.to_string(),\n                            matching_strategy: Some(MatchingStrategy::Legacy),\n                        }),\n                        initial_only: true,\n                    };\n\n                    if !workspace_rules.contains(&workspace_matching_rule) {\n                        workspace_rules.push(workspace_matching_rule);\n                    }\n                }\n            }\n            SocketMessage::WorkspaceRule(identifier, ref id, monitor_idx, workspace_idx) => {\n                let mut workspace_rules = WORKSPACE_MATCHING_RULES.lock();\n                let workspace_matching_rule = WorkspaceMatchingRule {\n                    monitor_index: monitor_idx,\n                    workspace_index: workspace_idx,\n                    matching_rule: MatchingRule::Simple(IdWithIdentifier {\n                        kind: identifier,\n                        id: id.to_string(),\n                        matching_strategy: Some(MatchingStrategy::Legacy),\n                    }),\n                    initial_only: false,\n                };\n\n                if !workspace_rules.contains(&workspace_matching_rule) {\n                    workspace_rules.push(workspace_matching_rule);\n                }\n            }\n            SocketMessage::NamedWorkspaceRule(identifier, ref id, ref workspace) => {\n                if let Some((monitor_idx, workspace_idx)) =\n                    self.monitor_workspace_index_by_name(workspace)\n                {\n                    let mut workspace_rules = WORKSPACE_MATCHING_RULES.lock();\n                    let workspace_matching_rule = WorkspaceMatchingRule {\n                        monitor_index: monitor_idx,\n                        workspace_index: workspace_idx,\n                        matching_rule: MatchingRule::Simple(IdWithIdentifier {\n                            kind: identifier,\n                            id: id.to_string(),\n                            matching_strategy: Some(MatchingStrategy::Legacy),\n                        }),\n                        initial_only: false,\n                    };\n\n                    if !workspace_rules.contains(&workspace_matching_rule) {\n                        workspace_rules.push(workspace_matching_rule);\n                    }\n                }\n            }\n            SocketMessage::ClearWorkspaceRules(monitor_idx, workspace_idx) => {\n                let mut workspace_rules = WORKSPACE_MATCHING_RULES.lock();\n\n                workspace_rules.retain(|r| {\n                    r.monitor_index != monitor_idx && r.workspace_index != workspace_idx\n                });\n            }\n            SocketMessage::ClearNamedWorkspaceRules(ref workspace) => {\n                if let Some((monitor_idx, workspace_idx)) =\n                    self.monitor_workspace_index_by_name(workspace)\n                {\n                    let mut workspace_rules = WORKSPACE_MATCHING_RULES.lock();\n                    workspace_rules.retain(|r| {\n                        r.monitor_index != monitor_idx && r.workspace_index != workspace_idx\n                    });\n                }\n            }\n            SocketMessage::ClearAllWorkspaceRules => {\n                let mut workspace_rules = WORKSPACE_MATCHING_RULES.lock();\n                workspace_rules.clear();\n            }\n            SocketMessage::EnforceWorkspaceRules => {\n                {\n                    let mut already_moved = self.already_moved_window_handles.lock();\n                    already_moved.clear();\n                }\n                self.enforce_workspace_rules()?;\n            }\n            SocketMessage::ManageRule(identifier, ref id) => {\n                let mut manage_identifiers = MANAGE_IDENTIFIERS.lock();\n\n                let mut should_push = true;\n                for m in &*manage_identifiers {\n                    if let MatchingRule::Simple(m) = m\n                        && m.id.eq(id)\n                    {\n                        should_push = false;\n                    }\n                }\n\n                if should_push {\n                    manage_identifiers.push(MatchingRule::Simple(IdWithIdentifier {\n                        kind: identifier,\n                        id: id.clone(),\n                        matching_strategy: Option::from(MatchingStrategy::Legacy),\n                    }));\n                }\n            }\n            SocketMessage::SessionFloatRule => {\n                let foreground_window = WindowsApi::foreground_window()?;\n                let window = Window::from(foreground_window);\n                if let (Ok(exe), Ok(title), Ok(class)) =\n                    (window.exe(), window.title(), window.class())\n                {\n                    let rule = MatchingRule::Composite(vec![\n                        IdWithIdentifier {\n                            kind: ApplicationIdentifier::Exe,\n                            id: exe,\n                            matching_strategy: Option::from(MatchingStrategy::Equals),\n                        },\n                        IdWithIdentifier {\n                            kind: ApplicationIdentifier::Title,\n                            id: title,\n                            matching_strategy: Option::from(MatchingStrategy::Equals),\n                        },\n                        IdWithIdentifier {\n                            kind: ApplicationIdentifier::Class,\n                            id: class,\n                            matching_strategy: Option::from(MatchingStrategy::Equals),\n                        },\n                    ]);\n\n                    let mut floating_applications = FLOATING_APPLICATIONS.lock();\n                    floating_applications.push(rule.clone());\n                    let mut session_floating_applications = SESSION_FLOATING_APPLICATIONS.lock();\n                    session_floating_applications.push(rule.clone());\n\n                    self.toggle_float(true)?;\n                }\n            }\n            SocketMessage::SessionFloatRules => {\n                let session_floating_applications = SESSION_FLOATING_APPLICATIONS.lock();\n                let rules = match serde_json::to_string_pretty(&*session_floating_applications) {\n                    Ok(rules) => rules,\n                    Err(error) => error.to_string(),\n                };\n\n                reply.write_all(rules.as_bytes())?;\n            }\n            SocketMessage::ClearSessionFloatRules => {\n                let mut floating_applications = FLOATING_APPLICATIONS.lock();\n                let mut session_floating_applications = SESSION_FLOATING_APPLICATIONS.lock();\n                floating_applications.retain(|r| !session_floating_applications.contains(r));\n                session_floating_applications.clear()\n            }\n            SocketMessage::IgnoreRule(identifier, ref id) => {\n                let mut ignore_identifiers = IGNORE_IDENTIFIERS.lock();\n\n                let mut should_push = true;\n                for i in &*ignore_identifiers {\n                    if let MatchingRule::Simple(i) = i\n                        && i.id.eq(id)\n                    {\n                        should_push = false;\n                    }\n                }\n\n                if should_push {\n                    ignore_identifiers.push(MatchingRule::Simple(IdWithIdentifier {\n                        kind: identifier,\n                        id: id.clone(),\n                        matching_strategy: Option::from(MatchingStrategy::Legacy),\n                    }));\n                }\n\n                let offset = self.work_area_offset;\n\n                let mut hwnds_to_purge = vec![];\n                for (i, monitor) in self.monitors().iter().enumerate() {\n                    for container in monitor\n                        .focused_workspace()\n                        .ok_or_eyre(\"there is no workspace\")?\n                        .containers()\n                    {\n                        for window in container.windows() {\n                            match identifier {\n                                ApplicationIdentifier::Path => {\n                                    if window.path()? == *id {\n                                        hwnds_to_purge.push((i, window.hwnd));\n                                    }\n                                }\n                                ApplicationIdentifier::Exe => {\n                                    if window.exe()? == *id {\n                                        hwnds_to_purge.push((i, window.hwnd));\n                                    }\n                                }\n                                ApplicationIdentifier::Class => {\n                                    if window.class()? == *id {\n                                        hwnds_to_purge.push((i, window.hwnd));\n                                    }\n                                }\n                                ApplicationIdentifier::Title => {\n                                    if window.title()? == *id {\n                                        hwnds_to_purge.push((i, window.hwnd));\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n\n                for (monitor_idx, hwnd) in hwnds_to_purge {\n                    let monitor = self\n                        .monitors_mut()\n                        .get_mut(monitor_idx)\n                        .ok_or_eyre(\"there is no monitor\")?;\n\n                    monitor\n                        .focused_workspace_mut()\n                        .ok_or_eyre(\"there is no focused workspace\")?\n                        .remove_window(hwnd)?;\n\n                    monitor.update_focused_workspace(offset)?;\n                }\n            }\n            SocketMessage::FocusedWorkspaceContainerPadding(adjustment) => {\n                let focused_monitor_idx = self.focused_monitor_idx();\n\n                let focused_monitor = self.focused_monitor().ok_or_eyre(\"there is no monitor\")?;\n\n                let focused_workspace_idx = focused_monitor.focused_workspace_idx();\n\n                self.set_container_padding(focused_monitor_idx, focused_workspace_idx, adjustment)?;\n            }\n            SocketMessage::FocusedWorkspacePadding(adjustment) => {\n                let focused_monitor_idx = self.focused_monitor_idx();\n\n                let focused_monitor = self.focused_monitor().ok_or_eyre(\"there is no monitor\")?;\n\n                let focused_workspace_idx = focused_monitor.focused_workspace_idx();\n\n                self.set_workspace_padding(focused_monitor_idx, focused_workspace_idx, adjustment)?;\n            }\n            SocketMessage::AdjustContainerPadding(sizing, adjustment) => {\n                self.adjust_container_padding(sizing, adjustment)?;\n            }\n            SocketMessage::AdjustWorkspacePadding(sizing, adjustment) => {\n                self.adjust_workspace_padding(sizing, adjustment)?;\n            }\n            SocketMessage::MoveContainerToLastWorkspace => {\n                // This is to ensure that even on an empty workspace on a secondary monitor, the\n                // secondary monitor where the cursor is focused will be used as the target for\n                // the workspace switch op\n                if let Some(monitor_idx) = self.monitor_idx_from_current_pos()\n                    && monitor_idx != self.focused_monitor_idx()\n                    && let Some(monitor) = self.monitors().get(monitor_idx)\n                    && let Some(workspace) = monitor.focused_workspace()\n                    && workspace.is_empty()\n                {\n                    self.focus_monitor(monitor_idx)?;\n                }\n\n                let idx = self\n                    .focused_monitor()\n                    .ok_or_eyre(\"there is no monitor\")?\n                    .focused_workspace_idx();\n\n                if let Some(monitor) = self.focused_monitor_mut()\n                    && let Some(last_focused_workspace) = monitor.last_focused_workspace\n                {\n                    self.move_container_to_workspace(last_focused_workspace, true, None)?;\n                }\n\n                self.focused_monitor_mut()\n                    .ok_or_eyre(\"there is no monitor\")?\n                    .last_focused_workspace = Option::from(idx);\n            }\n            SocketMessage::SendContainerToLastWorkspace => {\n                // This is to ensure that even on an empty workspace on a secondary monitor, the\n                // secondary monitor where the cursor is focused will be used as the target for\n                // the workspace switch op\n                if let Some(monitor_idx) = self.monitor_idx_from_current_pos()\n                    && monitor_idx != self.focused_monitor_idx()\n                    && let Some(monitor) = self.monitors().get(monitor_idx)\n                    && let Some(workspace) = monitor.focused_workspace()\n                    && workspace.is_empty()\n                {\n                    self.focus_monitor(monitor_idx)?;\n                }\n\n                let idx = self\n                    .focused_monitor()\n                    .ok_or_eyre(\"there is no monitor\")?\n                    .focused_workspace_idx();\n\n                if let Some(monitor) = self.focused_monitor_mut()\n                    && let Some(last_focused_workspace) = monitor.last_focused_workspace\n                {\n                    self.move_container_to_workspace(last_focused_workspace, false, None)?;\n                }\n                self.focused_monitor_mut()\n                    .ok_or_eyre(\"there is no monitor\")?\n                    .last_focused_workspace = Option::from(idx);\n            }\n            SocketMessage::MoveContainerToWorkspaceNumber(workspace_idx) => {\n                self.move_container_to_workspace(workspace_idx, true, None)?;\n            }\n            SocketMessage::CycleMoveContainerToWorkspace(direction) => {\n                let focused_monitor = self.focused_monitor().ok_or_eyre(\"there is no monitor\")?;\n\n                let focused_workspace_idx = focused_monitor.focused_workspace_idx();\n                let workspaces = focused_monitor.workspaces().len();\n\n                let workspace_idx = direction.next_idx(\n                    focused_workspace_idx,\n                    NonZeroUsize::new(workspaces)\n                        .ok_or_eyre(\"there must be at least one workspace\")?,\n                );\n\n                self.move_container_to_workspace(workspace_idx, true, None)?;\n            }\n            SocketMessage::MoveContainerToMonitorNumber(monitor_idx) => {\n                let direction = self.direction_from_monitor_idx(monitor_idx);\n                self.move_container_to_monitor(monitor_idx, None, true, direction)?;\n            }\n            SocketMessage::SwapWorkspacesToMonitorNumber(monitor_idx) => {\n                self.swap_focused_monitor(monitor_idx)?;\n            }\n            SocketMessage::CycleMoveContainerToMonitor(direction) => {\n                let monitor_idx = direction.next_idx(\n                    self.focused_monitor_idx(),\n                    NonZeroUsize::new(self.monitors().len())\n                        .ok_or_eyre(\"there must be at least one monitor\")?,\n                );\n\n                let direction = self.direction_from_monitor_idx(monitor_idx);\n                self.move_container_to_monitor(monitor_idx, None, true, direction)?;\n            }\n            SocketMessage::SendContainerToWorkspaceNumber(workspace_idx) => {\n                self.move_container_to_workspace(workspace_idx, false, None)?;\n            }\n            SocketMessage::CycleSendContainerToWorkspace(direction) => {\n                let focused_monitor = self.focused_monitor().ok_or_eyre(\"there is no monitor\")?;\n\n                let focused_workspace_idx = focused_monitor.focused_workspace_idx();\n                let workspaces = focused_monitor.workspaces().len();\n\n                let workspace_idx = direction.next_idx(\n                    focused_workspace_idx,\n                    NonZeroUsize::new(workspaces)\n                        .ok_or_eyre(\"there must be at least one workspace\")?,\n                );\n\n                self.move_container_to_workspace(workspace_idx, false, None)?;\n            }\n            SocketMessage::SendContainerToMonitorNumber(monitor_idx) => {\n                let direction = self.direction_from_monitor_idx(monitor_idx);\n                self.move_container_to_monitor(monitor_idx, None, false, direction)?;\n            }\n            SocketMessage::CycleSendContainerToMonitor(direction) => {\n                let monitor_idx = direction.next_idx(\n                    self.focused_monitor_idx(),\n                    NonZeroUsize::new(self.monitors().len())\n                        .ok_or_eyre(\"there must be at least one monitor\")?,\n                );\n\n                let direction = self.direction_from_monitor_idx(monitor_idx);\n                self.move_container_to_monitor(monitor_idx, None, false, direction)?;\n            }\n            SocketMessage::SendContainerToMonitorWorkspaceNumber(monitor_idx, workspace_idx) => {\n                let direction = self.direction_from_monitor_idx(monitor_idx);\n                self.move_container_to_monitor(\n                    monitor_idx,\n                    Option::from(workspace_idx),\n                    false,\n                    direction,\n                )?;\n            }\n            SocketMessage::MoveContainerToMonitorWorkspaceNumber(monitor_idx, workspace_idx) => {\n                let direction = self.direction_from_monitor_idx(monitor_idx);\n                self.move_container_to_monitor(\n                    monitor_idx,\n                    Option::from(workspace_idx),\n                    true,\n                    direction,\n                )?;\n            }\n            SocketMessage::SendContainerToNamedWorkspace(ref workspace) => {\n                if let Some((monitor_idx, workspace_idx)) =\n                    self.monitor_workspace_index_by_name(workspace)\n                {\n                    let direction = self.direction_from_monitor_idx(monitor_idx);\n                    self.move_container_to_monitor(\n                        monitor_idx,\n                        Option::from(workspace_idx),\n                        false,\n                        direction,\n                    )?;\n                }\n            }\n            SocketMessage::MoveContainerToNamedWorkspace(ref workspace) => {\n                if let Some((monitor_idx, workspace_idx)) =\n                    self.monitor_workspace_index_by_name(workspace)\n                {\n                    let direction = self.direction_from_monitor_idx(monitor_idx);\n                    self.move_container_to_monitor(\n                        monitor_idx,\n                        Option::from(workspace_idx),\n                        true,\n                        direction,\n                    )?;\n                }\n            }\n\n            SocketMessage::MoveWorkspaceToMonitorNumber(monitor_idx) => {\n                self.move_workspace_to_monitor(monitor_idx)?;\n            }\n            SocketMessage::CycleMoveWorkspaceToMonitor(direction) => {\n                let monitor_idx = direction.next_idx(\n                    self.focused_monitor_idx(),\n                    NonZeroUsize::new(self.monitors().len())\n                        .ok_or_eyre(\"there must be at least one monitor\")?,\n                );\n\n                self.move_workspace_to_monitor(monitor_idx)?;\n            }\n            SocketMessage::TogglePause => {\n                if self.is_paused {\n                    tracing::info!(\"resuming\");\n                } else {\n                    tracing::info!(\"pausing\");\n                }\n\n                self.is_paused = !self.is_paused;\n                self.retile_all(true)?;\n            }\n            SocketMessage::ToggleTiling => {\n                self.toggle_tiling()?;\n            }\n            SocketMessage::CycleFocusMonitor(direction) => {\n                let monitor_idx = direction.next_idx(\n                    self.focused_monitor_idx(),\n                    NonZeroUsize::new(self.monitors().len())\n                        .ok_or_eyre(\"there must be at least one monitor\")?,\n                );\n\n                self.focus_monitor(monitor_idx)?;\n                self.update_focused_workspace(self.mouse_follows_focus, true)?;\n            }\n            SocketMessage::FocusMonitorNumber(monitor_idx) => {\n                self.focus_monitor(monitor_idx)?;\n                self.update_focused_workspace(self.mouse_follows_focus, true)?;\n            }\n            SocketMessage::FocusMonitorAtCursor => {\n                if let Some(monitor_idx) = self.monitor_idx_from_current_pos() {\n                    self.focus_monitor(monitor_idx)?;\n                }\n            }\n            SocketMessage::Retile => {\n                border_manager::destroy_all_borders()?;\n                force_update_borders = true;\n                self.retile_all(false)?\n            }\n            SocketMessage::RetileWithResizeDimensions => {\n                border_manager::destroy_all_borders()?;\n                force_update_borders = true;\n                self.retile_all(true)?\n            }\n            SocketMessage::FlipLayout(layout_flip) => self.flip_layout(layout_flip)?,\n            SocketMessage::ScrollingLayoutColumns(count) => {\n                let focused_workspace = self.focused_workspace_mut()?;\n\n                let options = match focused_workspace.layout_options {\n                    Some(mut opts) => {\n                        if let Some(scrolling) = &mut opts.scrolling {\n                            scrolling.columns = count.into();\n                        }\n\n                        opts\n                    }\n                    None => LayoutOptions {\n                        scrolling: Some(ScrollingLayoutOptions {\n                            columns: count.into(),\n                            center_focused_column: Default::default(),\n                        }),\n                        grid: None,\n                        column_ratios: None,\n                        row_ratios: None,\n                    },\n                };\n\n                focused_workspace.layout_options = Some(options);\n                self.update_focused_workspace(false, false)?;\n            }\n            SocketMessage::ChangeLayout(layout) => self.change_workspace_layout_default(layout)?,\n            SocketMessage::CycleLayout(direction) => self.cycle_layout(direction)?,\n            SocketMessage::LayoutRatios(ref columns, ref rows) => {\n                use crate::core::validate_ratios;\n\n                let focused_workspace = self.focused_workspace_mut()?;\n\n                let mut options = focused_workspace.layout_options.unwrap_or(LayoutOptions {\n                    scrolling: None,\n                    grid: None,\n                    column_ratios: None,\n                    row_ratios: None,\n                });\n\n                if let Some(cols) = columns {\n                    options.column_ratios = Some(validate_ratios(cols));\n                }\n\n                if let Some(rws) = rows {\n                    options.row_ratios = Some(validate_ratios(rws));\n                }\n\n                focused_workspace.layout_options = Some(options);\n                self.update_focused_workspace(false, false)?;\n            }\n            SocketMessage::ChangeLayoutCustom(ref path) => {\n                self.change_workspace_custom_layout(path)?;\n            }\n            SocketMessage::WorkspaceLayoutCustom(monitor_idx, workspace_idx, ref path) => {\n                self.set_workspace_layout_custom(monitor_idx, workspace_idx, path)?;\n            }\n            SocketMessage::WorkspaceTiling(monitor_idx, workspace_idx, tile) => {\n                self.set_workspace_tiling(monitor_idx, workspace_idx, tile)?;\n            }\n            SocketMessage::WorkspaceLayout(monitor_idx, workspace_idx, layout) => {\n                self.set_workspace_layout_default(monitor_idx, workspace_idx, layout)?;\n            }\n            SocketMessage::WorkspaceLayoutRule(\n                monitor_idx,\n                workspace_idx,\n                at_container_count,\n                layout,\n            ) => {\n                self.add_workspace_layout_default_rule(\n                    monitor_idx,\n                    workspace_idx,\n                    at_container_count,\n                    layout,\n                )?;\n            }\n            SocketMessage::WorkspaceLayoutCustomRule(\n                monitor_idx,\n                workspace_idx,\n                at_container_count,\n                ref path,\n            ) => {\n                self.add_workspace_layout_custom_rule(\n                    monitor_idx,\n                    workspace_idx,\n                    at_container_count,\n                    path,\n                )?;\n            }\n            SocketMessage::ClearWorkspaceLayoutRules(monitor_idx, workspace_idx) => {\n                self.clear_workspace_layout_rules(monitor_idx, workspace_idx)?;\n            }\n            SocketMessage::NamedWorkspaceLayoutCustom(ref workspace, ref path) => {\n                if let Some((monitor_idx, workspace_idx)) =\n                    self.monitor_workspace_index_by_name(workspace)\n                {\n                    self.set_workspace_layout_custom(monitor_idx, workspace_idx, path)?;\n                }\n            }\n            SocketMessage::NamedWorkspaceTiling(ref workspace, tile) => {\n                if let Some((monitor_idx, workspace_idx)) =\n                    self.monitor_workspace_index_by_name(workspace)\n                {\n                    self.set_workspace_tiling(monitor_idx, workspace_idx, tile)?;\n                }\n            }\n            SocketMessage::NamedWorkspaceLayout(ref workspace, layout) => {\n                if let Some((monitor_idx, workspace_idx)) =\n                    self.monitor_workspace_index_by_name(workspace)\n                {\n                    self.set_workspace_layout_default(monitor_idx, workspace_idx, layout)?;\n                }\n            }\n            SocketMessage::NamedWorkspaceLayoutRule(ref workspace, at_container_count, layout) => {\n                if let Some((monitor_idx, workspace_idx)) =\n                    self.monitor_workspace_index_by_name(workspace)\n                {\n                    self.add_workspace_layout_default_rule(\n                        monitor_idx,\n                        workspace_idx,\n                        at_container_count,\n                        layout,\n                    )?;\n                }\n            }\n            SocketMessage::NamedWorkspaceLayoutCustomRule(\n                ref workspace,\n                at_container_count,\n                ref path,\n            ) => {\n                if let Some((monitor_idx, workspace_idx)) =\n                    self.monitor_workspace_index_by_name(workspace)\n                {\n                    self.add_workspace_layout_custom_rule(\n                        monitor_idx,\n                        workspace_idx,\n                        at_container_count,\n                        path,\n                    )?;\n                }\n            }\n            SocketMessage::ClearNamedWorkspaceLayoutRules(ref workspace) => {\n                if let Some((monitor_idx, workspace_idx)) =\n                    self.monitor_workspace_index_by_name(workspace)\n                {\n                    self.clear_workspace_layout_rules(monitor_idx, workspace_idx)?;\n                }\n            }\n            SocketMessage::CycleFocusWorkspace(direction) => {\n                // This is to ensure that even on an empty workspace on a secondary monitor, the\n                // secondary monitor where the cursor is focused will be used as the target for\n                // the workspace switch op\n                if let Some(monitor_idx) = self.monitor_idx_from_current_pos()\n                    && monitor_idx != self.focused_monitor_idx()\n                    && let Some(monitor) = self.monitors().get(monitor_idx)\n                    && let Some(workspace) = monitor.focused_workspace()\n                    && workspace.is_empty()\n                {\n                    self.focus_monitor(monitor_idx)?;\n                }\n\n                let focused_monitor = self.focused_monitor().ok_or_eyre(\"there is no monitor\")?;\n\n                let focused_workspace_idx = focused_monitor.focused_workspace_idx();\n                let workspaces = focused_monitor.workspaces().len();\n\n                let workspace_idx = direction.next_idx(\n                    focused_workspace_idx,\n                    NonZeroUsize::new(workspaces)\n                        .ok_or_eyre(\"there must be at least one workspace\")?,\n                );\n\n                self.focus_workspace(workspace_idx)?;\n            }\n            SocketMessage::CycleFocusEmptyWorkspace(direction) => {\n                // This is to ensure that even on an empty workspace on a secondary monitor, the\n                // secondary monitor where the cursor is focused will be used as the target for\n                // the workspace switch op\n                if let Some(monitor_idx) = self.monitor_idx_from_current_pos()\n                    && monitor_idx != self.focused_monitor_idx()\n                    && let Some(monitor) = self.monitors().get(monitor_idx)\n                    && let Some(workspace) = monitor.focused_workspace()\n                    && workspace.is_empty()\n                {\n                    self.focus_monitor(monitor_idx)?;\n                }\n\n                let focused_monitor = self.focused_monitor().ok_or_eyre(\"there is no monitor\")?;\n\n                let focused_workspace_idx = focused_monitor.focused_workspace_idx();\n                let workspaces = focused_monitor.workspaces().len();\n\n                let mut empty_workspaces = vec![];\n\n                for (idx, w) in focused_monitor.workspaces().iter().enumerate() {\n                    if w.is_empty() {\n                        empty_workspaces.push(idx);\n                    }\n                }\n\n                if !empty_workspaces.is_empty() {\n                    let mut workspace_idx = direction.next_idx(\n                        focused_workspace_idx,\n                        NonZeroUsize::new(workspaces)\n                            .ok_or_eyre(\"there must be at least one workspace\")?,\n                    );\n\n                    while !empty_workspaces.contains(&workspace_idx) {\n                        workspace_idx = direction.next_idx(\n                            workspace_idx,\n                            NonZeroUsize::new(workspaces)\n                                .ok_or_eyre(\"there must be at least one workspace\")?,\n                        );\n                    }\n\n                    self.focus_workspace(workspace_idx)?;\n                }\n            }\n            SocketMessage::CloseWorkspace => {\n                // This is to ensure that even on an empty workspace on a secondary monitor, the\n                // secondary monitor where the cursor is focused will be used as the target for\n                // the workspace switch op\n                if let Some(monitor_idx) = self.monitor_idx_from_current_pos()\n                    && monitor_idx != self.focused_monitor_idx()\n                    && let Some(monitor) = self.monitors().get(monitor_idx)\n                    && let Some(workspace) = monitor.focused_workspace()\n                    && workspace.is_empty()\n                {\n                    self.focus_monitor(monitor_idx)?;\n                }\n\n                let mut can_close = false;\n\n                if let Some(monitor) = self.focused_monitor_mut() {\n                    let focused_workspace_idx = monitor.focused_workspace_idx();\n                    let next_focused_workspace_idx = focused_workspace_idx.saturating_sub(1);\n\n                    if let Some(workspace) = monitor.focused_workspace()\n                        && monitor.workspaces().len() > 1\n                        && workspace.containers().is_empty()\n                        && workspace.floating_windows().is_empty()\n                        && workspace.monocle_container.is_none()\n                        && workspace.maximized_window.is_none()\n                        && workspace.name.is_none()\n                    {\n                        can_close = true;\n                    }\n\n                    if can_close\n                        && monitor\n                            .workspaces_mut()\n                            .remove(focused_workspace_idx)\n                            .is_some()\n                    {\n                        self.focus_workspace(next_focused_workspace_idx)?;\n                    }\n                }\n            }\n            SocketMessage::FocusLastWorkspace => {\n                // This is to ensure that even on an empty workspace on a secondary monitor, the\n                // secondary monitor where the cursor is focused will be used as the target for\n                // the workspace switch op\n                if let Some(monitor_idx) = self.monitor_idx_from_current_pos()\n                    && monitor_idx != self.focused_monitor_idx()\n                    && let Some(monitor) = self.monitors().get(monitor_idx)\n                    && let Some(workspace) = monitor.focused_workspace()\n                    && workspace.is_empty()\n                {\n                    self.focus_monitor(monitor_idx)?;\n                }\n\n                let idx = self\n                    .focused_monitor()\n                    .ok_or_eyre(\"there is no monitor\")?\n                    .focused_workspace_idx();\n\n                if let Some(monitor) = self.focused_monitor_mut()\n                    && let Some(last_focused_workspace) = monitor.last_focused_workspace\n                {\n                    self.focus_workspace(last_focused_workspace)?;\n                }\n\n                self.focused_monitor_mut()\n                    .ok_or_eyre(\"there is no monitor\")?\n                    .last_focused_workspace = Option::from(idx);\n            }\n            SocketMessage::FocusWorkspaceNumber(workspace_idx) => {\n                // This is to ensure that even on an empty workspace on a secondary monitor, the\n                // secondary monitor where the cursor is focused will be used as the target for\n                // the workspace switch op\n                if let Some(monitor_idx) = self.monitor_idx_from_current_pos()\n                    && monitor_idx != self.focused_monitor_idx()\n                    && let Some(monitor) = self.monitors().get(monitor_idx)\n                    && let Some(workspace) = monitor.focused_workspace()\n                    && workspace.is_empty()\n                {\n                    self.focus_monitor(monitor_idx)?;\n                }\n\n                if self.focused_workspace_idx().unwrap_or_default() != workspace_idx {\n                    self.focus_workspace(workspace_idx)?;\n                }\n            }\n            SocketMessage::FocusWorkspaceNumbers(workspace_idx) => {\n                // This is to ensure that even on an empty workspace on a secondary monitor, the\n                // secondary monitor where the cursor is focused will be used as the target for\n                // the workspace switch op\n                if let Some(monitor_idx) = self.monitor_idx_from_current_pos()\n                    && monitor_idx != self.focused_monitor_idx()\n                    && let Some(monitor) = self.monitors().get(monitor_idx)\n                    && let Some(workspace) = monitor.focused_workspace()\n                    && workspace.is_empty()\n                {\n                    self.focus_monitor(monitor_idx)?;\n                }\n\n                let focused_monitor_idx = self.focused_monitor_idx();\n\n                for (i, monitor) in self.monitors_mut().iter_mut().enumerate() {\n                    if i != focused_monitor_idx {\n                        monitor.focus_workspace(workspace_idx)?;\n                        monitor.load_focused_workspace(false)?;\n                    }\n                }\n\n                self.focus_workspace(workspace_idx)?;\n            }\n            SocketMessage::FocusMonitorWorkspaceNumber(monitor_idx, workspace_idx) => {\n                let focused_monitor_idx = self.focused_monitor_idx();\n                let focused_workspace_idx = self.focused_workspace_idx().unwrap_or_default();\n\n                let focused_pair = (focused_monitor_idx, focused_workspace_idx);\n\n                if focused_pair != (monitor_idx, workspace_idx) {\n                    self.focus_monitor(monitor_idx)?;\n                    self.focus_workspace(workspace_idx)?;\n                }\n            }\n            SocketMessage::FocusNamedWorkspace(ref name) => {\n                if let Some((monitor_idx, workspace_idx)) =\n                    self.monitor_workspace_index_by_name(name)\n                {\n                    self.focus_monitor(monitor_idx)?;\n                    self.focus_workspace(workspace_idx)?;\n                }\n            }\n            SocketMessage::ToggleWorkspaceLayer => {\n                let mouse_follows_focus = self.mouse_follows_focus;\n                let workspace = self.focused_workspace_mut()?;\n\n                let mut to_focus = None;\n                match workspace.layer {\n                    WorkspaceLayer::Tiling => {\n                        workspace.layer = WorkspaceLayer::Floating;\n\n                        let focused_idx = workspace.focused_floating_window_idx();\n                        let mut window_idx_pairs = workspace\n                            .floating_windows_mut()\n                            .make_contiguous()\n                            .iter()\n                            .enumerate()\n                            .collect::<Vec<_>>();\n\n                        // Sort by window area\n                        window_idx_pairs.sort_by_key(|(_, w)| {\n                            let rect = WindowsApi::window_rect(w.hwnd).unwrap_or_default();\n                            rect.right * rect.bottom\n                        });\n                        window_idx_pairs.reverse();\n\n                        for (i, window) in window_idx_pairs {\n                            if i == focused_idx {\n                                to_focus = Some(*window);\n                            } else {\n                                window.restore();\n                                window.raise()?;\n                            }\n                        }\n\n                        if let Some(focused_window) = &to_focus {\n                            // The focused window should be the last one raised to make sure it is\n                            // on top\n                            focused_window.restore();\n                            focused_window.raise()?;\n                        }\n\n                        for container in workspace.containers() {\n                            if let Some(window) = container.focused_window() {\n                                window.lower()?;\n                            }\n                        }\n\n                        if let Some(monocle) = &workspace.monocle_container\n                            && let Some(window) = monocle.focused_window()\n                        {\n                            window.lower()?;\n                        }\n                    }\n                    WorkspaceLayer::Floating => {\n                        workspace.layer = WorkspaceLayer::Tiling;\n\n                        if let Some(monocle) = &workspace.monocle_container {\n                            if let Some(window) = monocle.focused_window() {\n                                to_focus = Some(*window);\n                                window.raise()?;\n                            }\n                            for window in workspace.floating_windows() {\n                                window.hide();\n                            }\n                        } else {\n                            let focused_container_idx = workspace.focused_container_idx();\n                            for (i, container) in workspace.containers_mut().iter_mut().enumerate()\n                            {\n                                if let Some(window) = container.focused_window() {\n                                    if i == focused_container_idx {\n                                        to_focus = Some(*window);\n                                    }\n                                    window.raise()?;\n                                }\n                            }\n\n                            let mut window_idx_pairs = workspace\n                                .floating_windows_mut()\n                                .make_contiguous()\n                                .iter()\n                                .collect::<Vec<_>>();\n\n                            // Sort by window area\n                            window_idx_pairs.sort_by_key(|w| {\n                                let rect = WindowsApi::window_rect(w.hwnd).unwrap_or_default();\n                                rect.right * rect.bottom\n                            });\n\n                            for window in window_idx_pairs {\n                                window.lower()?;\n                            }\n                        }\n                    }\n                };\n                if let Some(window) = to_focus {\n                    window.focus(mouse_follows_focus)?;\n                }\n            }\n            SocketMessage::Stop => {\n                self.stop(false)?;\n            }\n            SocketMessage::StopIgnoreRestore => {\n                self.stop(true)?;\n            }\n            SocketMessage::MonitorIndexPreference(index_preference, left, top, right, bottom) => {\n                let mut monitor_index_preferences = MONITOR_INDEX_PREFERENCES.lock();\n                monitor_index_preferences.insert(\n                    index_preference,\n                    Rect {\n                        left,\n                        top,\n                        right,\n                        bottom,\n                    },\n                );\n            }\n            SocketMessage::DisplayIndexPreference(index_preference, ref display) => {\n                let mut display_index_preferences = DISPLAY_INDEX_PREFERENCES.write();\n                display_index_preferences.insert(index_preference, display.clone());\n            }\n            SocketMessage::EnsureWorkspaces(monitor_idx, workspace_count) => {\n                self.ensure_workspaces_for_monitor(monitor_idx, workspace_count)?;\n            }\n            SocketMessage::EnsureNamedWorkspaces(monitor_idx, ref names) => {\n                self.ensure_named_workspaces_for_monitor(monitor_idx, names)?;\n            }\n            SocketMessage::NewWorkspace => {\n                self.new_workspace()?;\n            }\n            SocketMessage::WorkspaceName(monitor_idx, workspace_idx, ref name) => {\n                self.set_workspace_name(monitor_idx, workspace_idx, name.to_string())?;\n            }\n            SocketMessage::State => {\n                let state = match serde_json::to_string_pretty(&state::State::from(&*self)) {\n                    Ok(state) => state,\n                    Err(error) => error.to_string(),\n                };\n\n                tracing::info!(\"replying to state\");\n\n                reply.write_all(state.as_bytes())?;\n\n                tracing::info!(\"replying to state done\");\n            }\n            SocketMessage::GlobalState => {\n                let state = match serde_json::to_string_pretty(&GlobalState::default()) {\n                    Ok(state) => state,\n                    Err(error) => error.to_string(),\n                };\n\n                tracing::info!(\"replying to global state\");\n\n                reply.write_all(state.as_bytes())?;\n\n                tracing::info!(\"replying to global state done\");\n            }\n            SocketMessage::VisibleWindows => {\n                let mut monitor_visible_windows = HashMap::new();\n\n                for monitor in self.monitors() {\n                    if let Some(ws) = monitor.focused_workspace() {\n                        monitor_visible_windows.insert(\n                            monitor.device_id.clone(),\n                            ws.visible_window_details().clone(),\n                        );\n                    }\n                }\n\n                let visible_windows_state = serde_json::to_string_pretty(&monitor_visible_windows)\n                    .unwrap_or_else(|error| error.to_string());\n\n                reply.write_all(visible_windows_state.as_bytes())?;\n            }\n            SocketMessage::MonitorInformation => {\n                let mut monitors = vec![];\n                for monitor in self.monitors() {\n                    monitors.push(MonitorInformation::from(monitor));\n                }\n\n                let monitors_state = serde_json::to_string_pretty(&monitors)\n                    .unwrap_or_else(|error| error.to_string());\n\n                reply.write_all(monitors_state.as_bytes())?;\n            }\n            SocketMessage::Query(query) => {\n                let response = match query {\n                    StateQuery::FocusedMonitorIndex => self.focused_monitor_idx().to_string(),\n                    StateQuery::FocusedWorkspaceIndex => self\n                        .focused_monitor()\n                        .ok_or_eyre(\"there is no monitor\")?\n                        .focused_workspace_idx()\n                        .to_string(),\n                    StateQuery::FocusedContainerIndex => self\n                        .focused_workspace()?\n                        .focused_container_idx()\n                        .to_string(),\n                    StateQuery::FocusedWindowIndex => {\n                        self.focused_container()?.focused_window_idx().to_string()\n                    }\n                    StateQuery::FocusedWorkspaceName => {\n                        let focused_monitor =\n                            self.focused_monitor().ok_or_eyre(\"there is no monitor\")?;\n\n                        focused_monitor\n                            .focused_workspace_name()\n                            .unwrap_or_else(|| focused_monitor.focused_workspace_idx().to_string())\n                    }\n                    StateQuery::Version => build::RUST_VERSION.to_string(),\n                    StateQuery::FocusedWorkspaceLayout => {\n                        let focused_monitor =\n                            self.focused_monitor().ok_or_eyre(\"there is no monitor\")?;\n\n                        focused_monitor.focused_workspace_layout().map_or_else(\n                            || \"None\".to_string(),\n                            |layout| match layout {\n                                Layout::Default(default_layout) => default_layout.to_string(),\n                                Layout::Custom(_) => \"Custom\".to_string(),\n                            },\n                        )\n                    }\n                    StateQuery::FocusedContainerKind => {\n                        match self.focused_workspace()?.focused_container() {\n                            None => \"None\".to_string(),\n                            Some(container) => {\n                                if container.windows().len() > 1 {\n                                    \"Stack\".to_string()\n                                } else {\n                                    \"Single\".to_string()\n                                }\n                            }\n                        }\n                    }\n                };\n\n                reply.write_all(response.as_bytes())?;\n            }\n            SocketMessage::ResizeWindowEdge(direction, sizing) => {\n                self.resize_window(direction, sizing, self.resize_delta, true)?;\n            }\n            SocketMessage::ResizeWindowAxis(axis, sizing) => {\n                // If the user has a custom layout, allow for the resizing of the primary column\n                // with this signal\n                let workspace = self.focused_workspace_mut()?;\n                let container_len = workspace.containers().len();\n                let no_layout_rules = workspace.layout_rules.is_empty();\n\n                if let Layout::Custom(custom) = &mut workspace.layout {\n                    if matches!(axis, Axis::Horizontal) {\n                        #[allow(clippy::cast_precision_loss)]\n                        let percentage = custom\n                            .primary_width_percentage()\n                            .unwrap_or(100.0 / (custom.len() as f32));\n\n                        if no_layout_rules {\n                            match sizing {\n                                Sizing::Increase => {\n                                    custom.set_primary_width_percentage(percentage + 5.0);\n                                }\n                                Sizing::Decrease => {\n                                    custom.set_primary_width_percentage(percentage - 5.0);\n                                }\n                            }\n                        } else {\n                            for rule in &mut workspace.layout_rules {\n                                if container_len >= rule.0\n                                    && let Layout::Custom(ref mut custom) = rule.1\n                                {\n                                    match sizing {\n                                        Sizing::Increase => {\n                                            custom.set_primary_width_percentage(percentage + 5.0);\n                                        }\n                                        Sizing::Decrease => {\n                                            custom.set_primary_width_percentage(percentage - 5.0);\n                                        }\n                                    }\n                                }\n                            }\n                        }\n                    }\n                    // Otherwise proceed with the resizing logic for individual window containers in the\n                    // assumed BSP layout\n                } else {\n                    match axis {\n                        Axis::Horizontal => {\n                            self.resize_window(\n                                OperationDirection::Left,\n                                sizing,\n                                self.resize_delta,\n                                false,\n                            )?;\n                            self.resize_window(\n                                OperationDirection::Right,\n                                sizing,\n                                self.resize_delta,\n                                false,\n                            )?;\n                        }\n                        Axis::Vertical => {\n                            self.resize_window(\n                                OperationDirection::Up,\n                                sizing,\n                                self.resize_delta,\n                                false,\n                            )?;\n                            self.resize_window(\n                                OperationDirection::Down,\n                                sizing,\n                                self.resize_delta,\n                                false,\n                            )?;\n                        }\n                        Axis::HorizontalAndVertical => {\n                            self.resize_window(\n                                OperationDirection::Left,\n                                sizing,\n                                self.resize_delta,\n                                false,\n                            )?;\n                            self.resize_window(\n                                OperationDirection::Right,\n                                sizing,\n                                self.resize_delta,\n                                false,\n                            )?;\n                            self.resize_window(\n                                OperationDirection::Up,\n                                sizing,\n                                self.resize_delta,\n                                false,\n                            )?;\n                            self.resize_window(\n                                OperationDirection::Down,\n                                sizing,\n                                self.resize_delta,\n                                false,\n                            )?;\n                        }\n                    }\n                }\n\n                self.update_focused_workspace(false, false)?;\n            }\n            SocketMessage::FocusFollowsMouse(mut implementation, enable) => {\n                if !CUSTOM_FFM.load(Ordering::SeqCst) {\n                    tracing::warn!(\n                        \"komorebi was not started with the --ffm flag, so the komorebi implementation of focus follows mouse cannot be enabled; defaulting to windows implementation\"\n                    );\n                    implementation = FocusFollowsMouseImplementation::Windows;\n                }\n\n                match implementation {\n                    FocusFollowsMouseImplementation::Komorebi => {\n                        if WindowsApi::focus_follows_mouse()? {\n                            tracing::warn!(\n                                \"the komorebi implementation of focus follows mouse cannot be enabled while the windows implementation is enabled\"\n                            );\n                        } else if enable {\n                            self.focus_follows_mouse = Option::from(implementation);\n                        } else {\n                            self.focus_follows_mouse = None;\n                            self.has_pending_raise_op = false;\n                        }\n                    }\n                    FocusFollowsMouseImplementation::Windows => {\n                        if matches!(\n                            self.focus_follows_mouse,\n                            Some(FocusFollowsMouseImplementation::Komorebi)\n                        ) {\n                            tracing::warn!(\n                                \"the windows implementation of focus follows mouse cannot be enabled while the komorebi implementation is enabled\"\n                            );\n                        } else if enable {\n                            WindowsApi::enable_focus_follows_mouse()?;\n                            self.focus_follows_mouse =\n                                Option::from(FocusFollowsMouseImplementation::Windows);\n                        } else {\n                            WindowsApi::disable_focus_follows_mouse()?;\n                            self.focus_follows_mouse = None;\n                        }\n                    }\n                }\n            }\n            SocketMessage::ToggleFocusFollowsMouse(mut implementation) => {\n                if !CUSTOM_FFM.load(Ordering::SeqCst) {\n                    tracing::warn!(\n                        \"komorebi was not started with the --ffm flag, so the komorebi implementation of focus follows mouse cannot be toggled; defaulting to windows implementation\"\n                    );\n                    implementation = FocusFollowsMouseImplementation::Windows;\n                }\n\n                match implementation {\n                    FocusFollowsMouseImplementation::Komorebi => {\n                        if WindowsApi::focus_follows_mouse()? {\n                            tracing::warn!(\n                                \"the komorebi implementation of focus follows mouse cannot be toggled while the windows implementation is enabled\"\n                            );\n                        } else {\n                            match self.focus_follows_mouse {\n                                None => {\n                                    self.focus_follows_mouse = Option::from(implementation);\n                                    self.has_pending_raise_op = false;\n                                }\n                                Some(FocusFollowsMouseImplementation::Komorebi) => {\n                                    self.focus_follows_mouse = None;\n                                }\n                                Some(FocusFollowsMouseImplementation::Windows) => {\n                                    tracing::warn!(\n                                        \"ignoring command that could mix different focus follows mouse implementations\"\n                                    );\n                                }\n                            }\n                        }\n                    }\n                    FocusFollowsMouseImplementation::Windows => {\n                        if matches!(\n                            self.focus_follows_mouse,\n                            Some(FocusFollowsMouseImplementation::Komorebi)\n                        ) {\n                            tracing::warn!(\n                                \"the windows implementation of focus follows mouse cannot be toggled while the komorebi implementation is enabled\"\n                            );\n                        } else {\n                            match self.focus_follows_mouse {\n                                None => {\n                                    WindowsApi::enable_focus_follows_mouse()?;\n                                    self.focus_follows_mouse = Option::from(implementation);\n                                }\n                                Some(FocusFollowsMouseImplementation::Windows) => {\n                                    WindowsApi::disable_focus_follows_mouse()?;\n                                    self.focus_follows_mouse = None;\n                                }\n                                Some(FocusFollowsMouseImplementation::Komorebi) => {\n                                    tracing::warn!(\n                                        \"ignoring command that could mix different focus follows mouse implementations\"\n                                    );\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n            SocketMessage::ReloadConfiguration => {\n                Self::reload_configuration();\n                force_update_borders = true;\n            }\n            SocketMessage::ReplaceConfiguration(ref config) => {\n                // Check that this is a valid static config file first\n                if StaticConfig::read(config).is_ok() {\n                    // Clear workspace rules; these will need to be replaced\n                    WORKSPACE_MATCHING_RULES.lock().clear();\n                    // Pause so that restored windows come to the foreground from all workspaces\n                    self.is_paused = true;\n                    // Bring all windows to the foreground\n                    self.restore_all_windows(false)?;\n\n                    // Create a new wm from the config path\n                    let mut wm = StaticConfig::preload(\n                        config,\n                        winevent_listener::event_rx(),\n                        self.command_listener.try_clone().ok(),\n                    )?;\n\n                    // Initialize the new wm\n                    wm.init()?;\n\n                    wm.restore_all_windows(true)?;\n\n                    // This is equivalent to StaticConfig::postload for this use case\n                    StaticConfig::reload(config, &mut wm)?;\n\n                    // Set self to the new wm instance\n                    *self = wm;\n\n                    // check if there are any bars\n                    let mut system = sysinfo::System::new_all();\n                    system.refresh_processes(sysinfo::ProcessesToUpdate::All, true);\n\n                    let has_bar = system\n                        .processes_by_name(\"komorebi-bar.exe\".as_ref())\n                        .next()\n                        .is_some();\n\n                    // stop bar(s)\n                    if has_bar {\n                        let script = r\"\nStop-Process -Name:komorebi-bar -ErrorAction SilentlyContinue\n                \";\n                        match powershell_script::run(script) {\n                            Ok(_) => {\n                                println!(\"{script}\");\n\n                                // start new bar(s)\n                                let mut config = StaticConfig::read(config)?;\n                                if let Some(display_bar_configurations) =\n                                    &mut config.bar_configurations\n                                {\n                                    for config_file_path in &mut *display_bar_configurations {\n                                        let script = r#\"Start-Process \"komorebi-bar\" '\"--config\" \"CONFIGFILE\"' -WindowStyle hidden\"#\n                                            .replace(\"CONFIGFILE\", &config_file_path.to_string_lossy());\n\n                                        match powershell_script::run(&script) {\n                                            Ok(_) => {\n                                                println!(\"{script}\");\n                                            }\n                                            Err(error) => {\n                                                println!(\"Error: {error}\");\n                                            }\n                                        }\n                                    }\n                                } else {\n                                    let script = r\"\nif (!(Get-Process komorebi-bar -ErrorAction SilentlyContinue))\n{\n  Start-Process komorebi-bar -WindowStyle hidden\n}\n                \";\n                                    match powershell_script::run(script) {\n                                        Ok(_) => {\n                                            println!(\"{script}\");\n                                        }\n                                        Err(error) => {\n                                            println!(\"Error: {error}\");\n                                        }\n                                    }\n                                }\n                            }\n                            Err(error) => {\n                                println!(\"Error: {error}\");\n                            }\n                        }\n                    }\n\n                    force_update_borders = true;\n                }\n            }\n            SocketMessage::ReloadStaticConfiguration(ref pathbuf) => {\n                self.reload_static_configuration(pathbuf)?;\n                force_update_borders = true;\n            }\n            SocketMessage::CompleteConfiguration => {\n                if !INITIAL_CONFIGURATION_LOADED.load(Ordering::SeqCst) {\n                    INITIAL_CONFIGURATION_LOADED.store(true, Ordering::SeqCst);\n                    self.update_focused_workspace(false, false)?;\n                    force_update_borders = true;\n                }\n            }\n            SocketMessage::WatchConfiguration(enable) => {\n                self.watch_configuration(enable)?;\n            }\n            SocketMessage::IdentifyObjectNameChangeApplication(identifier, ref id) => {\n                let mut identifiers = OBJECT_NAME_CHANGE_ON_LAUNCH.lock();\n\n                let mut should_push = true;\n                for i in &*identifiers {\n                    if let MatchingRule::Simple(i) = i\n                        && i.id.eq(id)\n                    {\n                        should_push = false;\n                    }\n                }\n\n                if should_push {\n                    identifiers.push(MatchingRule::Simple(IdWithIdentifier {\n                        kind: identifier,\n                        id: id.clone(),\n                        matching_strategy: Option::from(MatchingStrategy::Legacy),\n                    }));\n                }\n            }\n            SocketMessage::IdentifyTrayApplication(identifier, ref id) => {\n                let mut identifiers = TRAY_AND_MULTI_WINDOW_IDENTIFIERS.lock();\n                let mut should_push = true;\n                for i in &*identifiers {\n                    if let MatchingRule::Simple(i) = i\n                        && i.id.eq(id)\n                    {\n                        should_push = false;\n                    }\n                }\n\n                if should_push {\n                    identifiers.push(MatchingRule::Simple(IdWithIdentifier {\n                        kind: identifier,\n                        id: id.clone(),\n                        matching_strategy: Option::from(MatchingStrategy::Legacy),\n                    }));\n                }\n            }\n            SocketMessage::IdentifyLayeredApplication(identifier, ref id) => {\n                let mut identifiers = LAYERED_WHITELIST.lock();\n\n                let mut should_push = true;\n                for i in &*identifiers {\n                    if let MatchingRule::Simple(i) = i\n                        && i.id.eq(id)\n                    {\n                        should_push = false;\n                    }\n                }\n\n                if should_push {\n                    identifiers.push(MatchingRule::Simple(IdWithIdentifier {\n                        kind: identifier,\n                        id: id.clone(),\n                        matching_strategy: Option::from(MatchingStrategy::Legacy),\n                    }));\n                }\n            }\n            SocketMessage::ManageFocusedWindow => {\n                self.manage_focused_window()?;\n            }\n            SocketMessage::UnmanageFocusedWindow => {\n                self.unmanage_focused_window()?;\n            }\n            SocketMessage::InvisibleBorders(_rect) => {}\n            SocketMessage::WorkAreaOffset(rect) => {\n                self.work_area_offset = Option::from(rect);\n                self.retile_all(false)?;\n            }\n            SocketMessage::MonitorWorkAreaOffset(monitor_idx, rect) => {\n                if let Some(monitor) = self.monitors_mut().get_mut(monitor_idx) {\n                    monitor.work_area_offset = Option::from(rect);\n                    self.retile_all(false)?;\n                }\n            }\n            SocketMessage::WorkspaceWorkAreaOffset(monitor_idx, workspace_idx, rect) => {\n                if let Some(monitor) = self.monitors_mut().get_mut(monitor_idx)\n                    && let Some(workspace) = monitor.workspaces_mut().get_mut(workspace_idx)\n                {\n                    workspace.work_area_offset = Option::from(rect);\n                    self.retile_all(false)?\n                }\n            }\n            SocketMessage::ToggleWindowBasedWorkAreaOffset => {\n                let workspace = self.focused_workspace_mut()?;\n                workspace.apply_window_based_work_area_offset =\n                    !workspace.apply_window_based_work_area_offset;\n\n                self.retile_all(true)?;\n            }\n            SocketMessage::QuickSave => {\n                let workspace = self.focused_workspace()?;\n                let resize = &workspace.resize_dimensions;\n\n                let quicksave_json = std::env::temp_dir().join(\"komorebi.quicksave.json\");\n\n                let file = OpenOptions::new()\n                    .write(true)\n                    .truncate(true)\n                    .create(true)\n                    .open(quicksave_json)?;\n\n                serde_json::to_writer_pretty(&file, &resize)?;\n            }\n            SocketMessage::QuickLoad => {\n                let workspace = self.focused_workspace_mut()?;\n\n                let quicksave_json = std::env::temp_dir().join(\"komorebi.quicksave.json\");\n\n                let file = File::open(&quicksave_json).wrap_err(format!(\n                    \"no quicksave found at {}\",\n                    quicksave_json.display()\n                ))?;\n\n                let resize: Vec<Option<Rect>> = serde_json::from_reader(file)?;\n\n                workspace.resize_dimensions = resize;\n                self.update_focused_workspace(false, false)?;\n            }\n            SocketMessage::Save(ref path) => {\n                let workspace = self.focused_workspace_mut()?;\n                let resize = &workspace.resize_dimensions;\n\n                let file = OpenOptions::new()\n                    .write(true)\n                    .truncate(true)\n                    .create(true)\n                    .open(path)?;\n\n                serde_json::to_writer_pretty(&file, &resize)?;\n            }\n            SocketMessage::Load(ref path) => {\n                let workspace = self.focused_workspace_mut()?;\n\n                let file =\n                    File::open(path).wrap_err(format!(\"no file found at {}\", path.display()))?;\n\n                let resize: Vec<Option<Rect>> = serde_json::from_reader(file)?;\n\n                workspace.resize_dimensions = resize;\n                self.update_focused_workspace(false, false)?;\n            }\n            SocketMessage::AddSubscriberSocket(ref socket) => {\n                let mut sockets = SUBSCRIPTION_SOCKETS.lock();\n                let socket_path = DATA_DIR.join(socket);\n                sockets.insert(socket.clone(), socket_path);\n            }\n            SocketMessage::AddSubscriberSocketWithOptions(ref socket, options) => {\n                let mut sockets = SUBSCRIPTION_SOCKETS.lock();\n                let socket_path = DATA_DIR.join(socket);\n                sockets.insert(socket.clone(), socket_path);\n\n                let mut socket_options = SUBSCRIPTION_SOCKET_OPTIONS.lock();\n                socket_options.insert(socket.clone(), options);\n            }\n            SocketMessage::RemoveSubscriberSocket(ref socket) => {\n                let mut sockets = SUBSCRIPTION_SOCKETS.lock();\n                sockets.remove(socket);\n            }\n            SocketMessage::AddSubscriberPipe(ref subscriber) => {\n                let mut pipes = SUBSCRIPTION_PIPES.lock();\n                let pipe_path = format!(r\"\\\\.\\pipe\\{subscriber}\");\n                let pipe = connect(&pipe_path).wrap_err(\n                    format!(\"the named pipe '{}' has not yet been created; please create it before running this command\", pipe_path)\n                )?;\n\n                pipes.insert(subscriber.clone(), pipe);\n            }\n            SocketMessage::RemoveSubscriberPipe(ref subscriber) => {\n                let mut pipes = SUBSCRIPTION_PIPES.lock();\n                pipes.remove(subscriber);\n            }\n            SocketMessage::MouseFollowsFocus(enable) => {\n                self.mouse_follows_focus = enable;\n            }\n            SocketMessage::ToggleMouseFollowsFocus => {\n                self.mouse_follows_focus = !self.mouse_follows_focus;\n            }\n            SocketMessage::ResizeDelta(delta) => {\n                self.resize_delta = delta;\n            }\n            SocketMessage::ToggleWindowContainerBehaviour => {\n                match self.window_management_behaviour.current_behaviour {\n                    WindowContainerBehaviour::Create => {\n                        self.window_management_behaviour.current_behaviour =\n                            WindowContainerBehaviour::Append;\n                    }\n                    WindowContainerBehaviour::Append => {\n                        self.window_management_behaviour.current_behaviour =\n                            WindowContainerBehaviour::Create;\n                    }\n                }\n            }\n            SocketMessage::ToggleFloatOverride => {\n                self.window_management_behaviour.float_override =\n                    !self.window_management_behaviour.float_override;\n            }\n            SocketMessage::ToggleWorkspaceWindowContainerBehaviour => {\n                let current_global_behaviour = self.window_management_behaviour.current_behaviour;\n                if let Some(behaviour) =\n                    &mut self.focused_workspace_mut()?.window_container_behaviour\n                {\n                    match behaviour {\n                        WindowContainerBehaviour::Create => {\n                            *behaviour = WindowContainerBehaviour::Append\n                        }\n                        WindowContainerBehaviour::Append => {\n                            *behaviour = WindowContainerBehaviour::Create\n                        }\n                    }\n                } else {\n                    self.focused_workspace_mut()?.window_container_behaviour =\n                        Some(match current_global_behaviour {\n                            WindowContainerBehaviour::Create => WindowContainerBehaviour::Append,\n                            WindowContainerBehaviour::Append => WindowContainerBehaviour::Create,\n                        });\n                };\n            }\n            SocketMessage::ToggleWorkspaceFloatOverride => {\n                let current_global_override = self.window_management_behaviour.float_override;\n                if let Some(float_override) = &mut self.focused_workspace_mut()?.float_override {\n                    *float_override = !*float_override;\n                } else {\n                    self.focused_workspace_mut()?.float_override = Some(!current_global_override);\n                };\n            }\n            SocketMessage::WindowHidingBehaviour(behaviour) => {\n                let mut hiding_behaviour = HIDING_BEHAVIOUR.lock();\n                *hiding_behaviour = behaviour;\n            }\n            SocketMessage::ToggleCrossMonitorMoveBehaviour => {\n                match self.cross_monitor_move_behaviour {\n                    MoveBehaviour::Swap => {\n                        self.cross_monitor_move_behaviour = MoveBehaviour::Insert;\n                    }\n                    MoveBehaviour::Insert => {\n                        self.cross_monitor_move_behaviour = MoveBehaviour::Swap;\n                    }\n                    _ => {}\n                }\n            }\n            SocketMessage::CrossMonitorMoveBehaviour(behaviour) => {\n                self.cross_monitor_move_behaviour = behaviour;\n            }\n            SocketMessage::UnmanagedWindowOperationBehaviour(behaviour) => {\n                self.unmanaged_window_operation_behaviour = behaviour;\n            }\n            SocketMessage::Border(enable) => {\n                border_manager::BORDER_ENABLED.store(enable, Ordering::SeqCst);\n                if !enable {\n                    match IMPLEMENTATION.load() {\n                        BorderImplementation::Komorebi => {\n                            border_manager::destroy_all_borders()?;\n                        }\n                        BorderImplementation::Windows => {\n                            self.remove_all_accents()?;\n                        }\n                    }\n                } else if matches!(IMPLEMENTATION.load(), BorderImplementation::Komorebi) {\n                    force_update_borders = true;\n                }\n            }\n            SocketMessage::BorderImplementation(implementation) => {\n                if !*WINDOWS_11 && matches!(implementation, BorderImplementation::Windows) {\n                    tracing::error!(\n                        \"BorderImplementation::Windows is only supported on Windows 11 and above\"\n                    );\n                } else {\n                    IMPLEMENTATION.store(implementation);\n                    match IMPLEMENTATION.load() {\n                        BorderImplementation::Komorebi => {\n                            self.remove_all_accents()?;\n                            force_update_borders = true;\n                        }\n                        BorderImplementation::Windows => {\n                            border_manager::destroy_all_borders()?;\n                        }\n                    }\n                }\n            }\n            SocketMessage::BorderColour(kind, r, g, b) => {\n                match kind {\n                    WindowKind::Single => {\n                        border_manager::FOCUSED.store(Rgb::new(r, g, b).into(), Ordering::SeqCst);\n                    }\n                    WindowKind::Stack => {\n                        border_manager::STACK.store(Rgb::new(r, g, b).into(), Ordering::SeqCst);\n                    }\n                    WindowKind::Monocle => {\n                        border_manager::MONOCLE.store(Rgb::new(r, g, b).into(), Ordering::SeqCst);\n                    }\n                    WindowKind::Unfocused => {\n                        border_manager::UNFOCUSED.store(Rgb::new(r, g, b).into(), Ordering::SeqCst);\n                    }\n                    WindowKind::UnfocusedLocked => {\n                        border_manager::UNFOCUSED_LOCKED\n                            .store(Rgb::new(r, g, b).into(), Ordering::SeqCst);\n                    }\n                    WindowKind::Floating => {\n                        border_manager::FLOATING.store(Rgb::new(r, g, b).into(), Ordering::SeqCst);\n                    }\n                }\n                force_update_borders = true;\n            }\n            SocketMessage::BorderStyle(style) => {\n                STYLE.store(style);\n                force_update_borders = true;\n            }\n            SocketMessage::BorderWidth(width) => {\n                border_manager::BORDER_WIDTH.store(width, Ordering::SeqCst);\n                force_update_borders = true;\n            }\n            SocketMessage::BorderOffset(offset) => {\n                border_manager::BORDER_OFFSET.store(offset, Ordering::SeqCst);\n                force_update_borders = true;\n            }\n            SocketMessage::Animation(enable, prefix) => match prefix {\n                Some(prefix) => {\n                    ANIMATION_ENABLED_PER_ANIMATION\n                        .lock()\n                        .insert(prefix, enable);\n                }\n                None => {\n                    ANIMATION_ENABLED_GLOBAL.store(enable, Ordering::SeqCst);\n                    ANIMATION_ENABLED_PER_ANIMATION.lock().clear();\n                }\n            },\n            SocketMessage::AnimationDuration(duration, prefix) => match prefix {\n                Some(prefix) => {\n                    ANIMATION_DURATION_PER_ANIMATION\n                        .lock()\n                        .insert(prefix, duration);\n                }\n                None => {\n                    ANIMATION_DURATION_GLOBAL.store(duration, Ordering::SeqCst);\n                    ANIMATION_DURATION_PER_ANIMATION.lock().clear();\n                }\n            },\n            SocketMessage::AnimationFps(fps) => {\n                ANIMATION_FPS.store(fps, Ordering::SeqCst);\n            }\n            SocketMessage::AnimationStyle(style, prefix) => match prefix {\n                Some(prefix) => {\n                    ANIMATION_STYLE_PER_ANIMATION.lock().insert(prefix, style);\n                }\n                None => {\n                    let mut animation_style = ANIMATION_STYLE_GLOBAL.lock();\n                    *animation_style = style;\n                    ANIMATION_STYLE_PER_ANIMATION.lock().clear();\n                }\n            },\n            SocketMessage::ToggleTransparency => {\n                let current = transparency_manager::TRANSPARENCY_ENABLED.load(Ordering::SeqCst);\n                transparency_manager::TRANSPARENCY_ENABLED.store(!current, Ordering::SeqCst);\n            }\n            SocketMessage::Transparency(enable) => {\n                transparency_manager::TRANSPARENCY_ENABLED.store(enable, Ordering::SeqCst);\n            }\n            SocketMessage::TransparencyAlpha(alpha) => {\n                transparency_manager::TRANSPARENCY_ALPHA.store(alpha, Ordering::SeqCst);\n            }\n            SocketMessage::StackbarMode(mode) => {\n                STACKBAR_MODE.store(mode);\n                self.retile_all(true)?;\n            }\n            SocketMessage::StackbarLabel(label) => {\n                STACKBAR_LABEL.store(label);\n            }\n            SocketMessage::StackbarFocusedTextColour(r, g, b) => {\n                let rgb = Rgb::new(r, g, b);\n                STACKBAR_FOCUSED_TEXT_COLOUR.store(rgb.into(), Ordering::SeqCst);\n            }\n            SocketMessage::StackbarUnfocusedTextColour(r, g, b) => {\n                let rgb = Rgb::new(r, g, b);\n                STACKBAR_UNFOCUSED_TEXT_COLOUR.store(rgb.into(), Ordering::SeqCst);\n            }\n            SocketMessage::StackbarBackgroundColour(r, g, b) => {\n                let rgb = Rgb::new(r, g, b);\n                STACKBAR_TAB_BACKGROUND_COLOUR.store(rgb.into(), Ordering::SeqCst);\n            }\n            SocketMessage::StackbarHeight(height) => {\n                STACKBAR_TAB_HEIGHT.store(height, Ordering::SeqCst);\n            }\n            SocketMessage::StackbarTabWidth(width) => {\n                STACKBAR_TAB_WIDTH.store(width, Ordering::SeqCst);\n            }\n            SocketMessage::StackbarFontSize(size) => {\n                STACKBAR_FONT_SIZE.store(size, Ordering::SeqCst);\n            }\n            #[allow(clippy::assigning_clones)]\n            SocketMessage::StackbarFontFamily(ref font_family) => {\n                *STACKBAR_FONT_FAMILY.lock() = font_family.clone();\n            }\n            SocketMessage::ApplicationSpecificConfigurationSchema => {\n                #[cfg(feature = \"schemars\")]\n                {\n                    let asc = schemars::schema_for!(\n                        Vec<crate::core::config_generation::ApplicationConfiguration>\n                    );\n                    let schema = serde_json::to_string_pretty(&asc)?;\n\n                    reply.write_all(schema.as_bytes())?;\n                }\n            }\n            SocketMessage::NotificationSchema => {\n                #[cfg(feature = \"schemars\")]\n                {\n                    let notification = schemars::schema_for!(Notification);\n                    let schema = serde_json::to_string_pretty(&notification)?;\n\n                    reply.write_all(schema.as_bytes())?;\n                }\n            }\n            SocketMessage::SocketSchema => {\n                #[cfg(feature = \"schemars\")]\n                {\n                    let socket_message = schemars::schema_for!(SocketMessage);\n                    let schema = serde_json::to_string_pretty(&socket_message)?;\n\n                    reply.write_all(schema.as_bytes())?;\n                }\n            }\n            SocketMessage::StaticConfigSchema => {\n                #[cfg(feature = \"schemars\")]\n                {\n                    let socket_message = schemars::schema_for!(SocketMessage);\n                    let schema = serde_json::to_string_pretty(&socket_message)?;\n\n                    reply.write_all(schema.as_bytes())?;\n                }\n            }\n            SocketMessage::GenerateStaticConfig => {\n                let config = serde_json::to_string_pretty(&StaticConfig::from(&*self))?;\n\n                reply.write_all(config.as_bytes())?;\n            }\n            SocketMessage::RemoveTitleBar(identifier, ref id) => {\n                let mut identifiers = NO_TITLEBAR.lock();\n\n                let mut should_push = true;\n                for i in &*identifiers {\n                    if let MatchingRule::Simple(i) = i\n                        && i.id.eq(id)\n                    {\n                        should_push = false;\n                    }\n                }\n\n                if should_push {\n                    identifiers.push(MatchingRule::Simple(IdWithIdentifier {\n                        kind: identifier,\n                        id: id.clone(),\n                        matching_strategy: Option::from(MatchingStrategy::Legacy),\n                    }));\n                }\n            }\n            SocketMessage::ToggleTitleBars => {\n                let current = REMOVE_TITLEBARS.load(Ordering::SeqCst);\n                REMOVE_TITLEBARS.store(!current, Ordering::SeqCst);\n                self.update_focused_workspace(false, false)?;\n            }\n            SocketMessage::DebugWindow(hwnd) => {\n                let window = Window::from(hwnd);\n                let mut rule_debug = RuleDebug::default();\n                let _ = window.should_manage(None, &mut rule_debug);\n                let schema = serde_json::to_string_pretty(&rule_debug)?;\n\n                reply.write_all(schema.as_bytes())?;\n            }\n            SocketMessage::Theme(ref theme) => {\n                theme_manager::send_notification(*theme.clone());\n            }\n            // Deprecated commands\n            SocketMessage::AltFocusHack(_)\n            | SocketMessage::IdentifyBorderOverflowApplication(_, _) => {}\n        };\n\n        // Update list of known_hwnds and their monitor/workspace index pair\n        self.update_known_hwnds();\n\n        notify_subscribers(\n            Notification {\n                event: NotificationEvent::Socket(message.clone()),\n                state: self.as_ref().into(),\n            },\n            initial_state.has_been_modified(self.as_ref()),\n        )?;\n\n        if force_update_borders {\n            border_manager::send_force_update();\n        } else {\n            border_manager::send_notification(None);\n        }\n        transparency_manager::send_notification();\n        stackbar_manager::send_notification();\n\n        tracing::info!(\"processed\");\n        Ok(())\n    }\n}\n\npub fn read_commands_uds(\n    wm: &Arc<Mutex<WindowManager>>,\n    mut stream: UnixStream,\n) -> eyre::Result<()> {\n    let reader = BufReader::new(stream.try_clone()?);\n    // TODO(raggi): while this processes more than one command, if there are\n    // replies there is no clearly defined protocol for framing yet - it's\n    // perhaps whole-json objects for now, but termination is signalled by\n    // socket shutdown.\n    for line in reader.lines() {\n        let message = SocketMessage::from_str(&line?)?;\n\n        match wm.try_lock_for(Duration::from_secs(1)) {\n            None => {\n                tracing::warn!(\n                    \"could not acquire window manager lock, not processing message: {message}\"\n                );\n            }\n            Some(mut wm) => {\n                if wm.is_paused {\n                    return match message {\n                        SocketMessage::TogglePause\n                        | SocketMessage::State\n                        | SocketMessage::GlobalState\n                        | SocketMessage::Stop => Ok(wm.process_command(message, &mut stream)?),\n                        _ => {\n                            tracing::trace!(\"ignoring while paused\");\n                            Ok(())\n                        }\n                    };\n                }\n\n                wm.process_command(message.clone(), &mut stream)?;\n            }\n        }\n    }\n\n    Ok(())\n}\n\npub fn read_commands_tcp(\n    wm: &Arc<Mutex<WindowManager>>,\n    stream: &mut TcpStream,\n    addr: &str,\n) -> eyre::Result<()> {\n    let mut reader = BufReader::new(stream.try_clone()?);\n\n    loop {\n        let mut buf = vec![0; 1024];\n        match reader.read(&mut buf) {\n            Err(..) => {\n                tracing::warn!(\"removing disconnected tcp client: {addr}\");\n                let mut connections = TCP_CONNECTIONS.lock();\n                connections.remove(addr);\n                break;\n            }\n            Ok(size) => {\n                let Ok(message) = SocketMessage::from_str(&String::from_utf8_lossy(&buf[..size]))\n                else {\n                    tracing::warn!(\"client sent an invalid message, disconnecting: {addr}\");\n                    let mut connections = TCP_CONNECTIONS.lock();\n                    connections.remove(addr);\n                    break;\n                };\n\n                let mut wm = wm.lock();\n\n                if wm.is_paused {\n                    return match message {\n                        SocketMessage::TogglePause\n                        | SocketMessage::State\n                        | SocketMessage::GlobalState\n                        | SocketMessage::Stop => Ok(wm.process_command(message, stream)?),\n                        _ => {\n                            tracing::trace!(\"ignoring while paused\");\n                            Ok(())\n                        }\n                    };\n                }\n\n                wm.process_command(message.clone(), &mut *stream)?;\n            }\n        }\n    }\n\n    Ok(())\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::Rect;\n    use crate::SocketMessage;\n    use crate::WindowManagerEvent;\n    use crate::monitor;\n    use crate::window_manager::WindowManager;\n    use crossbeam_channel::Receiver;\n    use crossbeam_channel::Sender;\n    use crossbeam_channel::bounded;\n    use std::io::BufRead;\n    use std::io::BufReader;\n    use std::io::Write;\n    use std::path::PathBuf;\n    use std::str::FromStr;\n    use std::time::Duration;\n    use uds_windows::UnixStream;\n    use uuid::Uuid;\n\n    fn send_socket_message(socket: &PathBuf, message: SocketMessage) {\n        let mut stream = UnixStream::connect(socket).unwrap();\n        stream\n            .set_write_timeout(Some(Duration::from_secs(1)))\n            .unwrap();\n        stream\n            .write_all(serde_json::to_string(&message).unwrap().as_bytes())\n            .unwrap();\n    }\n\n    #[test]\n    fn test_receive_socket_message() {\n        let (_sender, receiver): (Sender<WindowManagerEvent>, Receiver<WindowManagerEvent>) =\n            bounded(1);\n        let socket_name = format!(\"komorebi-test-{}.sock\", Uuid::new_v4());\n        let socket_path = PathBuf::from(&socket_name);\n        let mut wm = WindowManager::new(receiver, Some(socket_path.clone())).unwrap();\n        let m = monitor::new(\n            0,\n            Rect::default(),\n            Rect::default(),\n            \"TestMonitor\".to_string(),\n            \"TestDevice\".to_string(),\n            \"TestDeviceID\".to_string(),\n            Some(\"TestMonitorID\".to_string()),\n        );\n\n        wm.monitors_mut().push_back(m);\n\n        // send a message\n        send_socket_message(&socket_path, SocketMessage::FocusWorkspaceNumber(5));\n\n        let (stream, _) = wm.command_listener.accept().unwrap();\n        let reader = BufReader::new(stream.try_clone().unwrap());\n        let next = reader.lines().next();\n\n        // read and deserialize the message\n        let message_string = next.unwrap().unwrap();\n        let message = SocketMessage::from_str(&message_string).unwrap();\n        assert!(matches!(message, SocketMessage::FocusWorkspaceNumber(5)));\n\n        // process the message\n        wm.process_command(message, stream).unwrap();\n\n        // check the updated window manager state\n        assert_eq!(wm.focused_workspace_idx().unwrap(), 5);\n\n        std::fs::remove_file(socket_path).unwrap();\n    }\n}\n"
  },
  {
    "path": "komorebi/src/process_event.rs",
    "content": "use std::process::Command;\nuse std::sync::Arc;\nuse std::sync::atomic::Ordering;\n\nuse color_eyre::eyre;\nuse color_eyre::eyre::OptionExt;\nuse crossbeam_utils::atomic::AtomicConsume;\nuse parking_lot::Mutex;\n\nuse crate::core::OperationDirection;\nuse crate::core::Rect;\nuse crate::core::Sizing;\nuse crate::core::WindowContainerBehaviour;\n\nuse crate::CURRENT_VIRTUAL_DESKTOP;\nuse crate::DefaultLayout;\nuse crate::FLOATING_APPLICATIONS;\nuse crate::HIDDEN_HWNDS;\nuse crate::Layout;\nuse crate::Notification;\nuse crate::NotificationEvent;\nuse crate::REGEX_IDENTIFIERS;\nuse crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;\nuse crate::VirtualDesktopNotification;\nuse crate::Window;\nuse crate::border_manager;\nuse crate::border_manager::BORDER_OFFSET;\nuse crate::border_manager::BORDER_WIDTH;\nuse crate::current_virtual_desktop;\nuse crate::notify_subscribers;\nuse crate::splash;\nuse crate::splash::mdm_enrollment;\nuse crate::stackbar_manager;\nuse crate::state::State;\nuse crate::transparency_manager;\nuse crate::window::RuleDebug;\nuse crate::window::should_act;\nuse crate::window_manager::WindowManager;\nuse crate::window_manager_event::WindowManagerEvent;\nuse crate::windows_api::WindowsApi;\nuse crate::winevent::WinEvent;\nuse crate::workspace::WorkspaceLayer;\n\n#[tracing::instrument]\npub fn listen_for_events(wm: Arc<Mutex<WindowManager>>) {\n    let receiver = wm.lock().incoming_events.clone();\n\n    std::thread::spawn(|| {\n        loop {\n            if let Ok((mdm, server)) = mdm_enrollment() {\n                #[allow(clippy::collapsible_if)]\n                if mdm && splash::should().map(|f| f.into()).unwrap_or(true) {\n                    let mut args = vec![\"splash\".to_string()];\n                    if let Some(server) = server {\n                        if !server.trim().is_empty() {\n                            args.push(server);\n                        }\n                    }\n\n                    let _ = Command::new(\"komorebic\").args(&args).spawn();\n                }\n            }\n\n            std::thread::sleep(std::time::Duration::from_secs(14400));\n        }\n    });\n\n    std::thread::spawn(move || {\n        tracing::info!(\"listening\");\n        loop {\n            if let Ok(event) = receiver.recv() {\n                let mut guard = wm.lock();\n                match guard.process_event(event) {\n                    Ok(()) => {}\n                    Err(error) => {\n                        if cfg!(debug_assertions) {\n                            tracing::error!(\"{:?}\", error)\n                        } else {\n                            tracing::error!(\"{}\", error)\n                        }\n                    }\n                }\n            }\n        }\n    });\n}\n\nimpl WindowManager {\n    #[allow(clippy::too_many_lines, clippy::cognitive_complexity)]\n    #[tracing::instrument(skip(self, event), fields(event = event.title(), winevent = event.winevent(), hwnd = event.hwnd()))]\n    pub fn process_event(&mut self, event: WindowManagerEvent) -> eyre::Result<()> {\n        if self.is_paused {\n            tracing::trace!(\"ignoring while paused\");\n            return Ok(());\n        }\n\n        let mut rule_debug = RuleDebug::default();\n\n        let should_manage = event.window().should_manage(Some(event), &mut rule_debug)?;\n\n        // All event handlers below this point should only be processed if the event is\n        // related to a window that should be managed by the WindowManager.\n        if !should_manage {\n            let mut transparency_override = false;\n\n            if transparency_manager::TRANSPARENCY_ENABLED.load_consume() {\n                for m in self.monitors() {\n                    for w in m.workspaces() {\n                        let event_hwnd = event.window().hwnd;\n\n                        let visible_hwnds = w\n                            .visible_windows()\n                            .iter()\n                            .flatten()\n                            .map(|w| w.hwnd)\n                            .collect::<Vec<_>>();\n\n                        let contains_managed_window = w.contains_managed_window(event_hwnd);\n\n                        // this is for an old stackbar clicking fix\n                        if contains_managed_window && !visible_hwnds.contains(&event_hwnd) {\n                            transparency_override = true;\n                        }\n\n                        // but we always want to handle a minimize event when transparency overrides\n                        // are applied\n                        if !transparency_override\n                            && contains_managed_window\n                            && matches!(event, WindowManagerEvent::Minimize(_, _))\n                        {\n                            transparency_override = true;\n                        }\n                    }\n                }\n            }\n\n            if !transparency_override {\n                if rule_debug.matches_ignore_identifier.is_some() {\n                    border_manager::send_notification(Option::from(event.hwnd()));\n                }\n\n                return Ok(());\n            }\n        }\n\n        let mut last_known_virtual_desktop_id = CURRENT_VIRTUAL_DESKTOP.lock();\n\n        if let Some(virtual_desktop_id) = &self.virtual_desktop_id {\n            let latest_virtual_desktop_id = current_virtual_desktop();\n            if let Some(id) = latest_virtual_desktop_id {\n                // if we are on the vd associated with komorebi\n                let should_retile = id == *virtual_desktop_id\n                    // and we came from a vd not associated with komorebi\n                    && (*last_known_virtual_desktop_id).clone().unwrap_or_default() != id;\n\n                *last_known_virtual_desktop_id = Some(id.clone());\n                if id != *virtual_desktop_id {\n                    tracing::info!(\n                        \"ignoring events and commands while not on virtual desktop {:?}\",\n                        virtual_desktop_id\n                    );\n\n                    // TODO: when returning from another VD to the VD associated with komorebi\n                    // if borders are enabled, they will not be drawn again until the user interacts\n                    // with the workspace or forces a retile\n                    border_manager::destroy_all_borders()?;\n\n                    // to be consumed by integrating gui applications like bars to know\n                    // when to hide visual components which don't make sense when not on\n                    // komorebi's associated virtual desktop\n                    tracing::debug!(\n                        \"notifying subscribers that we have left komorebi's associated virtual desktop\"\n                    );\n                    notify_subscribers(\n                        Notification {\n                            event: NotificationEvent::VirtualDesktop(\n                                VirtualDesktopNotification::LeftAssociatedVirtualDesktop,\n                            ),\n                            state: self.as_ref().into(),\n                        },\n                        true,\n                    )?;\n\n                    return Ok(());\n                }\n\n                if should_retile {\n                    self.retile_all(true)?;\n\n                    // to be consumed by integrating gui applications like bars to know\n                    // when to show visual components associated with komorebi's virtual\n                    // desktop\n                    tracing::debug!(\n                        \"notifying subscribers that we are back on komorebi's associated virtual desktop\"\n                    );\n                    notify_subscribers(\n                        Notification {\n                            event: NotificationEvent::VirtualDesktop(\n                                VirtualDesktopNotification::EnteredAssociatedVirtualDesktop,\n                            ),\n                            state: self.as_ref().into(),\n                        },\n                        true,\n                    )?;\n                }\n            }\n        }\n\n        #[allow(clippy::useless_asref)]\n        // We don't have From implemented for &mut WindowManager\n        let initial_state = State::from(self.as_ref());\n\n        // Make sure we have the most recently focused monitor from any event\n        match event {\n            WindowManagerEvent::FocusChange(_, window)\n            | WindowManagerEvent::Show(_, window)\n            | WindowManagerEvent::MoveResizeEnd(_, window) => {\n                if let Some(monitor_idx) = self.monitor_idx_from_window(window) {\n                    // This is a hidden window apparently associated with COM support mechanisms (based\n                    // on a post from http://www.databaseteam.org/1-ms-sql-server/a5bb344836fb889c.htm)\n                    //\n                    // The hidden window, OLEChannelWnd, associated with this class (spawned by\n                    // explorer.exe), after some debugging, is observed to always be tied to the primary\n                    // display monitor, or (usually) monitor 0 in the WindowManager state.\n                    //\n                    // Due to this, at least one user in the Discord has witnessed behaviour where, when\n                    // a MonitorPoll event is triggered by OLEChannelWnd, the focused monitor index gets\n                    // set repeatedly to 0, regardless of where the current foreground window is actually\n                    // located.\n                    //\n                    // This check ensures that we only update the focused monitor when the window\n                    // triggering monitor reconciliation is known to not be tied to a specific monitor.\n                    if let Ok(class) = window.class()\n                        && class != \"OleMainThreadWndClass\"\n                        && self.focused_monitor_idx() != monitor_idx\n                    {\n                        self.focus_monitor(monitor_idx)?;\n                    }\n                }\n            }\n            _ => {}\n        }\n\n        self.enforce_workspace_rules()?;\n\n        if matches!(event, WindowManagerEvent::MouseCapture(..)) {\n            tracing::trace!(\n                \"only reaping orphans and enforcing workspace rules for mouse capture event\"\n            );\n            return Ok(());\n        }\n\n        match event {\n            WindowManagerEvent::Raise(window) => {\n                window.focus(false)?;\n                self.has_pending_raise_op = false;\n            }\n            WindowManagerEvent::Destroy(_, window) | WindowManagerEvent::Unmanage(window) => {\n                if self.focused_workspace()?.contains_window(window.hwnd) {\n                    self.focused_workspace_mut()?.remove_window(window.hwnd)?;\n                    self.update_focused_workspace(false, false)?;\n\n                    let mut already_moved_window_handles = self.already_moved_window_handles.lock();\n\n                    already_moved_window_handles.remove(&window.hwnd);\n                }\n            }\n            WindowManagerEvent::Minimize(_, window) => {\n                // During transient display connection changes (e.g. monitor\n                // briefly disconnecting and reconnecting), Windows may fire\n                // SystemMinimizeStart for windows on the affected monitor.\n                // We must not treat these OS-initiated minimizes as user\n                // actions, otherwise the window gets removed from the\n                // workspace and the reconciliator cannot restore it.\n                if crate::monitor_reconciliator::display_change_in_progress(\n                    std::time::Duration::from_secs(10),\n                ) {\n                    tracing::debug!(\n                        \"ignoring minimize during display connection change for hwnd: {}\",\n                        window.hwnd\n                    );\n                } else {\n                    let mut hide = false;\n\n                    {\n                        let programmatically_hidden_hwnds = HIDDEN_HWNDS.lock();\n                        if !programmatically_hidden_hwnds.contains(&window.hwnd) {\n                            hide = true;\n                        }\n                    }\n\n                    if hide {\n                        self.focused_workspace_mut()?.remove_window(window.hwnd)?;\n                        self.update_focused_workspace(false, false)?;\n                    }\n                }\n            }\n            WindowManagerEvent::Hide(_, window) => {\n                let mut hide = false;\n                // Some major applications unfortunately send the HIDE signal when they are being\n                // minimized or destroyed. Applications that close to the tray also do the same,\n                // and will have is_window() return true, as the process is still running even if\n                // the window is not visible.\n                {\n                    let tray_and_multi_window_identifiers =\n                        TRAY_AND_MULTI_WINDOW_IDENTIFIERS.lock();\n                    let regex_identifiers = REGEX_IDENTIFIERS.lock();\n\n                    let title = &window.title()?;\n                    let exe_name = &window.exe()?;\n                    let class = &window.class()?;\n                    let path = &window.path()?;\n\n                    // We don't want to purge windows that have been deliberately hidden by us, eg. when\n                    // they are not on the top of a container stack.\n                    let programmatically_hidden_hwnds = HIDDEN_HWNDS.lock();\n                    let should_act = should_act(\n                        title,\n                        exe_name,\n                        class,\n                        path,\n                        &tray_and_multi_window_identifiers,\n                        &regex_identifiers,\n                    )\n                    .is_some();\n\n                    if !window.is_window()\n                        || (should_act && !programmatically_hidden_hwnds.contains(&window.hwnd))\n                    {\n                        hide = true;\n                    }\n                }\n\n                if hide {\n                    self.focused_workspace_mut()?.remove_window(window.hwnd)?;\n                    self.update_focused_workspace(false, false)?;\n                }\n\n                let mut already_moved_window_handles = self.already_moved_window_handles.lock();\n\n                already_moved_window_handles.remove(&window.hwnd);\n            }\n            WindowManagerEvent::FocusChange(_, window) => {\n                // don't want to trigger the full workspace updates when there are no managed\n                // containers - this makes floating windows on empty workspaces go into very\n                // annoying focus change loops which prevents users from interacting with them\n                if !matches!(\n                    self.focused_workspace()?.layout,\n                    Layout::Default(DefaultLayout::Scrolling)\n                ) && !self.focused_workspace()?.containers().is_empty()\n                {\n                    self.update_focused_workspace(self.mouse_follows_focus, false)?;\n                }\n\n                let workspace = self.focused_workspace_mut()?;\n                let floating_window_idx = workspace\n                    .floating_windows()\n                    .iter()\n                    .position(|w| w.hwnd == window.hwnd);\n\n                match floating_window_idx {\n                    None => {\n                        if let Some(w) = &workspace.maximized_window\n                            && w.hwnd == window.hwnd\n                        {\n                            return Ok(());\n                        }\n\n                        if let Some(monocle) = &workspace.monocle_container {\n                            if let Some(window) = monocle.focused_window() {\n                                window.focus(false)?;\n                            }\n                        } else {\n                            workspace.focus_container_by_window(window.hwnd)?;\n                        }\n\n                        workspace.layer = WorkspaceLayer::Tiling;\n\n                        if matches!(\n                            self.focused_workspace()?.layout,\n                            Layout::Default(DefaultLayout::Scrolling)\n                        ) && !self.focused_workspace()?.containers().is_empty()\n                        {\n                            self.update_focused_workspace(self.mouse_follows_focus, false)?;\n                        }\n                    }\n                    Some(idx) => {\n                        if let Some(_window) = workspace.floating_windows().get(idx) {\n                            workspace.layer = WorkspaceLayer::Floating;\n                        }\n                    }\n                }\n            }\n            WindowManagerEvent::Show(_, window)\n            | WindowManagerEvent::Manage(window)\n            | WindowManagerEvent::Uncloak(_, window) => {\n                if matches!(event, WindowManagerEvent::Uncloak(_, _))\n                    && self.uncloack_to_ignore >= 1\n                {\n                    tracing::info!(\"ignoring uncloak after monocle move by mouse across monitors\");\n                    self.uncloack_to_ignore = self.uncloack_to_ignore.saturating_sub(1);\n                } else {\n                    let focused_monitor_idx = self.focused_monitor_idx();\n                    let focused_workspace_idx =\n                        self.focused_workspace_idx_for_monitor_idx(focused_monitor_idx)?;\n\n                    let mut needs_reconciliation = None;\n\n                    // There are some applications such as Firefox where, if they are focused when a\n                    // workspace switch takes place, it will fire an additional Show event, which will\n                    // result in them being associated with both the original workspace and the workspace\n                    // being switched to. This loop is to try to ensure that we don't end up with\n                    // duplicates across multiple workspaces, as it results in ghost layout tiles.\n                    let mut proceed = true;\n\n                    // Check for potential `alt-tab` event\n                    if matches!(\n                        event,\n                        WindowManagerEvent::Uncloak(_, _) | WindowManagerEvent::Show(_, _)\n                    ) {\n                        needs_reconciliation = self.needs_reconciliation(window)?;\n\n                        if let Some((m_idx, ws_idx)) = needs_reconciliation {\n                            self.perform_reconciliation(window, (m_idx, ws_idx))?;\n\n                            // Since there was a reconciliation after an `alt-tab`, that means this\n                            // window is already handled by komorebi so we shouldn't proceed with\n                            // adding it as a new window.\n                            proceed = false;\n                        }\n                    }\n\n                    if let Some((m_idx, w_idx)) = self.known_hwnds.get(&window.hwnd)\n                        && let Some(focused_workspace_idx) = self\n                            .monitors()\n                            .get(*m_idx)\n                            .map(|m| m.focused_workspace_idx())\n                        && *m_idx != self.focused_monitor_idx()\n                        && *w_idx != focused_workspace_idx\n                    {\n                        tracing::debug!(\n                            \"ignoring show event for window already associated with another workspace\"\n                        );\n\n                        window.hide();\n                        proceed = false;\n                    }\n\n                    // after enforce_workspace_rules() has run, check if window exists in ANY workspace\n                    // to prevent duplication when workspace rules move windows across workspaces\n                    if proceed {\n                        let window_already_managed = self\n                            .monitors()\n                            .iter()\n                            .flat_map(|m| m.workspaces())\n                            .any(|ws| ws.contains_window(window.hwnd));\n\n                        if window_already_managed {\n                            tracing::debug!(\n                                \"skipping window addition, already managed after workspace rule enforcement\"\n                            );\n\n                            proceed = false;\n                        }\n                    }\n\n                    if proceed {\n                        let behaviour = self.window_management_behaviour(\n                            focused_monitor_idx,\n                            focused_workspace_idx,\n                        );\n                        let workspace = self.focused_workspace_mut()?;\n                        let workspace_contains_window = workspace.contains_window(window.hwnd);\n                        let monocle_container = workspace.monocle_container.clone();\n\n                        if !workspace_contains_window && needs_reconciliation.is_none() {\n                            let floating_applications = FLOATING_APPLICATIONS.lock();\n                            let mut should_float = false;\n\n                            if !floating_applications.is_empty() {\n                                let regex_identifiers = REGEX_IDENTIFIERS.lock();\n\n                                if let (Ok(title), Ok(exe_name), Ok(class), Ok(path)) =\n                                    (window.title(), window.exe(), window.class(), window.path())\n                                {\n                                    should_float = should_act(\n                                        &title,\n                                        &exe_name,\n                                        &class,\n                                        &path,\n                                        &floating_applications,\n                                        &regex_identifiers,\n                                    )\n                                    .is_some();\n                                }\n                            }\n\n                            if behaviour.float_override\n                                || behaviour.floating_layer_override\n                                || (should_float && !matches!(event, WindowManagerEvent::Manage(_)))\n                            {\n                                let placement = if behaviour.floating_layer_override {\n                                    // Floating layer override placement\n                                    behaviour.floating_layer_placement\n                                } else if behaviour.float_override {\n                                    // Float override placement\n                                    behaviour.float_override_placement\n                                } else {\n                                    // Float rule placement\n                                    behaviour.float_rule_placement\n                                };\n                                // Center floating windows according to the proper placement if not\n                                // on a floating workspace\n                                let center_spawned_floats =\n                                    placement.should_center() && workspace.tile;\n                                workspace.floating_windows_mut().push_back(window);\n                                workspace.layer = WorkspaceLayer::Floating;\n                                if center_spawned_floats {\n                                    let mut floating_window = window;\n                                    floating_window.center(\n                                        &workspace.globals.work_area,\n                                        placement.should_resize(),\n                                    )?;\n                                }\n                                self.update_focused_workspace(false, false)?;\n                            } else {\n                                match behaviour.current_behaviour {\n                                    WindowContainerBehaviour::Create => {\n                                        workspace.new_container_for_window(window);\n                                        workspace.layer = WorkspaceLayer::Tiling;\n                                        self.update_focused_workspace(false, false)?;\n                                    }\n                                    WindowContainerBehaviour::Append => {\n                                        workspace\n                                            .focused_container_mut()\n                                            .ok_or_eyre(\"there is no focused container\")?\n                                            .add_window(window);\n                                        workspace.layer = WorkspaceLayer::Tiling;\n                                        self.update_focused_workspace(true, false)?;\n                                        stackbar_manager::send_notification();\n                                    }\n                                }\n                            }\n\n                            if (self.focused_workspace()?.containers().len() == 1\n                                && self.focused_workspace()?.floating_windows().is_empty())\n                                || (self.focused_workspace()?.containers().is_empty()\n                                    && self.focused_workspace()?.floating_windows().len() == 1)\n                            {\n                                // If after adding this window the workspace only contains 1 window, it\n                                // means it was previously empty and we focused the desktop to unfocus\n                                // any previous window from other workspace, so now we need to focus\n                                // this window again. This is needed because sometimes some windows\n                                // first send the `FocusChange` event and only the `Show` event after\n                                // and we will be focusing the desktop on the `FocusChange` event since\n                                // it is still empty.\n                                window.focus(self.mouse_follows_focus)?;\n                            }\n                        }\n\n                        if workspace_contains_window {\n                            let mut monocle_window_event = false;\n                            if let Some(ref monocle) = monocle_container\n                                && let Some(monocle_window) = monocle.focused_window()\n                                && monocle_window.hwnd == window.hwnd\n                            {\n                                monocle_window_event = true;\n                            }\n\n                            let workspace = self.focused_workspace()?;\n                            if !(monocle_window_event || workspace.layer != WorkspaceLayer::Tiling)\n                                && monocle_container.is_some()\n                            {\n                                window.hide();\n                            }\n                        }\n                    }\n                }\n            }\n            WindowManagerEvent::MoveResizeStart(_, window) => {\n                let monitor_idx = self.focused_monitor_idx();\n                let workspace_idx = self\n                    .focused_monitor()\n                    .ok_or_eyre(\"there is no monitor with this idx\")?\n                    .focused_workspace_idx();\n\n                WindowsApi::bring_window_to_top(window.hwnd)?;\n\n                let pending_move_op = Arc::make_mut(&mut self.pending_move_op);\n                *pending_move_op = Option::from((monitor_idx, workspace_idx, window.hwnd));\n            }\n            WindowManagerEvent::MoveResizeEnd(_, window) => {\n                // We need this because if the event ends on a different monitor,\n                // that monitor will already have been focused and updated in the state\n                let pending = *self.pending_move_op;\n                // Always consume the pending move op whenever this event is handled\n                let pending_move_op = Arc::make_mut(&mut self.pending_move_op);\n                *pending_move_op = None;\n\n                // If the window handles don't match then something went wrong and the pending move\n                // is not related to this current move, if so abort this operation.\n                if let Some((_, _, w_hwnd)) = pending\n                    && w_hwnd != window.hwnd\n                {\n                    color_eyre::eyre::bail!(\n                        \"window handles for move operation don't match: {} != {}\",\n                        w_hwnd,\n                        window.hwnd\n                    );\n                }\n\n                let target_monitor_idx = self\n                    .monitor_idx_from_current_pos()\n                    .ok_or_eyre(\"cannot get monitor idx from current position\")?;\n\n                let focused_monitor_idx = self.focused_monitor_idx();\n                let focused_workspace_idx = self.focused_workspace_idx().unwrap_or_default();\n                let window_management_behaviour =\n                    self.window_management_behaviour(focused_monitor_idx, focused_workspace_idx);\n\n                let workspace = self.focused_workspace_mut()?;\n                let focused_container_idx = workspace.focused_container_idx();\n                let new_position = WindowsApi::window_rect(window.hwnd)?;\n                let old_position = *workspace\n                    .latest_layout\n                    .get(focused_container_idx)\n                    // If the move was to another monitor with an empty workspace, the\n                    // workspace here will refer to that empty workspace, which won't\n                    // have any latest layout set. We fall back to a Default for Rect\n                    // which allows us to make a reasonable guess that the drag has taken\n                    // place across a monitor boundary to an empty workspace\n                    .unwrap_or(&Rect::default());\n\n                // This will be true if we have moved to another monitor\n                let mut moved_across_monitors = false;\n\n                if let Some((m_idx, _)) = self.known_hwnds.get(&window.hwnd)\n                    && *m_idx != target_monitor_idx\n                {\n                    moved_across_monitors = true;\n                }\n\n                if let Some((origin_monitor_idx, origin_workspace_idx, _)) = pending {\n                    // If we didn't move to another monitor with an empty workspace, it is\n                    // still possible that we moved to another monitor with a populated workspace\n                    if !moved_across_monitors {\n                        // So we'll check if the origin monitor index and the target monitor index\n                        // are different, if they are, we can set the override\n                        moved_across_monitors = origin_monitor_idx != target_monitor_idx;\n\n                        if moved_across_monitors {\n                            // Want to make sure that we exclude unmanaged windows from cross-monitor\n                            // moves with a mouse, otherwise the currently focused idx container will\n                            // be moved when we just want to drag an unmanaged window\n                            let origin_workspace = self\n                                .monitors()\n                                .get(origin_monitor_idx)\n                                .ok_or_eyre(\"cannot get monitor idx\")?\n                                .workspaces()\n                                .get(origin_workspace_idx)\n                                .ok_or_eyre(\"cannot get workspace idx\")?;\n\n                            let managed_window = origin_workspace.contains_window(window.hwnd);\n\n                            if !managed_window {\n                                moved_across_monitors = false;\n                            }\n                        }\n                    }\n                }\n\n                let workspace = self.focused_workspace_mut()?;\n                if (workspace.tile && workspace.contains_managed_window(window.hwnd))\n                    || moved_across_monitors\n                {\n                    let resize = Rect {\n                        left: new_position.left - old_position.left,\n                        top: new_position.top - old_position.top,\n                        right: new_position.right - old_position.right,\n                        bottom: new_position.bottom - old_position.bottom,\n                    };\n\n                    // If we have moved across the monitors, use that override, otherwise determine\n                    // if a move has taken place by ruling out a resize\n                    let right_bottom_constant = 0;\n\n                    let is_move = moved_across_monitors\n                        || resize.right.abs() == right_bottom_constant\n                            && resize.bottom.abs() == right_bottom_constant;\n\n                    if is_move {\n                        tracing::info!(\"moving with mouse\");\n\n                        if moved_across_monitors {\n                            if let Some((origin_monitor_idx, origin_workspace_idx, w_hwnd)) =\n                                pending\n                            {\n                                let target_workspace_idx = self\n                                    .monitors()\n                                    .get(target_monitor_idx)\n                                    .ok_or_eyre(\"there is no monitor at this idx\")?\n                                    .focused_workspace_idx();\n\n                                let target_container_idx = self\n                                    .monitors()\n                                    .get(target_monitor_idx)\n                                    .ok_or_eyre(\"there is no monitor at this idx\")?\n                                    .focused_workspace()\n                                    .ok_or_eyre(\"there is no focused workspace for this monitor\")?\n                                    .container_idx_from_current_point()\n                                    // Default to 0 in the case of an empty workspace\n                                    .unwrap_or(0);\n\n                                let origin = (origin_monitor_idx, origin_workspace_idx, w_hwnd);\n                                let target = (\n                                    target_monitor_idx,\n                                    target_workspace_idx,\n                                    target_container_idx,\n                                );\n                                self.transfer_window(origin, target)?;\n\n                                // We want to make sure both the origin and target monitors are updated,\n                                // so that we don't have ghost tiles until we force an interaction on\n                                // the origin monitor's focused workspace\n                                self.focus_monitor(origin_monitor_idx)?;\n                                let origin_monitor = self\n                                    .monitors_mut()\n                                    .get_mut(origin_monitor_idx)\n                                    .ok_or_eyre(\"there is no monitor at this idx\")?;\n                                origin_monitor.focus_workspace(origin_workspace_idx)?;\n                                self.update_focused_workspace(false, false)?;\n\n                                self.focus_monitor(target_monitor_idx)?;\n                                let target_monitor = self\n                                    .monitors_mut()\n                                    .get_mut(target_monitor_idx)\n                                    .ok_or_eyre(\"there is no monitor at this idx\")?;\n                                target_monitor.focus_workspace(target_workspace_idx)?;\n                                self.update_focused_workspace(false, false)?;\n\n                                // Make sure to give focus to the moved window again\n                                window.focus(self.mouse_follows_focus)?;\n                            }\n                        } else if window_management_behaviour.float_override {\n                            workspace.floating_windows_mut().push_back(window);\n                            self.update_focused_workspace(false, false)?;\n                        } else {\n                            match window_management_behaviour.current_behaviour {\n                                WindowContainerBehaviour::Create => {\n                                    match workspace.container_idx_from_current_point() {\n                                        Some(target_idx) => {\n                                            workspace\n                                                .swap_containers(focused_container_idx, target_idx);\n                                            self.update_focused_workspace(false, false)?;\n                                        }\n                                        None => {\n                                            self.update_focused_workspace(\n                                                self.mouse_follows_focus,\n                                                false,\n                                            )?;\n                                        }\n                                    }\n                                }\n                                WindowContainerBehaviour::Append => {\n                                    match workspace.container_idx_from_current_point() {\n                                        Some(target_idx) => {\n                                            workspace.move_window_to_container(target_idx)?;\n                                            self.update_focused_workspace(false, false)?;\n                                        }\n                                        None => {\n                                            self.update_focused_workspace(\n                                                self.mouse_follows_focus,\n                                                false,\n                                            )?;\n                                        }\n                                    }\n\n                                    stackbar_manager::send_notification();\n                                }\n                            }\n                        }\n                    } else {\n                        tracing::info!(\"resizing with mouse\");\n                        let mut ops = vec![];\n\n                        macro_rules! resize_op {\n                            ($coordinate:expr, $comparator:tt, $direction:expr) => {{\n                                let adjusted = $coordinate * 2;\n                                let sizing = if adjusted $comparator 0 {\n                                    Sizing::Decrease\n                                } else {\n                                    Sizing::Increase\n                                };\n\n                                ($direction, sizing, adjusted.abs())\n                            }};\n                        }\n\n                        if resize.left != 0 {\n                            ops.push(resize_op!(resize.left, >, OperationDirection::Left));\n                        }\n\n                        if resize.top != 0 {\n                            ops.push(resize_op!(resize.top, >, OperationDirection::Up));\n                        }\n\n                        // TODO: Determine if this is still needed\n                        let top_left_constant = BORDER_WIDTH.load(Ordering::SeqCst)\n                            + BORDER_OFFSET.load(Ordering::SeqCst);\n\n                        if resize.right != 0\n                            && (resize.left == top_left_constant || resize.left == 0)\n                        {\n                            ops.push(resize_op!(resize.right, <, OperationDirection::Right));\n                        }\n\n                        if resize.bottom != 0\n                            && (resize.top == top_left_constant || resize.top == 0)\n                        {\n                            ops.push(resize_op!(resize.bottom, <, OperationDirection::Down));\n                        }\n\n                        for (edge, sizing, delta) in ops {\n                            self.resize_window(edge, sizing, delta, true)?;\n                        }\n\n                        self.update_focused_workspace(false, false)?;\n                    }\n                }\n            }\n            WindowManagerEvent::MouseCapture(..)\n            | WindowManagerEvent::Cloak(..)\n            | WindowManagerEvent::TitleUpdate(..) => {}\n        };\n\n        // If we unmanaged a window, it shouldn't be immediately hidden behind managed windows\n        if let WindowManagerEvent::Unmanage(mut window) = event {\n            window.center(&self.focused_monitor_work_area()?, true)?;\n        }\n\n        // Update list of known_hwnds and their monitor/workspace index pair\n        self.update_known_hwnds();\n\n        notify_subscribers(\n            Notification {\n                event: NotificationEvent::WindowManager(event),\n                state: self.as_ref().into(),\n            },\n            initial_state.has_been_modified(self.as_ref()),\n        )?;\n\n        border_manager::send_notification(Some(event.hwnd()));\n        transparency_manager::send_notification();\n        stackbar_manager::send_notification();\n\n        // Too many spammy OBJECT_NAMECHANGE events from JetBrains IDEs\n        if !matches!(\n            event,\n            WindowManagerEvent::Show(WinEvent::ObjectNameChange, _)\n        ) {\n            tracing::info!(\"processed: {}\", event.window().to_string());\n        } else {\n            tracing::trace!(\"processed: {}\", event.window().to_string());\n        }\n\n        Ok(())\n    }\n\n    /// Checks if this window is from another unfocused workspace or is an unfocused window on a\n    /// stack container. If it is it will return the monitor/workspace index pair of this window so\n    /// that a reconciliation of that monitor/workspace can be done.\n    fn needs_reconciliation(&self, window: Window) -> color_eyre::Result<Option<(usize, usize)>> {\n        let focused_monitor_idx = self.focused_monitor_idx();\n        let focused_workspace_idx =\n            self.focused_workspace_idx_for_monitor_idx(focused_monitor_idx)?;\n\n        let focused_pair = (focused_monitor_idx, focused_workspace_idx);\n\n        let mut needs_reconciliation = None;\n\n        if let Some((m_idx, ws_idx)) = self.known_hwnds.get(&window.hwnd) {\n            if (*m_idx, *ws_idx) == focused_pair {\n                if let Some(target_workspace) = self\n                    .monitors()\n                    .get(*m_idx)\n                    .and_then(|m| m.workspaces().get(*ws_idx))\n                {\n                    if let Some(monocle_with_window) = target_workspace\n                        .monocle_container\n                        .as_ref()\n                        .and_then(|m| m.contains_window(window.hwnd).then_some(m))\n                    {\n                        if monocle_with_window.focused_window() != Some(&window) {\n                            tracing::debug!(\"Needs reconciliation within a monocled stack\");\n                            needs_reconciliation = Some((*m_idx, *ws_idx));\n                        }\n                    } else {\n                        let c_idx = target_workspace.container_idx_for_window(window.hwnd);\n\n                        if let Some(target_container) =\n                            c_idx.and_then(|c_idx| target_workspace.containers().get(c_idx))\n                            && target_container.focused_window() != Some(&window)\n                        {\n                            tracing::debug!(\n                                \"Needs reconciliation within a stack on the focused workspace\"\n                            );\n                            needs_reconciliation = Some((*m_idx, *ws_idx));\n                        }\n                    }\n                }\n            } else {\n                tracing::debug!(\"Needs reconciliation for a different monitor/workspace pair\");\n                needs_reconciliation = Some((*m_idx, *ws_idx));\n            }\n        }\n\n        Ok(needs_reconciliation)\n    }\n\n    /// When there was an `alt-tab` to a hidden window we need to perform a reconciliation, meaning\n    /// we need to update the focused monitor, workspace, container and window indices to the ones\n    /// corresponding to the window the user just alt-tabbed into.\n    fn perform_reconciliation(\n        &mut self,\n        window: Window,\n        reconciliation_pair: (usize, usize),\n    ) -> color_eyre::Result<()> {\n        let (m_idx, ws_idx) = reconciliation_pair;\n\n        tracing::debug!(\"performing reconciliation\");\n        self.focus_monitor(m_idx)?;\n        let mouse_follows_focus = self.mouse_follows_focus;\n        let offset = self.work_area_offset;\n\n        if let Some(monitor) = self.focused_monitor_mut() {\n            if ws_idx != monitor.focused_workspace_idx() {\n                let previous_idx = monitor.focused_workspace_idx();\n                monitor.last_focused_workspace = Option::from(previous_idx);\n                monitor.focus_workspace(ws_idx)?;\n            }\n            if let Some(workspace) = monitor.focused_workspace_mut() {\n                let mut layer = WorkspaceLayer::Tiling;\n                if let Some((monocle, idx)) = workspace\n                    .monocle_container\n                    .as_mut()\n                    .and_then(|m| m.idx_for_window(window.hwnd).map(|i| (m, i)))\n                {\n                    monocle.focus_window(idx);\n                } else if workspace\n                    .floating_windows()\n                    .iter()\n                    .any(|w| w.hwnd == window.hwnd)\n                {\n                    layer = WorkspaceLayer::Floating;\n                } else if workspace\n                    .maximized_window\n                    .is_none_or(|w| w.hwnd != window.hwnd)\n                {\n                    // If the window is the maximized window do nothing, else we\n                    // reintegrate the monocle if it exists and then focus the\n                    // container\n                    if workspace.monocle_container.is_some() {\n                        tracing::info!(\"disabling monocle\");\n                        for container in workspace.containers_mut() {\n                            container.restore();\n                        }\n                        for window in workspace.floating_windows_mut() {\n                            window.restore();\n                        }\n                        workspace.reintegrate_monocle_container()?;\n                    }\n                    workspace.focus_container_by_window(window.hwnd)?;\n                }\n                workspace.layer = layer;\n            }\n            monitor.load_focused_workspace(mouse_follows_focus)?;\n            monitor.update_focused_workspace(offset)?;\n        }\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "komorebi/src/process_movement.rs",
    "content": "use std::sync::Arc;\n\nuse parking_lot::Mutex;\nuse winput::Action;\nuse winput::message_loop;\nuse winput::message_loop::Event;\n\nuse crate::core::FocusFollowsMouseImplementation;\n\nuse crate::window_manager::WindowManager;\n\n#[tracing::instrument]\npub fn listen_for_movements(wm: Arc<Mutex<WindowManager>>) {\n    std::thread::spawn(move || {\n        let mut ignore_movement = false;\n\n        let receiver = message_loop::start().expect(\"could not start winput message loop\");\n\n        loop {\n            let focus_follows_mouse = wm.lock().focus_follows_mouse;\n            if matches!(\n                focus_follows_mouse,\n                Some(FocusFollowsMouseImplementation::Komorebi)\n            ) {\n                match receiver.next_event() {\n                    // Don't want to send any raise events while we are dragging or resizing\n                    Event::MouseButton { action, .. } => match action {\n                        Action::Press => ignore_movement = true,\n                        Action::Release => ignore_movement = false,\n                    },\n                    Event::MouseMoveRelative { .. } => {\n                        if !ignore_movement {\n                            match wm.lock().raise_window_at_cursor_pos() {\n                                Ok(()) => {}\n                                Err(error) => tracing::error!(\"{}\", error),\n                            }\n                        }\n                    }\n                    _ => {}\n                }\n            }\n        }\n    });\n}\n"
  },
  {
    "path": "komorebi/src/reaper.rs",
    "content": "#![deny(clippy::unwrap_used, clippy::expect_used)]\n\nuse crate::DATA_DIR;\nuse crate::HIDING_BEHAVIOUR;\nuse crate::HidingBehaviour;\nuse crate::NotificationEvent;\nuse crate::Window;\nuse crate::WindowManager;\nuse crate::WindowManagerEvent;\nuse crate::border_manager;\nuse crate::notify_subscribers;\nuse crate::winevent::WinEvent;\n\nuse crossbeam_channel::Receiver;\nuse crossbeam_channel::Sender;\nuse lazy_static::lazy_static;\nuse parking_lot::Mutex;\nuse std::collections::HashMap;\nuse std::fs::OpenOptions;\nuse std::sync::Arc;\nuse std::sync::OnceLock;\nuse std::time::Duration;\n\nlazy_static! {\n    pub static ref HWNDS_CACHE: Arc<Mutex<HashMap<isize, (usize, usize)>>> =\n        Arc::new(Mutex::new(HashMap::new()));\n}\n\npub struct ReaperNotification(pub HashMap<isize, (usize, usize)>);\n\nstatic CHANNEL: OnceLock<(Sender<ReaperNotification>, Receiver<ReaperNotification>)> =\n    OnceLock::new();\n\npub fn channel() -> &'static (Sender<ReaperNotification>, Receiver<ReaperNotification>) {\n    CHANNEL.get_or_init(|| crossbeam_channel::bounded(50))\n}\n\nfn event_tx() -> Sender<ReaperNotification> {\n    channel().0.clone()\n}\n\nfn event_rx() -> Receiver<ReaperNotification> {\n    channel().1.clone()\n}\n\npub fn send_notification(hwnds: HashMap<isize, (usize, usize)>) {\n    if event_tx().try_send(ReaperNotification(hwnds)).is_err() {\n        tracing::warn!(\"channel is full; dropping notification\")\n    }\n}\n\npub fn listen_for_notifications(\n    wm: Arc<Mutex<WindowManager>>,\n    known_hwnds: HashMap<isize, (usize, usize)>,\n) {\n    watch_for_orphans(known_hwnds);\n\n    std::thread::spawn(move || {\n        loop {\n            match handle_notifications(wm.clone()) {\n                Ok(()) => {\n                    tracing::warn!(\"restarting finished thread\");\n                }\n                Err(error) => {\n                    tracing::warn!(\"restarting failed thread: {}\", error);\n                }\n            }\n        }\n    });\n}\n\nfn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result<()> {\n    tracing::info!(\"listening\");\n\n    let receiver = event_rx();\n\n    for notification in receiver {\n        let orphan_hwnds = notification.0;\n        let mut wm = wm.lock();\n\n        let mut update_borders = false;\n\n        for (hwnd, (m_idx, w_idx)) in orphan_hwnds.iter() {\n            if let Some(monitor) = wm.monitors_mut().get_mut(*m_idx) {\n                let focused_workspace_idx = monitor.focused_workspace_idx();\n\n                if let Some(workspace) = monitor.workspaces_mut().get_mut(*w_idx) {\n                    // Remove orphan window\n                    if let Err(error) = workspace.remove_window(*hwnd) {\n                        tracing::warn!(\n                            \"error reaping orphan window ({}) on monitor: {}, workspace: {}. Error: {}\",\n                            hwnd,\n                            m_idx,\n                            w_idx,\n                            error,\n                        );\n                    }\n\n                    if focused_workspace_idx == *w_idx {\n                        // If this is not a focused workspace there is no need to update the\n                        // workspace or the borders. That will already be done when the user\n                        // changes to this workspace.\n                        workspace.update()?;\n                        update_borders = true;\n                    }\n                    tracing::info!(\n                        \"reaped orphan window ({}) on monitor: {}, workspace: {}\",\n                        hwnd,\n                        m_idx,\n                        w_idx,\n                    );\n                }\n            }\n\n            wm.known_hwnds.remove(hwnd);\n\n            let window = Window::from(*hwnd);\n            notify_subscribers(\n                crate::Notification {\n                    event: NotificationEvent::WindowManager(WindowManagerEvent::Destroy(\n                        WinEvent::ObjectDestroy,\n                        window,\n                    )),\n                    state: wm.as_ref().into(),\n                },\n                true,\n            )?;\n        }\n\n        if update_borders {\n            border_manager::send_notification(None);\n        }\n\n        // Save to file\n        let hwnd_json = DATA_DIR.join(\"komorebi.hwnd.json\");\n        let file = OpenOptions::new()\n            .write(true)\n            .truncate(true)\n            .create(true)\n            .open(hwnd_json)?;\n\n        serde_json::to_writer_pretty(&file, &wm.known_hwnds.keys().collect::<Vec<_>>())?;\n    }\n\n    Ok(())\n}\n\nfn watch_for_orphans(known_hwnds: HashMap<isize, (usize, usize)>) {\n    // Cache current hwnds\n    {\n        let mut cache = HWNDS_CACHE.lock();\n        *cache = known_hwnds;\n    }\n\n    std::thread::spawn(move || {\n        loop {\n            match find_orphans() {\n                Ok(()) => {\n                    tracing::warn!(\"restarting finished thread\");\n                }\n                Err(error) => {\n                    if cfg!(debug_assertions) {\n                        tracing::error!(\"restarting failed thread: {:?}\", error)\n                    } else {\n                        tracing::error!(\"restarting failed thread: {}\", error)\n                    }\n                }\n            }\n        }\n    });\n}\n\nfn find_orphans() -> color_eyre::Result<()> {\n    tracing::info!(\"watching\");\n\n    loop {\n        std::thread::sleep(Duration::from_millis(20));\n        let hiding_behaviour = *HIDING_BEHAVIOUR.lock();\n\n        let mut cache = HWNDS_CACHE.lock();\n        let mut orphan_hwnds = HashMap::new();\n\n        for (hwnd, (m_idx, w_idx)) in cache.iter() {\n            let window = Window::from(*hwnd);\n\n            #[allow(deprecated)]\n            if !window.is_window()\n                || (\n                    // This one is a hack because WINWORD.EXE is an absolute trainwreck of an app\n                    // when multiple docs are open, it keeps open an invisible window, with WS_EX_LAYERED\n                    // (A STYLE THAT THE REGULAR WINDOWS NEED IN ORDER TO BE MANAGED!) when one of the\n                    // docs is closed\n                    //\n                    // I hate every single person who worked on Microsoft Office 365, especially Word\n                    !window.is_visible()\n                    // We cannot execute this lovely hack if the user is using HidingBehaviour::Hide because\n                    // it will result in legitimate hidden, non-visible windows being yeeted from the state\n                    && !matches!(hiding_behaviour, HidingBehaviour::Hide)\n                )\n            {\n                orphan_hwnds.insert(window.hwnd, (*m_idx, *w_idx));\n            }\n        }\n\n        if !orphan_hwnds.is_empty() {\n            // Update reaper cache\n            cache.retain(|h, _| !orphan_hwnds.contains_key(h));\n\n            // Send handles to remove\n            event_tx().send(ReaperNotification(orphan_hwnds))?;\n        }\n    }\n}\n"
  },
  {
    "path": "komorebi/src/ring.rs",
    "content": "use std::collections::VecDeque;\n\nuse serde::Deserialize;\nuse serde::Serialize;\n\n#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\npub struct Ring<T> {\n    elements: VecDeque<T>,\n    focused: usize,\n}\n\nimpl<T> Default for Ring<T> {\n    fn default() -> Self {\n        Self {\n            elements: VecDeque::default(),\n            focused: 0,\n        }\n    }\n}\n\nimpl<T> Ring<T> {\n    pub const fn elements(&self) -> &VecDeque<T> {\n        &self.elements\n    }\n\n    pub fn elements_mut(&mut self) -> &mut VecDeque<T> {\n        &mut self.elements\n    }\n\n    pub fn focus(&mut self, idx: usize) {\n        self.focused = idx;\n    }\n\n    pub fn focused(&self) -> Option<&T> {\n        self.elements.get(self.focused)\n    }\n\n    pub const fn focused_idx(&self) -> usize {\n        self.focused\n    }\n\n    pub fn focused_mut(&mut self) -> Option<&mut T> {\n        self.elements.get_mut(self.focused)\n    }\n}\n\nmacro_rules! impl_ring_elements {\n    ($name:ty, $element:ident) => {\n        paste::paste! {\n            impl $name {\n                pub const fn [<$element:lower s>](&self) -> &VecDeque<$element> {\n                    self.[<$element:lower s>].elements()\n                }\n\n                pub fn [<$element:lower s_mut>](&mut self) -> &mut VecDeque<$element> {\n                    self.[<$element:lower s>].elements_mut()\n                }\n\n                #[allow(dead_code)]\n                pub fn [<focused_ $element:lower>](&self) -> Option<&$element> {\n                    self.[<$element:lower s>].focused()\n                }\n\n                pub const fn [<focused_ $element:lower _idx>](&self) -> usize {\n                    self.[<$element:lower s>].focused_idx()\n                }\n\n                pub fn [<focused_ $element:lower _mut>](&mut self) -> Option<&mut $element> {\n                    self.[<$element:lower s>].focused_mut()\n                }\n            }\n        }\n    };\n    // This allows passing a different name to be used for the functions. For instance, the\n    // `floating_windows` ring calls this as:\n    // ```rust\n    // impl_ring_elements!(Workspace, Window, \"floating_window\");\n    // ```\n    // Which allows using the `Window` element but name the functions as `floating_window`\n    ($name:ty, $element:ident, $el_name:literal) => {\n        paste::paste! {\n            impl $name {\n                pub const fn [<$el_name:lower s>](&self) -> &VecDeque<$element> {\n                    self.[<$el_name:lower s>].elements()\n                }\n\n                pub fn [<$el_name:lower s_mut>](&mut self) -> &mut VecDeque<$element> {\n                    self.[<$el_name:lower s>].elements_mut()\n                }\n\n                #[allow(dead_code)]\n                pub fn [<focused_ $el_name:lower>](&self) -> Option<&$element> {\n                    self.[<$el_name:lower s>].focused()\n                }\n\n                pub const fn [<focused_ $el_name:lower _idx>](&self) -> usize {\n                    self.[<$el_name:lower s>].focused_idx()\n                }\n\n                pub fn [<focused_ $el_name:lower _mut>](&mut self) -> Option<&mut $element> {\n                    self.[<$el_name:lower s>].focused_mut()\n                }\n            }\n        }\n    };\n}\n"
  },
  {
    "path": "komorebi/src/set_window_position.rs",
    "content": "use bitflags::bitflags;\nuse windows::Win32::UI::WindowsAndMessaging::SWP_ASYNCWINDOWPOS;\nuse windows::Win32::UI::WindowsAndMessaging::SWP_DEFERERASE;\nuse windows::Win32::UI::WindowsAndMessaging::SWP_DRAWFRAME;\nuse windows::Win32::UI::WindowsAndMessaging::SWP_FRAMECHANGED;\nuse windows::Win32::UI::WindowsAndMessaging::SWP_HIDEWINDOW;\nuse windows::Win32::UI::WindowsAndMessaging::SWP_NOACTIVATE;\nuse windows::Win32::UI::WindowsAndMessaging::SWP_NOCOPYBITS;\nuse windows::Win32::UI::WindowsAndMessaging::SWP_NOMOVE;\nuse windows::Win32::UI::WindowsAndMessaging::SWP_NOOWNERZORDER;\nuse windows::Win32::UI::WindowsAndMessaging::SWP_NOREDRAW;\nuse windows::Win32::UI::WindowsAndMessaging::SWP_NOREPOSITION;\nuse windows::Win32::UI::WindowsAndMessaging::SWP_NOSENDCHANGING;\nuse windows::Win32::UI::WindowsAndMessaging::SWP_NOSIZE;\nuse windows::Win32::UI::WindowsAndMessaging::SWP_NOZORDER;\nuse windows::Win32::UI::WindowsAndMessaging::SWP_SHOWWINDOW;\n\nbitflags! {\n    #[derive(Default)]\n    pub struct SetWindowPosition: u32 {\n        const ASYNC_WINDOW_POS = SWP_ASYNCWINDOWPOS.0;\n        const DEFER_ERASE = SWP_DEFERERASE.0;\n        const DRAW_FRAME = SWP_DRAWFRAME.0;\n        const FRAME_CHANGED = SWP_FRAMECHANGED.0;\n        const HIDE_WINDOW = SWP_HIDEWINDOW.0;\n        const NO_ACTIVATE = SWP_NOACTIVATE.0;\n        const NO_COPY_BITS = SWP_NOCOPYBITS.0;\n        const NO_MOVE = SWP_NOMOVE.0;\n        const NO_OWNER_Z_ORDER = SWP_NOOWNERZORDER.0;\n        const NO_REDRAW = SWP_NOREDRAW.0;\n        const NO_REPOSITION = SWP_NOREPOSITION.0;\n        const NO_SEND_CHANGING = SWP_NOSENDCHANGING.0;\n        const NO_SIZE = SWP_NOSIZE.0;\n        const NO_Z_ORDER = SWP_NOZORDER.0;\n        const SHOW_WINDOW = SWP_SHOWWINDOW.0;\n    }\n}\n"
  },
  {
    "path": "komorebi/src/splash.rs",
    "content": "use crate::DATA_DIR;\nuse crate::License;\nuse crate::PUBLIC_KEY;\nuse base64::Engine;\nuse base64::engine::general_purpose;\nuse chrono::Duration;\nuse chrono::TimeZone;\nuse chrono::Utc;\nuse color_eyre::eyre;\nuse color_eyre::eyre::OptionExt;\nuse ed25519_dalek::Verifier;\nuse ed25519_dalek::VerifyingKey;\nuse std::path::PathBuf;\nuse std::process::Command;\n\npub fn mdm_enrollment() -> eyre::Result<(bool, Option<String>)> {\n    let mut command = Command::new(\"dsregcmd\");\n    command.args([\"/status\"]);\n    let stdout = command.output()?.stdout;\n    let output = std::str::from_utf8(&stdout)?;\n    if !output.contains(\"WorkspaceTenantName\") {\n        return Ok((false, None));\n    }\n\n    let mut tenant = None;\n\n    for line in output.lines() {\n        if line.contains(\"WorkspaceTenantName\") {\n            let line = line.trim().to_string();\n            tenant = Some(\n                line.trim_start_matches(\"WorkspaceTenantName : \")\n                    .to_string(),\n            )\n        }\n    }\n\n    Ok((true, tenant))\n}\n\nfn is_valid_payload(raw: &str, fresh: bool) -> eyre::Result<bool> {\n    let mut validation_successful = false;\n\n    let payload = serde_json::from_str::<License>(raw)?;\n\n    let signature = ed25519_dalek::Signature::from_slice(\n        general_purpose::STANDARD\n            .decode(&payload.signature)?\n            .as_slice(),\n    )?;\n\n    let mut value: serde_json::Value = serde_json::from_str(raw)?;\n    if let serde_json::Value::Object(ref mut map) = value {\n        map.remove(\"signature\");\n    }\n\n    let message_to_verify = serde_json::to_string(&value)?;\n    let verifying_key = VerifyingKey::from_bytes(&PUBLIC_KEY)?;\n\n    if verifying_key\n        .verify(message_to_verify.as_bytes(), &signature)\n        .is_ok()\n    {\n        if fresh {\n            let timestamp = Utc\n                .timestamp_opt(payload.timestamp, 0)\n                .single()\n                .ok_or_eyre(\"invalid timestamp\")?;\n\n            let valid_duration = Utc::now() - Duration::minutes(5);\n\n            if timestamp <= valid_duration {\n                tracing::debug!(\"individual commercial use license verification payload was stale\");\n                return Ok(true);\n            }\n        }\n\n        if payload.has_valid_subscription\n            && let Some(current_end_period) = payload.current_end_period\n        {\n            let subscription_valid_until = Utc\n                .timestamp_opt(current_end_period, 0)\n                .single()\n                .ok_or_eyre(\"invalid timestamp\")?;\n\n            if Utc::now() <= subscription_valid_until {\n                tracing::debug!(\n                    \"individual commercial use license verification - subscription valid until: {subscription_valid_until}\",\n                );\n\n                validation_successful = true;\n            }\n        }\n    }\n\n    Ok(validation_successful)\n}\n\npub enum ValidationFeedback {\n    Successful(PathBuf),\n    Unsuccessful(String),\n    NoEmail,\n    NoConnectivity,\n}\n\nimpl From<ValidationFeedback> for bool {\n    fn from(value: ValidationFeedback) -> Self {\n        match value {\n            ValidationFeedback::Successful(_) => false,\n\n            ValidationFeedback::Unsuccessful(_)\n            | ValidationFeedback::NoEmail\n            | ValidationFeedback::NoConnectivity => true,\n        }\n    }\n}\n\npub fn should() -> eyre::Result<ValidationFeedback> {\n    let icul_validation = DATA_DIR.join(\"icul.validation\");\n    if icul_validation.exists() {\n        tracing::debug!(\"found local individual commercial use license validation payload\");\n        let raw_payload = std::fs::read_to_string(&icul_validation)?;\n        if is_valid_payload(&raw_payload, false)? {\n            return Ok(ValidationFeedback::Successful(icul_validation));\n        } else {\n            std::fs::remove_file(&icul_validation)?;\n        }\n    }\n\n    let icul = DATA_DIR.join(\"icul\");\n    if !icul.exists() {\n        return Ok(ValidationFeedback::NoEmail);\n    }\n\n    let email = std::fs::read_to_string(icul)?;\n    tracing::debug!(\"found individual commercial use license email: {}\", email);\n\n    let client = reqwest::blocking::Client::new();\n    let response = match client\n        .get(\"https://kw-icul.lgug2z.com\")\n        .query(&[(\"email\", email.trim())])\n        .send()\n    {\n        Ok(response) => response,\n        Err(error) => {\n            tracing::error!(\"{error}\");\n            return Ok(ValidationFeedback::NoConnectivity);\n        }\n    };\n\n    let raw_payload = response.text()?;\n    if is_valid_payload(&raw_payload, true)? {\n        std::fs::write(&icul_validation, &raw_payload)?;\n        Ok(ValidationFeedback::Successful(icul_validation))\n    } else {\n        Ok(ValidationFeedback::Unsuccessful(raw_payload))\n    }\n}\n"
  },
  {
    "path": "komorebi/src/stackbar_manager/mod.rs",
    "content": "mod stackbar;\n\nuse crate::DEFAULT_CONTAINER_PADDING;\nuse crate::WindowManager;\nuse crate::WindowsApi;\nuse crate::container::Container;\nuse crate::core::StackbarLabel;\nuse crate::core::StackbarMode;\nuse crate::stackbar_manager::stackbar::Stackbar;\nuse crossbeam_channel::Receiver;\nuse crossbeam_channel::Sender;\nuse crossbeam_utils::atomic::AtomicCell;\nuse crossbeam_utils::atomic::AtomicConsume;\nuse lazy_static::lazy_static;\nuse parking_lot::Mutex;\nuse std::collections::HashMap;\nuse std::collections::hash_map::Entry;\nuse std::sync::Arc;\nuse std::sync::OnceLock;\nuse std::sync::atomic::AtomicBool;\nuse std::sync::atomic::AtomicI32;\nuse std::sync::atomic::AtomicU32;\nuse std::sync::atomic::Ordering;\n\npub static STACKBAR_FONT_SIZE: AtomicI32 = AtomicI32::new(0); // 0 will produce the system default\npub static STACKBAR_FOCUSED_TEXT_COLOUR: AtomicU32 = AtomicU32::new(16777215); // white\npub static STACKBAR_UNFOCUSED_TEXT_COLOUR: AtomicU32 = AtomicU32::new(11776947); // gray text\npub static STACKBAR_TAB_BACKGROUND_COLOUR: AtomicU32 = AtomicU32::new(3355443); // gray\npub static STACKBAR_TAB_HEIGHT: AtomicI32 = AtomicI32::new(40);\npub static STACKBAR_TAB_WIDTH: AtomicI32 = AtomicI32::new(200);\npub static STACKBAR_LABEL: AtomicCell<StackbarLabel> = AtomicCell::new(StackbarLabel::Title);\npub static STACKBAR_MODE: AtomicCell<StackbarMode> = AtomicCell::new(StackbarMode::Never);\n\npub static STACKBAR_TEMPORARILY_DISABLED: AtomicBool = AtomicBool::new(false);\n\nlazy_static! {\n    pub static ref STACKBAR_STATE: Mutex<HashMap<String, Stackbar>> = Mutex::new(HashMap::new());\n    pub static ref STACKBAR_FONT_FAMILY: Mutex<Option<String>> = Mutex::new(None);\n    static ref STACKBARS_MONITORS: Mutex<HashMap<String, usize>> = Mutex::new(HashMap::new());\n    static ref STACKBARS_CONTAINERS: Mutex<HashMap<isize, Container>> = Mutex::new(HashMap::new());\n}\n\npub struct Notification;\n\nstatic CHANNEL: OnceLock<(Sender<Notification>, Receiver<Notification>)> = OnceLock::new();\n\npub fn channel() -> &'static (Sender<Notification>, Receiver<Notification>) {\n    CHANNEL.get_or_init(|| crossbeam_channel::bounded(20))\n}\n\nfn event_tx() -> Sender<Notification> {\n    channel().0.clone()\n}\n\nfn event_rx() -> Receiver<Notification> {\n    channel().1.clone()\n}\n\npub fn send_notification() {\n    if event_tx().try_send(Notification).is_err() {\n        tracing::warn!(\"channel is full; dropping notification\")\n    }\n}\n\npub fn should_have_stackbar(window_count: usize) -> bool {\n    match STACKBAR_MODE.load() {\n        StackbarMode::Always => true,\n        StackbarMode::OnStack => window_count > 1,\n        StackbarMode::Never => false,\n    }\n}\n\npub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) {\n    std::thread::spawn(move || {\n        loop {\n            match handle_notifications(wm.clone()) {\n                Ok(()) => {\n                    tracing::warn!(\"restarting finished thread\");\n                }\n                Err(error) => {\n                    tracing::warn!(\"restarting failed thread: {}\", error);\n                }\n            }\n        }\n    });\n}\n\npub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result<()> {\n    tracing::info!(\"listening\");\n\n    let receiver = event_rx();\n\n    'receiver: for _ in receiver {\n        let mut stackbars = STACKBAR_STATE.lock();\n        let mut stackbars_monitors = STACKBARS_MONITORS.lock();\n\n        // Check the wm state every time we receive a notification\n        let mut state = wm.lock();\n\n        // If stackbars are disabled\n        if matches!(STACKBAR_MODE.load(), StackbarMode::Never)\n            || STACKBAR_TEMPORARILY_DISABLED.load(Ordering::SeqCst)\n        {\n            for (_, stackbar) in stackbars.iter() {\n                stackbar.destroy()?;\n            }\n\n            stackbars.clear();\n            continue 'receiver;\n        }\n\n        for (monitor_idx, m) in state.monitors_mut().iter_mut().enumerate() {\n            // Only operate on the focused workspace of each monitor\n            if let Some(ws) = m.focused_workspace_mut() {\n                // Workspaces with tiling disabled don't have stackbars\n                if !ws.tile {\n                    let mut to_remove = vec![];\n                    for (id, border) in stackbars.iter() {\n                        if stackbars_monitors.get(id).copied().unwrap_or_default() == monitor_idx {\n                            border.destroy()?;\n                            to_remove.push(id.clone());\n                        }\n                    }\n\n                    for id in &to_remove {\n                        stackbars.remove(id);\n                    }\n\n                    continue 'receiver;\n                }\n\n                let is_maximized =\n                    WindowsApi::is_zoomed(WindowsApi::foreground_window().unwrap_or_default());\n\n                // Handle the monocle container separately\n                if ws.monocle_container.is_some() || is_maximized {\n                    // Destroy any stackbars associated with the focused workspace\n                    let mut to_remove = vec![];\n                    for (id, stackbar) in stackbars.iter() {\n                        if stackbars_monitors.get(id).copied().unwrap_or_default() == monitor_idx {\n                            stackbar.destroy()?;\n                            to_remove.push(id.clone());\n                        }\n                    }\n\n                    for id in &to_remove {\n                        stackbars.remove(id);\n                    }\n\n                    continue 'receiver;\n                }\n\n                // Destroy any stackbars not associated with the focused workspace\n                let container_ids = ws\n                    .containers()\n                    .iter()\n                    .map(|c| c.id.clone())\n                    .collect::<Vec<_>>();\n\n                let mut to_remove = vec![];\n                for (id, stackbar) in stackbars.iter() {\n                    if stackbars_monitors.get(id).copied().unwrap_or_default() == monitor_idx\n                        && !container_ids.contains(id)\n                    {\n                        stackbar.destroy()?;\n                        to_remove.push(id.clone());\n                    }\n                }\n\n                for id in &to_remove {\n                    stackbars.remove(id);\n                }\n\n                let container_padding = ws\n                    .container_padding\n                    .unwrap_or_else(|| DEFAULT_CONTAINER_PADDING.load_consume());\n\n                'containers: for container in ws.containers_mut() {\n                    let should_add_stackbar = match STACKBAR_MODE.load() {\n                        StackbarMode::Always => true,\n                        StackbarMode::OnStack => container.windows().len() > 1,\n                        StackbarMode::Never => false,\n                    };\n\n                    if !should_add_stackbar {\n                        if let Some(stackbar) = stackbars.get(&container.id) {\n                            stackbar.destroy()?\n                        }\n\n                        stackbars.remove(&container.id);\n                        stackbars_monitors.remove(&container.id);\n                        continue 'containers;\n                    }\n\n                    // Get the stackbar entry for this container from the map or create one\n                    let stackbar = match stackbars.entry(container.id.clone()) {\n                        Entry::Occupied(entry) => entry.into_mut(),\n                        Entry::Vacant(entry) => {\n                            if let Ok(stackbar) = Stackbar::create(&container.id) {\n                                entry.insert(stackbar)\n                            } else {\n                                continue 'receiver;\n                            }\n                        }\n                    };\n\n                    stackbars_monitors.insert(container.id.clone(), monitor_idx);\n\n                    let rect = WindowsApi::window_rect(\n                        container.focused_window().copied().unwrap_or_default().hwnd,\n                    )?;\n\n                    stackbar.update(container_padding, container, &rect)?;\n                }\n            }\n        }\n    }\n\n    Ok(())\n}\n"
  },
  {
    "path": "komorebi/src/stackbar_manager/stackbar.rs",
    "content": "use crate::DEFAULT_CONTAINER_PADDING;\nuse crate::WINDOWS_11;\nuse crate::WindowsApi;\nuse crate::border_manager::BORDER_OFFSET;\nuse crate::border_manager::BORDER_WIDTH;\nuse crate::border_manager::STYLE;\nuse crate::container::Container;\nuse crate::core::BorderStyle;\nuse crate::core::Rect;\nuse crate::core::StackbarLabel;\nuse crate::stackbar_manager::STACKBAR_FOCUSED_TEXT_COLOUR;\nuse crate::stackbar_manager::STACKBAR_FONT_FAMILY;\nuse crate::stackbar_manager::STACKBAR_FONT_SIZE;\nuse crate::stackbar_manager::STACKBAR_LABEL;\nuse crate::stackbar_manager::STACKBAR_TAB_BACKGROUND_COLOUR;\nuse crate::stackbar_manager::STACKBAR_TAB_HEIGHT;\nuse crate::stackbar_manager::STACKBAR_TAB_WIDTH;\nuse crate::stackbar_manager::STACKBAR_UNFOCUSED_TEXT_COLOUR;\nuse crate::stackbar_manager::STACKBARS_CONTAINERS;\nuse crate::windows_api;\nuse crossbeam_utils::atomic::AtomicConsume;\nuse std::os::windows::ffi::OsStrExt;\nuse std::sync::atomic::Ordering;\nuse std::sync::mpsc;\nuse std::time::Duration;\nuse windows::Win32::Foundation::COLORREF;\nuse windows::Win32::Foundation::HINSTANCE;\nuse windows::Win32::Foundation::HWND;\nuse windows::Win32::Foundation::LPARAM;\nuse windows::Win32::Foundation::LRESULT;\nuse windows::Win32::Foundation::WPARAM;\nuse windows::Win32::Graphics::Gdi::CreateFontIndirectW;\nuse windows::Win32::Graphics::Gdi::CreatePen;\nuse windows::Win32::Graphics::Gdi::CreateSolidBrush;\nuse windows::Win32::Graphics::Gdi::DT_CENTER;\nuse windows::Win32::Graphics::Gdi::DT_END_ELLIPSIS;\nuse windows::Win32::Graphics::Gdi::DT_SINGLELINE;\nuse windows::Win32::Graphics::Gdi::DT_VCENTER;\nuse windows::Win32::Graphics::Gdi::DeleteObject;\nuse windows::Win32::Graphics::Gdi::DrawTextW;\nuse windows::Win32::Graphics::Gdi::FONT_QUALITY;\nuse windows::Win32::Graphics::Gdi::FW_BOLD;\nuse windows::Win32::Graphics::Gdi::GetDC;\nuse windows::Win32::Graphics::Gdi::GetDeviceCaps;\nuse windows::Win32::Graphics::Gdi::LOGFONTW;\nuse windows::Win32::Graphics::Gdi::LOGPIXELSY;\nuse windows::Win32::Graphics::Gdi::PROOF_QUALITY;\nuse windows::Win32::Graphics::Gdi::PS_SOLID;\nuse windows::Win32::Graphics::Gdi::Rectangle;\nuse windows::Win32::Graphics::Gdi::ReleaseDC;\nuse windows::Win32::Graphics::Gdi::RoundRect;\nuse windows::Win32::Graphics::Gdi::SelectObject;\nuse windows::Win32::Graphics::Gdi::SetBkColor;\nuse windows::Win32::Graphics::Gdi::SetTextColor;\nuse windows::Win32::System::WindowsProgramming::MulDiv;\nuse windows::Win32::UI::WindowsAndMessaging::CS_HREDRAW;\nuse windows::Win32::UI::WindowsAndMessaging::CS_VREDRAW;\nuse windows::Win32::UI::WindowsAndMessaging::CreateWindowExW;\nuse windows::Win32::UI::WindowsAndMessaging::DefWindowProcW;\nuse windows::Win32::UI::WindowsAndMessaging::DispatchMessageW;\nuse windows::Win32::UI::WindowsAndMessaging::GetMessageW;\nuse windows::Win32::UI::WindowsAndMessaging::IDC_ARROW;\nuse windows::Win32::UI::WindowsAndMessaging::LWA_COLORKEY;\nuse windows::Win32::UI::WindowsAndMessaging::LoadCursorW;\nuse windows::Win32::UI::WindowsAndMessaging::MSG;\nuse windows::Win32::UI::WindowsAndMessaging::PostQuitMessage;\nuse windows::Win32::UI::WindowsAndMessaging::SetCursor;\nuse windows::Win32::UI::WindowsAndMessaging::SetLayeredWindowAttributes;\nuse windows::Win32::UI::WindowsAndMessaging::TranslateMessage;\nuse windows::Win32::UI::WindowsAndMessaging::WM_DESTROY;\nuse windows::Win32::UI::WindowsAndMessaging::WM_LBUTTONDOWN;\nuse windows::Win32::UI::WindowsAndMessaging::WM_SETCURSOR;\nuse windows::Win32::UI::WindowsAndMessaging::WNDCLASSW;\nuse windows::Win32::UI::WindowsAndMessaging::WS_EX_LAYERED;\nuse windows::Win32::UI::WindowsAndMessaging::WS_EX_TOOLWINDOW;\nuse windows::Win32::UI::WindowsAndMessaging::WS_POPUP;\nuse windows::Win32::UI::WindowsAndMessaging::WS_VISIBLE;\nuse windows::core::PCWSTR;\n\n#[derive(Debug)]\npub struct Stackbar {\n    pub hwnd: isize,\n}\n\nimpl From<isize> for Stackbar {\n    fn from(value: isize) -> Self {\n        Self { hwnd: value }\n    }\n}\n\nimpl Stackbar {\n    pub const fn hwnd(&self) -> HWND {\n        HWND(windows_api::as_ptr!(self.hwnd))\n    }\n\n    pub fn create(id: &str) -> color_eyre::Result<Self> {\n        let name: Vec<u16> = format!(\"komostackbar-{id}\\0\").encode_utf16().collect();\n        let class_name = PCWSTR(name.as_ptr());\n\n        let h_module = WindowsApi::module_handle_w()?;\n\n        let window_class = WNDCLASSW {\n            style: CS_HREDRAW | CS_VREDRAW,\n            lpfnWndProc: Some(Self::callback),\n            hInstance: h_module.into(),\n            lpszClassName: class_name,\n            hbrBackground: WindowsApi::create_solid_brush(0),\n            ..Default::default()\n        };\n\n        let _ = WindowsApi::register_class_w(&window_class);\n\n        let (hwnd_sender, hwnd_receiver) = mpsc::channel();\n\n        let name_cl = name.clone();\n        let instance = h_module.0 as isize;\n        std::thread::spawn(move || -> color_eyre::Result<()> {\n            unsafe {\n                let hwnd = CreateWindowExW(\n                    WS_EX_TOOLWINDOW | WS_EX_LAYERED,\n                    PCWSTR(name_cl.as_ptr()),\n                    PCWSTR(name_cl.as_ptr()),\n                    WS_POPUP | WS_VISIBLE,\n                    0,\n                    0,\n                    0,\n                    0,\n                    None,\n                    None,\n                    Option::from(HINSTANCE(windows_api::as_ptr!(instance))),\n                    None,\n                )?;\n\n                SetLayeredWindowAttributes(hwnd, COLORREF(0), 0, LWA_COLORKEY)?;\n                hwnd_sender.send(hwnd.0 as isize)?;\n\n                let mut msg: MSG = MSG::default();\n\n                loop {\n                    if !GetMessageW(&mut msg, None, 0, 0).as_bool() {\n                        tracing::debug!(\"stackbar window event processing thread shutdown\");\n                        break;\n                    };\n                    // TODO: error handling\n                    let _ = TranslateMessage(&msg);\n                    DispatchMessageW(&msg);\n\n                    std::thread::sleep(Duration::from_millis(10))\n                }\n            }\n\n            Ok(())\n        });\n\n        Ok(Self {\n            hwnd: hwnd_receiver.recv()?,\n        })\n    }\n\n    pub fn destroy(&self) -> color_eyre::Result<()> {\n        WindowsApi::close_window(self.hwnd)\n    }\n\n    pub fn update(\n        &self,\n        container_padding: i32,\n        container: &mut Container,\n        layout: &Rect,\n    ) -> color_eyre::Result<()> {\n        let width = STACKBAR_TAB_WIDTH.load_consume();\n        let height = STACKBAR_TAB_HEIGHT.load_consume();\n        let gap = DEFAULT_CONTAINER_PADDING.load_consume();\n        let background = STACKBAR_TAB_BACKGROUND_COLOUR.load_consume();\n        let focused_text_colour = STACKBAR_FOCUSED_TEXT_COLOUR.load_consume();\n        let unfocused_text_colour = STACKBAR_UNFOCUSED_TEXT_COLOUR.load_consume();\n\n        let mut stackbars_containers = STACKBARS_CONTAINERS.lock();\n        stackbars_containers.insert(self.hwnd, container.clone());\n\n        let mut layout = *layout;\n        let workspace_specific_offset =\n            BORDER_WIDTH.load_consume() + BORDER_OFFSET.load_consume() + container_padding;\n\n        layout.top -= workspace_specific_offset + STACKBAR_TAB_HEIGHT.load_consume();\n        layout.left -= workspace_specific_offset;\n\n        // Async causes the stackbar to disappear or flicker because we modify it right after,\n        // so we have to do a synchronous call\n        WindowsApi::position_window(self.hwnd, &layout, false, false)?;\n\n        unsafe {\n            let hdc = GetDC(Option::from(self.hwnd()));\n\n            let hpen = CreatePen(PS_SOLID, 0, COLORREF(background));\n            let hbrush = CreateSolidBrush(COLORREF(background));\n\n            SelectObject(hdc, hpen.into());\n            SelectObject(hdc, hbrush.into());\n            SetBkColor(hdc, COLORREF(background));\n\n            let mut logfont = LOGFONTW {\n                lfWeight: FW_BOLD.0 as i32,\n                lfQuality: FONT_QUALITY(PROOF_QUALITY.0),\n                lfFaceName: [0; 32],\n                ..Default::default()\n            };\n\n            if let Some(font_name) = &*STACKBAR_FONT_FAMILY.lock() {\n                let font = wide_string(font_name);\n                for (i, &c) in font.iter().enumerate() {\n                    logfont.lfFaceName[i] = c;\n                }\n            }\n\n            let logical_height = -MulDiv(\n                STACKBAR_FONT_SIZE.load(Ordering::SeqCst),\n                72,\n                GetDeviceCaps(Option::from(hdc), LOGPIXELSY),\n            );\n\n            logfont.lfHeight = logical_height;\n\n            let hfont = CreateFontIndirectW(&logfont);\n\n            SelectObject(hdc, hfont.into());\n\n            for (i, window) in container.windows().iter().enumerate() {\n                if window.hwnd == container.focused_window().copied().unwrap_or_default().hwnd {\n                    SetTextColor(hdc, COLORREF(focused_text_colour));\n                } else {\n                    SetTextColor(hdc, COLORREF(unfocused_text_colour));\n                }\n\n                let left = gap + (i as i32 * (width + gap));\n                let mut rect = Rect {\n                    top: 0,\n                    left,\n                    right: left + width,\n                    bottom: height,\n                };\n\n                match STYLE.load() {\n                    BorderStyle::System => {\n                        if *WINDOWS_11 {\n                            // TODO: error handling\n                            let _ = RoundRect(\n                                hdc,\n                                rect.left,\n                                rect.top,\n                                rect.right,\n                                rect.bottom,\n                                20,\n                                20,\n                            );\n                        } else {\n                            // TODO: error handling\n                            let _ = Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom);\n                        }\n                    }\n                    BorderStyle::Rounded => {\n                        // TODO: error handling\n                        let _ =\n                            RoundRect(hdc, rect.left, rect.top, rect.right, rect.bottom, 20, 20);\n                    }\n                    BorderStyle::Square => {\n                        // TODO: error handling\n                        let _ = Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom);\n                    }\n                }\n\n                let label = match STACKBAR_LABEL.load() {\n                    StackbarLabel::Process => {\n                        let exe = window.exe()?;\n                        exe.trim_end_matches(\".exe\").to_string()\n                    }\n                    StackbarLabel::Title => window.title()?,\n                };\n\n                let mut tab_title: Vec<u16> = label.encode_utf16().collect();\n\n                rect.left_padding(10);\n                rect.right_padding(10);\n\n                DrawTextW(\n                    hdc,\n                    &mut tab_title,\n                    &mut rect.into(),\n                    DT_SINGLELINE | DT_CENTER | DT_VCENTER | DT_END_ELLIPSIS,\n                );\n            }\n\n            ReleaseDC(Option::from(self.hwnd()), hdc);\n            // TODO: error handling\n            let _ = DeleteObject(hpen.into());\n            // TODO: error handling\n            let _ = DeleteObject(hbrush.into());\n            // TODO: error handling\n            let _ = DeleteObject(hfont.into());\n        }\n\n        Ok(())\n    }\n\n    pub fn get_position_from_container_layout(&self, layout: &Rect) -> Rect {\n        Rect {\n            bottom: STACKBAR_TAB_HEIGHT.load_consume(),\n            ..*layout\n        }\n    }\n\n    unsafe extern \"system\" fn callback(\n        hwnd: HWND,\n        msg: u32,\n        w_param: WPARAM,\n        l_param: LPARAM,\n    ) -> LRESULT {\n        unsafe {\n            match msg {\n                WM_SETCURSOR => match LoadCursorW(None, IDC_ARROW) {\n                    Ok(cursor) => {\n                        SetCursor(Some(cursor));\n                        LRESULT(0)\n                    }\n                    Err(error) => {\n                        tracing::error!(\"{error}\");\n                        LRESULT(1)\n                    }\n                },\n                WM_LBUTTONDOWN => {\n                    let stackbars_containers = STACKBARS_CONTAINERS.lock();\n                    if let Some(container) = stackbars_containers.get(&(hwnd.0 as isize)) {\n                        let x = l_param.0 as i32 & 0xFFFF;\n                        let y = (l_param.0 as i32 >> 16) & 0xFFFF;\n\n                        let width = STACKBAR_TAB_WIDTH.load_consume();\n                        let height = STACKBAR_TAB_HEIGHT.load_consume();\n                        let gap = DEFAULT_CONTAINER_PADDING.load_consume();\n\n                        let focused_window_idx = container.focused_window_idx();\n                        let focused_window_rect = WindowsApi::window_rect(\n                            container.focused_window().cloned().unwrap_or_default().hwnd,\n                        )\n                        .unwrap_or_default();\n\n                        for (index, window) in container.windows().iter().enumerate() {\n                            let left = gap + (index as i32 * (width + gap));\n                            let right = left + width;\n                            let top = 0;\n                            let bottom = height;\n\n                            if x >= left && x <= right && y >= top && y <= bottom {\n                                // If we are focusing a window that isn't currently focused in the\n                                // stackbar, make sure we update its location so that it doesn't render\n                                // on top of other tiles before eventually ending up in the correct\n                                // tile\n                                if index != focused_window_idx\n                                    && let Err(err) =\n                                        window.set_position(&focused_window_rect, false)\n                                {\n                                    tracing::error!(\n                                        \"stackbar WM_LBUTTONDOWN repositioning error: hwnd {} ({})\",\n                                        *window,\n                                        err\n                                    );\n                                }\n\n                                // Restore the window corresponding to the tab we have clicked\n                                window.restore_with_border(false);\n                                if let Err(err) = window.focus(false) {\n                                    tracing::error!(\n                                        \"stackbar WMLBUTTONDOWN focus error: hwnd {} ({})\",\n                                        *window,\n                                        err\n                                    );\n                                }\n                            } else {\n                                // Hide any windows in the stack that don't correspond to the window\n                                // we have clicked\n                                window.hide_with_border(false);\n                            }\n                        }\n                    }\n\n                    LRESULT(0)\n                }\n                WM_DESTROY => {\n                    PostQuitMessage(0);\n                    LRESULT(0)\n                }\n                _ => DefWindowProcW(hwnd, msg, w_param, l_param),\n            }\n        }\n    }\n}\n\nfn wide_string(s: &str) -> Vec<u16> {\n    std::ffi::OsStr::new(s)\n        .encode_wide()\n        .chain(std::iter::once(0))\n        .collect()\n}\n"
  },
  {
    "path": "komorebi/src/state.rs",
    "content": "use crate::BorderColours;\nuse crate::BorderStyle;\nuse crate::CURRENT_VIRTUAL_DESKTOP;\nuse crate::CUSTOM_FFM;\nuse crate::DATA_DIR;\nuse crate::DISPLAY_INDEX_PREFERENCES;\nuse crate::DUPLICATE_MONITOR_SERIAL_IDS;\nuse crate::FocusFollowsMouseImplementation;\nuse crate::HIDING_BEHAVIOUR;\nuse crate::HOME_DIR;\nuse crate::HidingBehaviour;\nuse crate::IGNORE_IDENTIFIERS;\nuse crate::LAYERED_WHITELIST;\nuse crate::MANAGE_IDENTIFIERS;\nuse crate::MONITOR_INDEX_PREFERENCES;\nuse crate::MoveBehaviour;\nuse crate::OBJECT_NAME_CHANGE_ON_LAUNCH;\nuse crate::OperationBehaviour;\nuse crate::REMOVE_TITLEBARS;\nuse crate::Rect;\nuse crate::StackbarLabel;\nuse crate::StackbarMode;\nuse crate::TRANSPARENCY_BLACKLIST;\nuse crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;\nuse crate::WORKSPACE_MATCHING_RULES;\nuse crate::WindowContainerBehaviour;\nuse crate::WindowManager;\nuse crate::border_manager;\nuse crate::border_manager::STYLE;\nuse crate::config_generation::MatchingRule;\nuse crate::config_generation::WorkspaceMatchingRule;\nuse crate::monitor::Monitor;\nuse crate::ring::Ring;\nuse crate::stackbar_manager::STACKBAR_FOCUSED_TEXT_COLOUR;\nuse crate::stackbar_manager::STACKBAR_LABEL;\nuse crate::stackbar_manager::STACKBAR_MODE;\nuse crate::stackbar_manager::STACKBAR_TAB_BACKGROUND_COLOUR;\nuse crate::stackbar_manager::STACKBAR_TAB_HEIGHT;\nuse crate::stackbar_manager::STACKBAR_TAB_WIDTH;\nuse crate::stackbar_manager::STACKBAR_UNFOCUSED_TEXT_COLOUR;\nuse crate::transparency_manager::TRANSPARENCY_ALPHA;\nuse crate::transparency_manager::TRANSPARENCY_ENABLED;\nuse crate::workspace::Workspace;\nuse komorebi_themes::colour::Colour;\nuse komorebi_themes::colour::Rgb;\nuse serde::Deserialize;\nuse serde::Serialize;\nuse std::collections::HashMap;\nuse std::collections::VecDeque;\nuse std::path::PathBuf;\nuse std::sync::atomic::Ordering;\n\n#[allow(clippy::struct_excessive_bools)]\n#[derive(Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\npub struct State {\n    pub monitors: Ring<Monitor>,\n    pub monitor_usr_idx_map: HashMap<usize, usize>,\n    pub is_paused: bool,\n    pub resize_delta: i32,\n    pub new_window_behaviour: WindowContainerBehaviour,\n    pub float_override: bool,\n    pub cross_monitor_move_behaviour: MoveBehaviour,\n    pub unmanaged_window_operation_behaviour: OperationBehaviour,\n    pub work_area_offset: Option<Rect>,\n    pub focus_follows_mouse: Option<FocusFollowsMouseImplementation>,\n    pub mouse_follows_focus: bool,\n    pub has_pending_raise_op: bool,\n    pub virtual_desktop_id: Option<Vec<u8>>,\n}\n\nimpl State {\n    pub fn has_been_modified(&self, wm: &WindowManager) -> bool {\n        let new = Self::from(wm);\n\n        if self.monitors != new.monitors {\n            return true;\n        }\n\n        if self.is_paused != new.is_paused {\n            return true;\n        }\n\n        if self.new_window_behaviour != new.new_window_behaviour {\n            return true;\n        }\n\n        if self.float_override != new.float_override {\n            return true;\n        }\n\n        if self.cross_monitor_move_behaviour != new.cross_monitor_move_behaviour {\n            return true;\n        }\n\n        if self.unmanaged_window_operation_behaviour != new.unmanaged_window_operation_behaviour {\n            return true;\n        }\n\n        if self.work_area_offset != new.work_area_offset {\n            return true;\n        }\n\n        if self.focus_follows_mouse != new.focus_follows_mouse {\n            return true;\n        }\n\n        if self.mouse_follows_focus != new.mouse_follows_focus {\n            return true;\n        }\n\n        if self.has_pending_raise_op != new.has_pending_raise_op {\n            return true;\n        }\n\n        false\n    }\n}\n\n#[allow(clippy::struct_excessive_bools)]\n#[derive(Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\npub struct GlobalState {\n    pub border_enabled: bool,\n    pub border_colours: BorderColours,\n    pub border_style: BorderStyle,\n    pub border_offset: i32,\n    pub border_width: i32,\n    pub stackbar_mode: StackbarMode,\n    pub stackbar_label: StackbarLabel,\n    pub stackbar_focused_text_colour: Colour,\n    pub stackbar_unfocused_text_colour: Colour,\n    pub stackbar_tab_background_colour: Colour,\n    pub stackbar_tab_width: i32,\n    pub stackbar_height: i32,\n    pub transparency_enabled: bool,\n    pub transparency_alpha: u8,\n    pub transparency_blacklist: Vec<MatchingRule>,\n    pub remove_titlebars: bool,\n    #[serde(alias = \"float_identifiers\")]\n    pub ignore_identifiers: Vec<MatchingRule>,\n    pub manage_identifiers: Vec<MatchingRule>,\n    pub layered_whitelist: Vec<MatchingRule>,\n    pub tray_and_multi_window_identifiers: Vec<MatchingRule>,\n    pub name_change_on_launch_identifiers: Vec<MatchingRule>,\n    pub monitor_index_preferences: HashMap<usize, Rect>,\n    pub display_index_preferences: HashMap<usize, String>,\n    pub ignored_duplicate_monitor_serial_ids: Vec<String>,\n    pub workspace_rules: Vec<WorkspaceMatchingRule>,\n    pub window_hiding_behaviour: HidingBehaviour,\n    pub configuration_dir: PathBuf,\n    pub data_dir: PathBuf,\n    pub custom_ffm: bool,\n    pub current_virtual_desktop_id: Option<Vec<u8>>,\n}\n\nimpl Default for GlobalState {\n    fn default() -> Self {\n        Self {\n            border_enabled: border_manager::BORDER_ENABLED.load(Ordering::SeqCst),\n            border_colours: BorderColours {\n                single: Option::from(Colour::Rgb(Rgb::from(\n                    border_manager::FOCUSED.load(Ordering::SeqCst),\n                ))),\n                stack: Option::from(Colour::Rgb(Rgb::from(\n                    border_manager::STACK.load(Ordering::SeqCst),\n                ))),\n                monocle: Option::from(Colour::Rgb(Rgb::from(\n                    border_manager::MONOCLE.load(Ordering::SeqCst),\n                ))),\n                floating: Option::from(Colour::Rgb(Rgb::from(\n                    border_manager::FLOATING.load(Ordering::SeqCst),\n                ))),\n                unfocused: Option::from(Colour::Rgb(Rgb::from(\n                    border_manager::UNFOCUSED.load(Ordering::SeqCst),\n                ))),\n                unfocused_locked: Option::from(Colour::Rgb(Rgb::from(\n                    border_manager::UNFOCUSED_LOCKED.load(Ordering::SeqCst),\n                ))),\n            },\n            border_style: STYLE.load(),\n            border_offset: border_manager::BORDER_OFFSET.load(Ordering::SeqCst),\n            border_width: border_manager::BORDER_WIDTH.load(Ordering::SeqCst),\n            stackbar_mode: STACKBAR_MODE.load(),\n            stackbar_label: STACKBAR_LABEL.load(),\n            stackbar_focused_text_colour: Colour::Rgb(Rgb::from(\n                STACKBAR_FOCUSED_TEXT_COLOUR.load(Ordering::SeqCst),\n            )),\n            stackbar_unfocused_text_colour: Colour::Rgb(Rgb::from(\n                STACKBAR_UNFOCUSED_TEXT_COLOUR.load(Ordering::SeqCst),\n            )),\n            stackbar_tab_background_colour: Colour::Rgb(Rgb::from(\n                STACKBAR_TAB_BACKGROUND_COLOUR.load(Ordering::SeqCst),\n            )),\n            stackbar_tab_width: STACKBAR_TAB_WIDTH.load(Ordering::SeqCst),\n            stackbar_height: STACKBAR_TAB_HEIGHT.load(Ordering::SeqCst),\n            transparency_enabled: TRANSPARENCY_ENABLED.load(Ordering::SeqCst),\n            transparency_alpha: TRANSPARENCY_ALPHA.load(Ordering::SeqCst),\n            transparency_blacklist: TRANSPARENCY_BLACKLIST.lock().clone(),\n            remove_titlebars: REMOVE_TITLEBARS.load(Ordering::SeqCst),\n            ignore_identifiers: IGNORE_IDENTIFIERS.lock().clone(),\n            manage_identifiers: MANAGE_IDENTIFIERS.lock().clone(),\n            layered_whitelist: LAYERED_WHITELIST.lock().clone(),\n            tray_and_multi_window_identifiers: TRAY_AND_MULTI_WINDOW_IDENTIFIERS.lock().clone(),\n            name_change_on_launch_identifiers: OBJECT_NAME_CHANGE_ON_LAUNCH.lock().clone(),\n            monitor_index_preferences: MONITOR_INDEX_PREFERENCES.lock().clone(),\n            display_index_preferences: DISPLAY_INDEX_PREFERENCES.read().clone(),\n            ignored_duplicate_monitor_serial_ids: DUPLICATE_MONITOR_SERIAL_IDS.read().clone(),\n            workspace_rules: WORKSPACE_MATCHING_RULES.lock().clone(),\n            window_hiding_behaviour: *HIDING_BEHAVIOUR.lock(),\n            configuration_dir: HOME_DIR.clone(),\n            data_dir: DATA_DIR.clone(),\n            custom_ffm: CUSTOM_FFM.load(Ordering::SeqCst),\n            current_virtual_desktop_id: CURRENT_VIRTUAL_DESKTOP.lock().clone(),\n        }\n    }\n}\n\nimpl From<&WindowManager> for State {\n    fn from(wm: &WindowManager) -> Self {\n        // This is used to remove any information that doesn't need to be passed on to subscribers\n        // or to be shown with the `komorebic state` command. Currently it is only removing the\n        // `workspace_config` field from every workspace, but more stripping can be added later if\n        // needed.\n        let mut stripped_monitors = Ring::default();\n        *stripped_monitors.elements_mut() = wm\n            .monitors()\n            .iter()\n            .map(|monitor| Monitor {\n                id: monitor.id,\n                name: monitor.name.clone(),\n                device: monitor.device.clone(),\n                device_id: monitor.device_id.clone(),\n                serial_number_id: monitor.serial_number_id.clone(),\n                size: monitor.size,\n                work_area_size: monitor.work_area_size,\n                work_area_offset: monitor.work_area_offset,\n                window_based_work_area_offset: monitor.window_based_work_area_offset,\n                window_based_work_area_offset_limit: monitor.window_based_work_area_offset_limit,\n                workspaces: {\n                    let mut ws = Ring::default();\n                    *ws.elements_mut() = monitor\n                        .workspaces()\n                        .iter()\n                        .map(|workspace| Workspace {\n                            name: workspace.name.clone(),\n                            containers: workspace.containers.clone(),\n                            monocle_container: workspace.monocle_container.clone(),\n                            monocle_container_restore_idx: workspace.monocle_container_restore_idx,\n                            maximized_window: workspace.maximized_window,\n                            maximized_window_restore_idx: workspace.maximized_window_restore_idx,\n                            floating_windows: workspace.floating_windows.clone(),\n                            layout: workspace.layout.clone(),\n                            layout_options: workspace.layout_options,\n                            layout_rules: workspace.layout_rules.clone(),\n                            layout_flip: workspace.layout_flip,\n                            workspace_padding: workspace.workspace_padding,\n                            container_padding: workspace.container_padding,\n                            latest_layout: workspace.latest_layout.clone(),\n                            resize_dimensions: workspace.resize_dimensions.clone(),\n                            tile: workspace.tile,\n                            work_area_offset: workspace.work_area_offset,\n                            apply_window_based_work_area_offset: workspace\n                                .apply_window_based_work_area_offset,\n                            window_container_behaviour: workspace.window_container_behaviour,\n                            window_container_behaviour_rules: workspace\n                                .window_container_behaviour_rules\n                                .clone(),\n                            float_override: workspace.float_override,\n                            layer: workspace.layer,\n                            floating_layer_behaviour: workspace.floating_layer_behaviour,\n                            globals: workspace.globals,\n                            wallpaper: workspace.wallpaper.clone(),\n                            workspace_config: None,\n                            preselected_container_idx: None,\n                            promotion_swap_container_idx: None,\n                        })\n                        .collect::<VecDeque<_>>();\n                    ws.focus(monitor.workspaces.focused_idx());\n                    ws\n                },\n                last_focused_workspace: monitor.last_focused_workspace,\n                workspace_names: monitor.workspace_names.clone(),\n                container_padding: monitor.container_padding,\n                workspace_padding: monitor.workspace_padding,\n                wallpaper: monitor.wallpaper.clone(),\n                floating_layer_behaviour: monitor.floating_layer_behaviour,\n            })\n            .collect::<VecDeque<_>>();\n        stripped_monitors.focus(wm.monitors.focused_idx());\n\n        Self {\n            monitors: stripped_monitors,\n            monitor_usr_idx_map: wm.monitor_usr_idx_map.clone(),\n            is_paused: wm.is_paused,\n            work_area_offset: wm.work_area_offset,\n            resize_delta: wm.resize_delta,\n            new_window_behaviour: wm.window_management_behaviour.current_behaviour,\n            float_override: wm.window_management_behaviour.float_override,\n            cross_monitor_move_behaviour: wm.cross_monitor_move_behaviour,\n            focus_follows_mouse: wm.focus_follows_mouse,\n            mouse_follows_focus: wm.mouse_follows_focus,\n            has_pending_raise_op: wm.has_pending_raise_op,\n            unmanaged_window_operation_behaviour: wm.unmanaged_window_operation_behaviour,\n            virtual_desktop_id: wm.virtual_desktop_id.clone(),\n        }\n    }\n}\n"
  },
  {
    "path": "komorebi/src/static_config.rs",
    "content": "use crate::AspectRatio;\nuse crate::Axis;\nuse crate::CrossBoundaryBehaviour;\nuse crate::DATA_DIR;\nuse crate::DEFAULT_CONTAINER_PADDING;\nuse crate::DEFAULT_MOUSE_FOLLOWS_FOCUS;\nuse crate::DEFAULT_RESIZE_DELTA;\nuse crate::DEFAULT_WORKSPACE_PADDING;\nuse crate::DISPLAY_INDEX_PREFERENCES;\nuse crate::FLOATING_APPLICATIONS;\nuse crate::FLOATING_WINDOW_TOGGLE_ASPECT_RATIO;\nuse crate::FloatingLayerBehaviour;\nuse crate::HIDING_BEHAVIOUR;\nuse crate::IGNORE_IDENTIFIERS;\nuse crate::LAYERED_WHITELIST;\nuse crate::MANAGE_IDENTIFIERS;\nuse crate::MONITOR_INDEX_PREFERENCES;\nuse crate::NO_TITLEBAR;\nuse crate::OBJECT_NAME_CHANGE_ON_LAUNCH;\nuse crate::OBJECT_NAME_CHANGE_TITLE_IGNORE_LIST;\nuse crate::Placement;\nuse crate::PredefinedAspectRatio;\nuse crate::REGEX_IDENTIFIERS;\nuse crate::ResolvedPathBuf;\nuse crate::SLOW_APPLICATION_COMPENSATION_TIME;\nuse crate::SLOW_APPLICATION_IDENTIFIERS;\nuse crate::TRANSPARENCY_BLACKLIST;\nuse crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;\nuse crate::WINDOW_HANDLING_BEHAVIOUR;\nuse crate::WINDOWS_11;\nuse crate::WORKSPACE_MATCHING_RULES;\nuse crate::WindowHandlingBehaviour;\nuse crate::animation::ANIMATION_DURATION_GLOBAL;\nuse crate::animation::ANIMATION_DURATION_PER_ANIMATION;\nuse crate::animation::ANIMATION_ENABLED_GLOBAL;\nuse crate::animation::ANIMATION_ENABLED_PER_ANIMATION;\nuse crate::animation::ANIMATION_FPS;\nuse crate::animation::ANIMATION_STYLE_GLOBAL;\nuse crate::animation::ANIMATION_STYLE_PER_ANIMATION;\nuse crate::animation::DEFAULT_ANIMATION_FPS;\nuse crate::animation::PerAnimationPrefixConfig;\nuse crate::asc::ApplicationSpecificConfiguration;\nuse crate::asc::AscApplicationRulesOrSchema;\nuse crate::border_manager;\nuse crate::border_manager::IMPLEMENTATION;\nuse crate::border_manager::STYLE;\nuse crate::border_manager::ZOrder;\nuse crate::config_generation::WorkspaceMatchingRule;\nuse crate::core::AnimationStyle;\nuse crate::core::BorderImplementation;\nuse crate::core::BorderStyle;\nuse crate::core::DefaultLayout;\nuse crate::core::FocusFollowsMouseImplementation;\nuse crate::core::HidingBehaviour;\nuse crate::core::Layout;\nuse crate::core::LayoutOptions;\nuse crate::core::MoveBehaviour;\nuse crate::core::OperationBehaviour;\nuse crate::core::Rect;\nuse crate::core::SocketMessage;\nuse crate::core::StackbarLabel;\nuse crate::core::StackbarMode;\nuse crate::core::WindowContainerBehaviour;\nuse crate::core::WindowManagementBehaviour;\nuse crate::core::config_generation::ApplicationConfiguration;\nuse crate::core::config_generation::ApplicationConfigurationGenerator;\nuse crate::core::config_generation::ApplicationOptions;\nuse crate::core::config_generation::MatchingRule;\nuse crate::core::config_generation::MatchingStrategy;\nuse crate::current_virtual_desktop;\nuse crate::monitor;\nuse crate::monitor::Monitor;\nuse crate::monitor_reconciliator;\nuse crate::resolve_option_hashmap_usize_path;\nuse crate::ring::Ring;\nuse crate::stackbar_manager::STACKBAR_FOCUSED_TEXT_COLOUR;\nuse crate::stackbar_manager::STACKBAR_FONT_FAMILY;\nuse crate::stackbar_manager::STACKBAR_FONT_SIZE;\nuse crate::stackbar_manager::STACKBAR_LABEL;\nuse crate::stackbar_manager::STACKBAR_MODE;\nuse crate::stackbar_manager::STACKBAR_TAB_BACKGROUND_COLOUR;\nuse crate::stackbar_manager::STACKBAR_TAB_HEIGHT;\nuse crate::stackbar_manager::STACKBAR_TAB_WIDTH;\nuse crate::stackbar_manager::STACKBAR_UNFOCUSED_TEXT_COLOUR;\nuse crate::theme_manager;\nuse crate::transparency_manager;\nuse crate::window;\nuse crate::window_manager::WindowManager;\nuse crate::window_manager_event::WindowManagerEvent;\nuse crate::windows_api::WindowsApi;\nuse crate::workspace::Workspace;\nuse color_eyre::eyre;\nuse crossbeam_channel::Receiver;\nuse hotwatch::EventKind;\nuse hotwatch::Hotwatch;\nuse komorebi_themes::colour::Colour;\nuse parking_lot::Mutex;\nuse regex::Regex;\nuse serde::Deserialize;\nuse serde::Serialize;\nuse std::collections::HashMap;\nuse std::collections::HashSet;\nuse std::io::ErrorKind;\nuse std::io::Write;\nuse std::path::PathBuf;\nuse std::sync::Arc;\nuse std::sync::atomic::Ordering;\nuse uds_windows::UnixListener;\nuse uds_windows::UnixStream;\n\n#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Border colours for different container states\npub struct BorderColours {\n    /// Border colour when the container contains a single window\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub single: Option<Colour>,\n    /// Border colour when the container contains multiple windows\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub stack: Option<Colour>,\n    /// Border colour when the container is in monocle mode\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub monocle: Option<Colour>,\n    /// Border colour when the container is in floating mode\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub floating: Option<Colour>,\n    /// Border colour when the container is unfocused\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub unfocused: Option<Colour>,\n    /// Border colour when the container is unfocused and locked\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub unfocused_locked: Option<Colour>,\n}\n\n#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Theme options\npub struct ThemeOptions {\n    /// Specify Light or Dark variant for theme generation\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = komorebi_themes::ThemeVariant::Dark)))]\n    pub theme_variant: Option<komorebi_themes::ThemeVariant>,\n    /// Border colour when the container contains a single window\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = komorebi_themes::Base16Value::Base0D)))]\n    pub single_border: Option<komorebi_themes::Base16Value>,\n    /// Border colour when the container contains multiple windows\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = komorebi_themes::Base16Value::Base0B)))]\n    pub stack_border: Option<komorebi_themes::Base16Value>,\n    /// Border colour when the container is in monocle mode\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = komorebi_themes::Base16Value::Base0F)))]\n    pub monocle_border: Option<komorebi_themes::Base16Value>,\n    /// Border colour when the window is floating\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = komorebi_themes::Base16Value::Base09)))]\n    pub floating_border: Option<komorebi_themes::Base16Value>,\n    /// Border colour when the container is unfocused\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = komorebi_themes::Base16Value::Base01)))]\n    pub unfocused_border: Option<komorebi_themes::Base16Value>,\n    /// Border colour when the container is unfocused and locked\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = komorebi_themes::Base16Value::Base08)))]\n    pub unfocused_locked_border: Option<komorebi_themes::Base16Value>,\n    /// Stackbar focused tab text colour\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = komorebi_themes::Base16Value::Base0B)))]\n    pub stackbar_focused_text: Option<komorebi_themes::Base16Value>,\n    /// Stackbar unfocused tab text colour\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = komorebi_themes::Base16Value::Base05)))]\n    pub stackbar_unfocused_text: Option<komorebi_themes::Base16Value>,\n    /// Stackbar tab background colour\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = komorebi_themes::Base16Value::Base01)))]\n    pub stackbar_background: Option<komorebi_themes::Base16Value>,\n    /// Komorebi status bar accent\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = komorebi_themes::Base16Value::Base0D)))]\n    pub bar_accent: Option<komorebi_themes::Base16Value>,\n}\n\n#[serde_with::serde_as]\n#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Wallpaper configuration\npub struct Wallpaper {\n    /// Path to the wallpaper image file\n    #[serde_as(as = \"ResolvedPathBuf\")]\n    pub path: PathBuf,\n    /// Generate and apply Base16 theme for this wallpaper\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = true)))]\n    pub generate_theme: Option<bool>,\n    /// Specify Light or Dark variant for theme generation\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = komorebi_themes::ThemeVariant::Dark)))]\n    pub theme_options: Option<ThemeOptions>,\n}\n\n// serde_as must be before derive\n#[serde_with::serde_as]\n#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Workspace configuration\npub struct WorkspaceConfig {\n    /// Name\n    pub name: String,\n    /// Layout\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = DefaultLayout::BSP)))]\n    pub layout: Option<DefaultLayout>,\n    /// Layout-specific options\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub layout_options: Option<LayoutOptions>,\n    /// END OF LIFE FEATURE: Custom Layout\n    #[deprecated(note = \"End of life feature\")]\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[serde_as(as = \"Option<ResolvedPathBuf>\")]\n    pub custom_layout: Option<PathBuf>,\n    /// Layout rules in the format of threshold => layout\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub layout_rules: Option<HashMap<usize, DefaultLayout>>,\n    /// END OF LIFE FEATURE: Custom layout rules\n    #[deprecated(note = \"End of life feature\")]\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[serde(deserialize_with = \"resolve_option_hashmap_usize_path\", default)]\n    pub custom_layout_rules: Option<HashMap<usize, PathBuf>>,\n    /// Container padding (default: global)\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub container_padding: Option<i32>,\n    /// Workspace padding (default: global)\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub workspace_padding: Option<i32>,\n    /// Initial workspace application rules\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub initial_workspace_rules: Option<Vec<MatchingRule>>,\n    /// Permanent workspace application rules\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub workspace_rules: Option<Vec<MatchingRule>>,\n    /// Workspace specific work area offset\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub work_area_offset: Option<Rect>,\n    /// Apply this monitor's window-based work area offset\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = true)))]\n    pub apply_window_based_work_area_offset: Option<bool>,\n    /// Determine what happens when a new window is opened\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = WindowContainerBehaviour::Create)))]\n    pub window_container_behaviour: Option<WindowContainerBehaviour>,\n    /// Window container behaviour rules in the format of threshold => behaviour\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub window_container_behaviour_rules: Option<HashMap<usize, WindowContainerBehaviour>>,\n    /// Enable or disable float override, which makes it so every new window opens in floating mode\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = false)))]\n    pub float_override: Option<bool>,\n    /// Enable or disable tiling for the workspace\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = true)))]\n    pub tile: Option<bool>,\n    /// Specify an axis on which to flip the selected layout\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub layout_flip: Option<Axis>,\n    /// Determine what happens to a new window when the Floating workspace layer is active\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = FloatingLayerBehaviour::Tile)))]\n    pub floating_layer_behaviour: Option<FloatingLayerBehaviour>,\n    /// Specify a wallpaper for this workspace\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub wallpaper: Option<Wallpaper>,\n}\n\nimpl From<&Workspace> for WorkspaceConfig {\n    fn from(value: &Workspace) -> Self {\n        let mut layout_rules = HashMap::new();\n        for (threshold, layout) in &value.layout_rules {\n            match layout {\n                Layout::Default(value) => {\n                    layout_rules.insert(*threshold, *value);\n                }\n                Layout::Custom(_) => {}\n            }\n        }\n        let layout_rules = (!layout_rules.is_empty()).then_some(layout_rules);\n\n        let mut window_container_behaviour_rules = HashMap::new();\n        for (threshold, behaviour) in value.window_container_behaviour_rules.iter().flatten() {\n            window_container_behaviour_rules.insert(*threshold, *behaviour);\n        }\n\n        let default_container_padding = DEFAULT_CONTAINER_PADDING.load(Ordering::SeqCst);\n        let default_workspace_padding = DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst);\n\n        let container_padding = value.container_padding.and_then(|container_padding| {\n            if container_padding == default_container_padding {\n                None\n            } else {\n                Option::from(container_padding)\n            }\n        });\n\n        let workspace_padding = value.workspace_padding.and_then(|workspace_padding| {\n            if workspace_padding == default_workspace_padding {\n                None\n            } else {\n                Option::from(workspace_padding)\n            }\n        });\n\n        let tile = if value.tile { None } else { Some(false) };\n\n        Self {\n            name: value\n                .name\n                .clone()\n                .unwrap_or_else(|| String::from(\"unnamed\")),\n            layout: value\n                .tile\n                .then_some(match value.layout {\n                    Layout::Default(layout) => Option::from(layout),\n                    Layout::Custom(_) => None,\n                })\n                .flatten(),\n            layout_options: {\n                tracing::debug!(\n                    \"Parsing workspace config - layout_options: {:?}\",\n                    value.layout_options\n                );\n                value.layout_options\n            },\n            #[allow(deprecated)]\n            custom_layout: value\n                .workspace_config\n                .as_ref()\n                .and_then(|c| c.custom_layout.clone()),\n            layout_rules,\n            #[allow(deprecated)]\n            custom_layout_rules: value\n                .workspace_config\n                .as_ref()\n                .and_then(|c| c.custom_layout_rules.clone()),\n            container_padding,\n            workspace_padding,\n            initial_workspace_rules: value\n                .workspace_config\n                .as_ref()\n                .and_then(|c| c.initial_workspace_rules.clone()),\n            workspace_rules: value\n                .workspace_config\n                .as_ref()\n                .and_then(|c| c.workspace_rules.clone()),\n            work_area_offset: value.work_area_offset,\n            apply_window_based_work_area_offset: Some(value.apply_window_based_work_area_offset),\n            window_container_behaviour: value.window_container_behaviour,\n            window_container_behaviour_rules: Option::from(window_container_behaviour_rules),\n            float_override: value.float_override,\n            tile,\n            layout_flip: value.layout_flip,\n            floating_layer_behaviour: value.floating_layer_behaviour,\n            wallpaper: None,\n        }\n    }\n}\n\n#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Monitor configuration\npub struct MonitorConfig {\n    /// Workspace configurations\n    pub workspaces: Vec<WorkspaceConfig>,\n    /// Monitor-specific work area offset\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub work_area_offset: Option<Rect>,\n    /// Window based work area offset\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub window_based_work_area_offset: Option<Rect>,\n    /// Open window limit after which the window based work area offset will no longer be applied\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = 1)))]\n    pub window_based_work_area_offset_limit: Option<isize>,\n    /// Container padding (default: global)\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub container_padding: Option<i32>,\n    /// Workspace padding (default: global)\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub workspace_padding: Option<i32>,\n    /// Specify a wallpaper for this monitor\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub wallpaper: Option<Wallpaper>,\n    /// Determine what happens to a new window when the Floating workspace layer is active\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = FloatingLayerBehaviour::Tile)))]\n    pub floating_layer_behaviour: Option<FloatingLayerBehaviour>,\n}\n\nimpl From<&Monitor> for MonitorConfig {\n    fn from(value: &Monitor) -> Self {\n        let mut workspaces = vec![];\n        for w in value.workspaces() {\n            workspaces.push(WorkspaceConfig::from(w));\n        }\n\n        let default_container_padding = DEFAULT_CONTAINER_PADDING.load(Ordering::SeqCst);\n        let default_workspace_padding = DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst);\n\n        let container_padding = value.container_padding.and_then(|container_padding| {\n            if container_padding == default_container_padding {\n                None\n            } else {\n                Option::from(container_padding)\n            }\n        });\n\n        let workspace_padding = value.workspace_padding.and_then(|workspace_padding| {\n            if workspace_padding == default_workspace_padding {\n                None\n            } else {\n                Option::from(workspace_padding)\n            }\n        });\n\n        Self {\n            workspaces,\n            work_area_offset: value.work_area_offset,\n            window_based_work_area_offset: value.window_based_work_area_offset,\n            window_based_work_area_offset_limit: Some(value.window_based_work_area_offset_limit),\n            container_padding,\n            workspace_padding,\n            wallpaper: value.wallpaper.clone(),\n            floating_layer_behaviour: value.floating_layer_behaviour,\n        }\n    }\n}\n\n#[serde_with::serde_as]\n#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n#[serde(untagged)]\n/// Path(s) to application-specific configuration file(s)\npub enum AppSpecificConfigurationPath {\n    /// A single `applications.json` file\n    Single(#[serde_as(as = \"ResolvedPathBuf\")] PathBuf),\n    /// Multiple `applications.json` files\n    Multiple(#[serde_as(as = \"Vec<ResolvedPathBuf>\")] Vec<PathBuf>),\n}\n\n#[serde_with::serde_as]\n#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// The `komorebi.json` static configuration file reference for `v0.1.41`\npub struct StaticConfig {\n    /// DEPRECATED from v0.1.22: no longer required\n    #[deprecated(note = \"No longer required\")]\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub invisible_borders: Option<Rect>,\n    /// DISCOURAGED: Minimum width for a window to be eligible for tiling\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub minimum_window_width: Option<i32>,\n    /// DISCOURAGED: Minimum height for a window to be eligible for tiling\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub minimum_window_height: Option<i32>,\n    /// Delta to resize windows by\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = DEFAULT_RESIZE_DELTA)))]\n    pub resize_delta: Option<i32>,\n    /// Determine what happens when a new window is opened\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = WindowContainerBehaviour::Create)))]\n    pub window_container_behaviour: Option<WindowContainerBehaviour>,\n    /// Enable or disable float override, which makes it so every new window opens in floating mode\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = false)))]\n    pub float_override: Option<bool>,\n    /// Determines what happens on a new window when on the `FloatingLayer`\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = FloatingLayerBehaviour::Tile)))]\n    pub floating_layer_behaviour: Option<FloatingLayerBehaviour>,\n    /// Determines the placement of a new window when toggling to float\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = Placement::CenterAndResize)))]\n    pub toggle_float_placement: Option<Placement>,\n    /// Determines the `Placement` to be used when spawning a window on the floating layer with the\n    /// `FloatingLayerBehaviour` set to `FloatingLayerBehaviour::Float`\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = Placement::Center)))]\n    pub floating_layer_placement: Option<Placement>,\n    /// Determines the `Placement` to be used when spawning a window with float override active\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub float_override_placement: Option<Placement>,\n    /// Determines the `Placement` to be used when spawning a window that matches a\n    /// `floating_applications` rule\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub float_rule_placement: Option<Placement>,\n    /// Determine what happens when a window is moved across a monitor boundary\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = MoveBehaviour::Swap)))]\n    pub cross_monitor_move_behaviour: Option<MoveBehaviour>,\n    /// Determine what happens when an action is called on a window at a monitor boundary\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = CrossBoundaryBehaviour::Monitor)))]\n    pub cross_boundary_behaviour: Option<CrossBoundaryBehaviour>,\n    /// Determine what happens when commands are sent while an unmanaged window is in the foreground\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = OperationBehaviour::Op)))]\n    pub unmanaged_window_operation_behaviour: Option<OperationBehaviour>,\n    /// END OF LIFE FEATURE: Use https://github.com/LGUG2Z/masir instead\n    #[deprecated(\n        note = \"End of life feature, use [masir](https://github.com/LGUG2Z/masir) instead\"\n    )]\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub focus_follows_mouse: Option<FocusFollowsMouseImplementation>,\n    /// Enable or disable mouse follows focus\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = DEFAULT_MOUSE_FOLLOWS_FOCUS)))]\n    pub mouse_follows_focus: Option<bool>,\n    /// Path to applications.json from komorebi-application-specific-configurations\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub app_specific_configuration_path: Option<AppSpecificConfigurationPath>,\n    /// Width of window borders\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[serde(alias = \"active_window_border_width\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = border_manager::BORDER_WIDTH)))]\n    pub border_width: Option<i32>,\n    /// Offset of window borders\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[serde(alias = \"active_window_border_offset\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = border_manager::BORDER_OFFSET)))]\n    pub border_offset: Option<i32>,\n    /// Display window borders\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[serde(alias = \"active_window_border\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = border_manager::BORDER_ENABLED)))]\n    pub border: Option<bool>,\n    /// Window border colours for different container types (has no effect if [`theme`] is defined)\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[serde(alias = \"active_window_border_colours\")]\n    pub border_colours: Option<BorderColours>,\n    /// Window border style\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[serde(alias = \"active_window_border_style\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = BorderStyle::System)))]\n    pub border_style: Option<BorderStyle>,\n    /// DEPRECATED from v0.1.31: no longer required\n    #[deprecated(note = \"No longer required\")]\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub border_z_order: Option<ZOrder>,\n    /// Window border implementation\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = BorderImplementation::Komorebi)))]\n    pub border_implementation: Option<BorderImplementation>,\n    /// Add transparency to unfocused windows\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = transparency_manager::TRANSPARENCY_ENABLED)))]\n    pub transparency: Option<bool>,\n    /// Alpha value for unfocused window transparency [[0-255]]\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = transparency_manager::TRANSPARENCY_ALPHA)))]\n    pub transparency_alpha: Option<u8>,\n    /// Individual window transparency ignore rules\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub transparency_ignore_rules: Option<Vec<MatchingRule>>,\n    /// Global default workspace padding\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = DEFAULT_WORKSPACE_PADDING)))]\n    pub default_workspace_padding: Option<i32>,\n    /// Global default container padding\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = DEFAULT_CONTAINER_PADDING)))]\n    pub default_container_padding: Option<i32>,\n    /// Monitor and workspace configurations\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub monitors: Option<Vec<MonitorConfig>>,\n    /// Which Windows signal to use when hiding windows\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = HidingBehaviour::Cloak)))]\n    pub window_hiding_behaviour: Option<HidingBehaviour>,\n    /// Global work area (space used for tiling) offset\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub global_work_area_offset: Option<Rect>,\n    /// Individual window floating rules\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[serde(alias = \"float_rules\")]\n    pub ignore_rules: Option<Vec<MatchingRule>>,\n    /// Individual window force-manage rules\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub manage_rules: Option<Vec<MatchingRule>>,\n    /// Identify applications which should be managed as floating windows\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub floating_applications: Option<Vec<MatchingRule>>,\n    /// Identify border overflow applications\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub border_overflow_applications: Option<Vec<MatchingRule>>,\n    /// Identify tray and multi-window applications\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub tray_and_multi_window_applications: Option<Vec<MatchingRule>>,\n    /// Identify applications that have the `WS_EX_LAYERED` extended window style\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub layered_applications: Option<Vec<MatchingRule>>,\n    /// Identify applications that send `EVENT_OBJECT_NAMECHANGE` on launch (very rare)\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub object_name_change_applications: Option<Vec<MatchingRule>>,\n    /// Do not process `EVENT_OBJECT_NAMECHANGE` events as Show events for identified applications matching these title regexes\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub object_name_change_title_ignore_list: Option<Vec<String>>,\n    /// Set monitor index preferences\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub monitor_index_preferences: Option<HashMap<usize, Rect>>,\n    /// Set display index preferences\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub display_index_preferences: Option<HashMap<usize, String>>,\n    /// Stackbar configuration options\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub stackbar: Option<StackbarConfig>,\n    /// Animations configuration options\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub animation: Option<AnimationsConfig>,\n    /// Theme configuration options\n    ///\n    /// If a theme is specified, `border_colours` will have no effect\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub theme: Option<KomorebiTheme>,\n    /// Identify applications which are slow to send initial event notifications\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub slow_application_identifiers: Option<Vec<MatchingRule>>,\n    /// How long to wait when compensating for slow applications, in milliseconds\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = SLOW_APPLICATION_COMPENSATION_TIME)))]\n    pub slow_application_compensation_time: Option<u64>,\n    /// Komorebi status bar configuration files for multiple instances on different monitors\n    // this option is a little special because it is only consumed by komorebic\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[serde_as(as = \"Option<Vec<ResolvedPathBuf>>\")]\n    pub bar_configurations: Option<Vec<PathBuf>>,\n    /// HEAVILY DISCOURAGED: Identify applications for which komorebi should forcibly remove title bars\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub remove_titlebar_applications: Option<Vec<MatchingRule>>,\n    /// Aspect ratio to resize with when toggling floating mode for a window\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub floating_window_aspect_ratio: Option<AspectRatio>,\n    /// Which Windows API behaviour to use when manipulating windows\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = WindowHandlingBehaviour::Sync)))]\n    pub window_handling_behaviour: Option<WindowHandlingBehaviour>,\n}\n\n#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Animations configuration options\npub struct AnimationsConfig {\n    /// Enable or disable animations\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = PerAnimationPrefixConfig::Global(false))))]\n    pub enabled: PerAnimationPrefixConfig<bool>,\n    /// Set the animation duration in ms\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = PerAnimationPrefixConfig::Global(250))))]\n    pub duration: Option<PerAnimationPrefixConfig<u64>>,\n    /// Set the animation style\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = PerAnimationPrefixConfig::Global(AnimationStyle::Linear))))]\n    pub style: Option<PerAnimationPrefixConfig<AnimationStyle>>,\n    /// Set the animation FPS\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = ANIMATION_FPS)))]\n    pub fps: Option<u64>,\n}\n\npub use komorebi_themes::KomorebiTheme;\n\nimpl StaticConfig {\n    pub fn end_of_life(raw: &str) {\n        let features = vec![\n            \"focus_follows_mouse\",\n            \"custom_layout\",\n            \"custom_layout_rules\",\n        ];\n\n        let mut display = false;\n\n        for feature in features {\n            if raw.contains(feature) {\n                if !display {\n                    display = true;\n                    println!(\"\\n\\\"{feature}\\\" is now end-of-life\");\n                } else {\n                    println!(r#\"\"{feature}\" is now end-of-life\"#);\n                }\n            }\n        }\n\n        if display {\n            println!(\n                \"\\nEnd-of-life features will not receive any further bug fixes or updates; they should not be used\\n\"\n            )\n        }\n    }\n\n    pub fn aliases(raw: &str) {\n        let mut map = HashMap::new();\n        map.insert(\"border\", [\"active_window_border\"]);\n        map.insert(\"border_width\", [\"active_window_border_width\"]);\n        map.insert(\"border_offset\", [\"active_window_border_offset\"]);\n        map.insert(\"border_colours\", [\"active_window_border_colours\"]);\n        map.insert(\"border_style\", [\"active_window_border_style\"]);\n        map.insert(\"applications.json\", [\"applications.yaml\"]);\n        map.insert(\"ignore_rules\", [\"float_rules\"]);\n\n        let mut display = false;\n\n        for aliases in map.values() {\n            for a in aliases {\n                if raw.contains(a) {\n                    display = true;\n                }\n            }\n        }\n\n        if display {\n            println!(\n                \"\\nYour configuration file contains some options that have been renamed or deprecated:\\n\"\n            );\n            for (canonical, aliases) in map {\n                for alias in aliases {\n                    if raw.contains(alias) {\n                        println!(r#\"\"{alias}\" is now \"{canonical}\"\"#);\n                    }\n                }\n            }\n        }\n    }\n\n    pub fn deprecated(raw: &str) {\n        let deprecated_options = [\"invisible_borders\", \"border_z_order\"];\n        let deprecated_variants = vec![\n            (\"Hide\", \"window_hiding_behaviour\", \"Cloak\"),\n            (\"Minimize\", \"window_hiding_behaviour\", \"Cloak\"),\n        ];\n\n        for option in deprecated_options {\n            if raw.contains(option) {\n                println!(r#\"\"{option}\" is deprecated and can be removed\"#);\n            }\n        }\n\n        for (variant, option, recommended) in deprecated_variants {\n            if raw.contains(option) && raw.contains(variant) {\n                println!(\n                    r#\"The \"{variant}\" option for \"{option}\" is deprecated and can be removed or replaced with \"{recommended}\"\"#\n                );\n            }\n        }\n    }\n}\n\n#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Stackbar tabs configuration\npub struct TabsConfig {\n    /// Width of a stackbar tab\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub width: Option<i32>,\n    /// Focused tab text colour\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub focused_text: Option<Colour>,\n    /// Unfocused tab text colour\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub unfocused_text: Option<Colour>,\n    /// Tab background colour\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub background: Option<Colour>,\n    /// Font family\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub font_family: Option<String>,\n    /// Font size\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub font_size: Option<i32>,\n}\n\n#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Stackbar configuration\npub struct StackbarConfig {\n    /// Stackbar height\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub height: Option<i32>,\n    /// Stackbar label\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub label: Option<StackbarLabel>,\n    /// Stackbar mode\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = StackbarMode::Never)))]\n    pub mode: Option<StackbarMode>,\n    /// Stackbar tab configuration options\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub tabs: Option<TabsConfig>,\n}\n\nimpl From<&WindowManager> for StaticConfig {\n    #[allow(clippy::too_many_lines)]\n    fn from(value: &WindowManager) -> Self {\n        let mut monitors = vec![];\n        for m in value.monitors() {\n            monitors.push(MonitorConfig::from(m));\n        }\n\n        let border_colours = if border_manager::FOCUSED.load(Ordering::SeqCst) == 0 {\n            None\n        } else {\n            Option::from(BorderColours {\n                single: Option::from(Colour::from(border_manager::FOCUSED.load(Ordering::SeqCst))),\n                stack: Option::from(Colour::from(border_manager::STACK.load(Ordering::SeqCst))),\n                monocle: Option::from(Colour::from(border_manager::MONOCLE.load(Ordering::SeqCst))),\n                floating: Option::from(Colour::from(\n                    border_manager::FLOATING.load(Ordering::SeqCst),\n                )),\n                unfocused: Option::from(Colour::from(\n                    border_manager::UNFOCUSED.load(Ordering::SeqCst),\n                )),\n                unfocused_locked: Option::from(Colour::from(\n                    border_manager::UNFOCUSED_LOCKED.load(Ordering::SeqCst),\n                )),\n            })\n        };\n\n        Self {\n            #[allow(deprecated)]\n            invisible_borders: None,\n            resize_delta: Option::from(value.resize_delta),\n            window_container_behaviour: Option::from(\n                value.window_management_behaviour.current_behaviour,\n            ),\n            float_override: Option::from(value.window_management_behaviour.float_override),\n            floating_layer_behaviour: Option::from(\n                value.window_management_behaviour.floating_layer_behaviour,\n            ),\n            toggle_float_placement: Option::from(\n                value.window_management_behaviour.toggle_float_placement,\n            ),\n            floating_layer_placement: Option::from(\n                value.window_management_behaviour.floating_layer_placement,\n            ),\n            float_override_placement: Option::from(\n                value.window_management_behaviour.float_override_placement,\n            ),\n            float_rule_placement: Option::from(\n                value.window_management_behaviour.float_rule_placement,\n            ),\n            cross_monitor_move_behaviour: Option::from(value.cross_monitor_move_behaviour),\n            cross_boundary_behaviour: Option::from(value.cross_boundary_behaviour),\n            unmanaged_window_operation_behaviour: Option::from(\n                value.unmanaged_window_operation_behaviour,\n            ),\n            minimum_window_height: Some(window::MINIMUM_HEIGHT.load(Ordering::SeqCst)),\n            minimum_window_width: Some(window::MINIMUM_WIDTH.load(Ordering::SeqCst)),\n            #[allow(deprecated)]\n            focus_follows_mouse: value.focus_follows_mouse,\n            mouse_follows_focus: Option::from(value.mouse_follows_focus),\n            app_specific_configuration_path: None,\n            border_width: Option::from(border_manager::BORDER_WIDTH.load(Ordering::SeqCst)),\n            border_offset: Option::from(border_manager::BORDER_OFFSET.load(Ordering::SeqCst)),\n            border: Option::from(border_manager::BORDER_ENABLED.load(Ordering::SeqCst)),\n            border_colours,\n            transparency: Option::from(\n                transparency_manager::TRANSPARENCY_ENABLED.load(Ordering::SeqCst),\n            ),\n            transparency_alpha: Option::from(\n                transparency_manager::TRANSPARENCY_ALPHA.load(Ordering::SeqCst),\n            ),\n            transparency_ignore_rules: None,\n            border_style: Option::from(STYLE.load()),\n            #[allow(deprecated)]\n            border_z_order: None,\n            border_implementation: Option::from(IMPLEMENTATION.load()),\n            default_workspace_padding: Option::from(\n                DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst),\n            ),\n            default_container_padding: Option::from(\n                DEFAULT_CONTAINER_PADDING.load(Ordering::SeqCst),\n            ),\n            monitors: Option::from(monitors),\n            window_hiding_behaviour: Option::from(*HIDING_BEHAVIOUR.lock()),\n            global_work_area_offset: value.work_area_offset,\n            ignore_rules: None,\n            floating_applications: None,\n            manage_rules: None,\n            border_overflow_applications: None,\n            tray_and_multi_window_applications: None,\n            layered_applications: None,\n            object_name_change_applications: Option::from(\n                OBJECT_NAME_CHANGE_ON_LAUNCH.lock().clone(),\n            ),\n            object_name_change_title_ignore_list: Option::from(\n                OBJECT_NAME_CHANGE_TITLE_IGNORE_LIST\n                    .lock()\n                    .clone()\n                    .iter()\n                    .map(|r| r.to_string())\n                    .collect::<Vec<_>>(),\n            ),\n            monitor_index_preferences: Option::from(MONITOR_INDEX_PREFERENCES.lock().clone()),\n            display_index_preferences: Option::from(DISPLAY_INDEX_PREFERENCES.read().clone()),\n            stackbar: None,\n            animation: None,\n            theme: None,\n            slow_application_compensation_time: Option::from(\n                SLOW_APPLICATION_COMPENSATION_TIME.load(Ordering::SeqCst),\n            ),\n            slow_application_identifiers: Option::from(SLOW_APPLICATION_IDENTIFIERS.lock().clone()),\n            bar_configurations: None,\n            remove_titlebar_applications: Option::from(NO_TITLEBAR.lock().clone()),\n            floating_window_aspect_ratio: Option::from(*FLOATING_WINDOW_TOGGLE_ASPECT_RATIO.lock()),\n            window_handling_behaviour: Option::from(WINDOW_HANDLING_BEHAVIOUR.load()),\n        }\n    }\n}\n\nimpl StaticConfig {\n    #[allow(clippy::cognitive_complexity, clippy::too_many_lines)]\n    fn apply_globals(&mut self) -> eyre::Result<()> {\n        *FLOATING_WINDOW_TOGGLE_ASPECT_RATIO.lock() = self\n            .floating_window_aspect_ratio\n            .unwrap_or(AspectRatio::Predefined(PredefinedAspectRatio::Standard));\n\n        if let Some(monitor_index_preferences) = &self.monitor_index_preferences {\n            let mut preferences = MONITOR_INDEX_PREFERENCES.lock();\n            preferences.clone_from(monitor_index_preferences);\n        }\n\n        if let Some(display_index_preferences) = &self.display_index_preferences {\n            let mut preferences = DISPLAY_INDEX_PREFERENCES.write();\n            preferences.clone_from(display_index_preferences);\n        }\n\n        if let Some(behaviour) = self.window_hiding_behaviour {\n            let mut window_hiding_behaviour = HIDING_BEHAVIOUR.lock();\n            *window_hiding_behaviour = behaviour;\n        }\n\n        if let Some(height) = self.minimum_window_height {\n            window::MINIMUM_HEIGHT.store(height, Ordering::SeqCst);\n        }\n\n        if let Some(width) = self.minimum_window_width {\n            window::MINIMUM_WIDTH.store(width, Ordering::SeqCst);\n        }\n\n        if let Some(animations) = &self.animation {\n            match &animations.enabled {\n                PerAnimationPrefixConfig::Prefix(enabled) => {\n                    ANIMATION_ENABLED_PER_ANIMATION.lock().clone_from(enabled);\n                }\n                PerAnimationPrefixConfig::Global(enabled) => {\n                    ANIMATION_ENABLED_GLOBAL.store(*enabled, Ordering::SeqCst);\n                    ANIMATION_ENABLED_PER_ANIMATION.lock().clear();\n                }\n            }\n\n            match &animations.style {\n                Some(PerAnimationPrefixConfig::Prefix(style)) => {\n                    ANIMATION_STYLE_PER_ANIMATION.lock().clone_from(style);\n                }\n                Some(PerAnimationPrefixConfig::Global(style)) => {\n                    let mut animation_style = ANIMATION_STYLE_GLOBAL.lock();\n                    *animation_style = *style;\n                    ANIMATION_STYLE_PER_ANIMATION.lock().clear();\n                }\n                None => {}\n            }\n\n            match &animations.duration {\n                Some(PerAnimationPrefixConfig::Prefix(duration)) => {\n                    ANIMATION_DURATION_PER_ANIMATION.lock().clone_from(duration);\n                }\n                Some(PerAnimationPrefixConfig::Global(duration)) => {\n                    ANIMATION_DURATION_GLOBAL.store(*duration, Ordering::SeqCst);\n                    ANIMATION_DURATION_PER_ANIMATION.lock().clear();\n                }\n                None => {}\n            }\n\n            ANIMATION_FPS.store(\n                animations.fps.unwrap_or(DEFAULT_ANIMATION_FPS),\n                Ordering::SeqCst,\n            );\n        }\n\n        if let Some(container) = self.default_container_padding {\n            DEFAULT_CONTAINER_PADDING.store(container, Ordering::SeqCst);\n        }\n\n        if let Some(workspace) = self.default_workspace_padding {\n            DEFAULT_WORKSPACE_PADDING.store(workspace, Ordering::SeqCst);\n        }\n\n        if let Some(border_width) = self.border_width {\n            border_manager::BORDER_WIDTH.store(border_width, Ordering::SeqCst);\n        }\n\n        if let Some(border_offset) = self.border_offset {\n            border_manager::BORDER_OFFSET.store(border_offset, Ordering::SeqCst);\n        }\n\n        if let Some(border_enabled) = self.border {\n            border_manager::BORDER_ENABLED.store(border_enabled, Ordering::SeqCst);\n        }\n\n        if let Some(colours) = &self.border_colours {\n            if let Some(single) = colours.single {\n                border_manager::FOCUSED.store(u32::from(single), Ordering::SeqCst);\n            }\n\n            if let Some(stack) = colours.stack {\n                border_manager::STACK.store(u32::from(stack), Ordering::SeqCst);\n            }\n\n            if let Some(monocle) = colours.monocle {\n                border_manager::MONOCLE.store(u32::from(monocle), Ordering::SeqCst);\n            }\n\n            if let Some(floating) = colours.floating {\n                border_manager::FLOATING.store(u32::from(floating), Ordering::SeqCst);\n            }\n\n            if let Some(unfocused) = colours.unfocused {\n                border_manager::UNFOCUSED.store(u32::from(unfocused), Ordering::SeqCst);\n            }\n\n            if let Some(unfocused_locked) = colours.unfocused_locked {\n                border_manager::UNFOCUSED_LOCKED\n                    .store(u32::from(unfocused_locked), Ordering::SeqCst);\n            }\n        }\n\n        STYLE.store(self.border_style.unwrap_or_default());\n\n        if !*WINDOWS_11\n            && matches!(\n                self.border_implementation.unwrap_or_default(),\n                BorderImplementation::Windows\n            )\n        {\n            tracing::error!(\n                \"BorderImplementation::Windows is only supported on Windows 11 and above\"\n            );\n        } else {\n            IMPLEMENTATION.store(self.border_implementation.unwrap_or_default());\n            match IMPLEMENTATION.load() {\n                BorderImplementation::Komorebi => {\n                    border_manager::destroy_all_borders()?;\n                }\n                BorderImplementation::Windows => {\n                    // TODO: figure out how to call wm.remove_all_accents here\n                }\n            }\n\n            border_manager::send_notification(None);\n        }\n\n        if let Some(transparency_enabled) = self.transparency {\n            transparency_manager::TRANSPARENCY_ENABLED\n                .store(transparency_enabled, Ordering::SeqCst);\n        }\n\n        if let Some(transparency_alpha) = self.transparency_alpha {\n            transparency_manager::TRANSPARENCY_ALPHA.store(transparency_alpha, Ordering::SeqCst);\n        }\n\n        let mut ignore_identifiers = IGNORE_IDENTIFIERS.lock();\n        let mut regex_identifiers = REGEX_IDENTIFIERS.lock();\n        let mut manage_identifiers = MANAGE_IDENTIFIERS.lock();\n        let mut tray_and_multi_window_identifiers = TRAY_AND_MULTI_WINDOW_IDENTIFIERS.lock();\n        let mut object_name_change_identifiers = OBJECT_NAME_CHANGE_ON_LAUNCH.lock();\n        let mut object_name_change_title_ignore_list = OBJECT_NAME_CHANGE_TITLE_IGNORE_LIST.lock();\n        let mut layered_identifiers = LAYERED_WHITELIST.lock();\n        let mut transparency_blacklist = TRANSPARENCY_BLACKLIST.lock();\n        let mut slow_application_identifiers = SLOW_APPLICATION_IDENTIFIERS.lock();\n        let mut floating_applications = FLOATING_APPLICATIONS.lock();\n        let mut no_titlebar_applications = NO_TITLEBAR.lock();\n\n        if let Some(rules) = &mut self.ignore_rules {\n            populate_rules(rules, &mut ignore_identifiers, &mut regex_identifiers)?;\n        }\n\n        if let Some(rules) = &mut self.floating_applications {\n            populate_rules(rules, &mut floating_applications, &mut regex_identifiers)?;\n        }\n\n        if let Some(rules) = &mut self.manage_rules {\n            populate_rules(rules, &mut manage_identifiers, &mut regex_identifiers)?;\n        }\n\n        if let Some(rules) = &mut self.object_name_change_applications {\n            populate_rules(\n                rules,\n                &mut object_name_change_identifiers,\n                &mut regex_identifiers,\n            )?;\n        }\n\n        if let Some(regexes) = &mut self.object_name_change_title_ignore_list {\n            let mut updated = vec![];\n            for r in regexes {\n                if let Ok(regex) = Regex::new(r) {\n                    updated.push(regex);\n                }\n            }\n\n            *object_name_change_title_ignore_list = updated;\n        }\n\n        if let Some(rules) = &mut self.layered_applications {\n            populate_rules(rules, &mut layered_identifiers, &mut regex_identifiers)?;\n        }\n\n        if let Some(rules) = &mut self.tray_and_multi_window_applications {\n            populate_rules(\n                rules,\n                &mut tray_and_multi_window_identifiers,\n                &mut regex_identifiers,\n            )?;\n        }\n\n        if let Some(rules) = &mut self.transparency_ignore_rules {\n            populate_rules(rules, &mut transparency_blacklist, &mut regex_identifiers)?;\n        }\n\n        if let Some(rules) = &mut self.slow_application_identifiers {\n            populate_rules(\n                rules,\n                &mut slow_application_identifiers,\n                &mut regex_identifiers,\n            )?;\n        }\n\n        if let Some(rules) = &mut self.remove_titlebar_applications {\n            populate_rules(rules, &mut no_titlebar_applications, &mut regex_identifiers)?;\n        }\n\n        if let Some(stackbar) = &self.stackbar {\n            if let Some(height) = &stackbar.height {\n                STACKBAR_TAB_HEIGHT.store(*height, Ordering::SeqCst);\n            }\n\n            if let Some(label) = &stackbar.label {\n                STACKBAR_LABEL.store(*label);\n            }\n\n            if let Some(mode) = &stackbar.mode {\n                STACKBAR_MODE.store(*mode);\n            }\n\n            #[allow(clippy::assigning_clones)]\n            if let Some(tabs) = &stackbar.tabs {\n                if let Some(background) = &tabs.background {\n                    STACKBAR_TAB_BACKGROUND_COLOUR.store((*background).into(), Ordering::SeqCst);\n                }\n\n                if let Some(colour) = &tabs.focused_text {\n                    STACKBAR_FOCUSED_TEXT_COLOUR.store((*colour).into(), Ordering::SeqCst);\n                }\n\n                if let Some(colour) = &tabs.unfocused_text {\n                    STACKBAR_UNFOCUSED_TEXT_COLOUR.store((*colour).into(), Ordering::SeqCst);\n                }\n\n                if let Some(width) = &tabs.width {\n                    STACKBAR_TAB_WIDTH.store(*width, Ordering::SeqCst);\n                }\n\n                STACKBAR_FONT_SIZE.store(tabs.font_size.unwrap_or(0), Ordering::SeqCst);\n                *STACKBAR_FONT_FAMILY.lock() = tabs.font_family.clone();\n            }\n        }\n\n        if let Some(theme) = &self.theme {\n            theme_manager::send_notification(theme.clone());\n        }\n\n        if let Some(path) = &self.app_specific_configuration_path {\n            match path {\n                AppSpecificConfigurationPath::Single(path) => handle_asc_file(\n                    path,\n                    &mut ignore_identifiers,\n                    &mut object_name_change_identifiers,\n                    &mut layered_identifiers,\n                    &mut tray_and_multi_window_identifiers,\n                    &mut manage_identifiers,\n                    &mut floating_applications,\n                    &mut transparency_blacklist,\n                    &mut slow_application_identifiers,\n                    &mut regex_identifiers,\n                )?,\n                AppSpecificConfigurationPath::Multiple(paths) => {\n                    for path in paths {\n                        handle_asc_file(\n                            path,\n                            &mut ignore_identifiers,\n                            &mut object_name_change_identifiers,\n                            &mut layered_identifiers,\n                            &mut tray_and_multi_window_identifiers,\n                            &mut manage_identifiers,\n                            &mut floating_applications,\n                            &mut transparency_blacklist,\n                            &mut slow_application_identifiers,\n                            &mut regex_identifiers,\n                        )?\n                    }\n                }\n            }\n        }\n\n        if let Some(behaviour) = self.window_handling_behaviour {\n            WINDOW_HANDLING_BEHAVIOUR.store(behaviour);\n        }\n\n        Ok(())\n    }\n\n    pub fn read_raw(raw: &str) -> eyre::Result<Self> {\n        Ok(serde_json::from_str(raw)?)\n    }\n\n    pub fn read(path: &PathBuf) -> eyre::Result<Self> {\n        let content = std::fs::read_to_string(path)?;\n        serde_json::from_str(&content).map_err(Into::into)\n    }\n\n    #[allow(clippy::too_many_lines)]\n    pub fn preload(\n        path: &PathBuf,\n        incoming: Receiver<WindowManagerEvent>,\n        unix_listener: Option<UnixListener>,\n    ) -> eyre::Result<WindowManager> {\n        let mut value = Self::read(path)?;\n        value.apply_globals()?;\n\n        let listener = match unix_listener {\n            Some(listener) => listener,\n            None => {\n                let socket = DATA_DIR.join(\"komorebi.sock\");\n\n                match std::fs::remove_file(&socket) {\n                    Ok(()) => {}\n                    Err(error) => match error.kind() {\n                        // Doing this because ::exists() doesn't work reliably on Windows via IntelliJ\n                        ErrorKind::NotFound => {}\n                        _ => {\n                            return Err(error.into());\n                        }\n                    },\n                };\n\n                UnixListener::bind(&socket)?\n            }\n        };\n\n        let mut wm = WindowManager {\n            monitors: Ring::default(),\n            monitor_usr_idx_map: HashMap::new(),\n            incoming_events: incoming,\n            command_listener: listener,\n            is_paused: false,\n            virtual_desktop_id: current_virtual_desktop(),\n            work_area_offset: value.global_work_area_offset,\n            window_management_behaviour: WindowManagementBehaviour {\n                current_behaviour: value\n                    .window_container_behaviour\n                    .unwrap_or(WindowContainerBehaviour::Create),\n                float_override: value.float_override.unwrap_or_default(),\n                floating_layer_override: false, // this value is always automatically calculated\n                floating_layer_behaviour: FloatingLayerBehaviour::default(),\n                toggle_float_placement: value\n                    .toggle_float_placement\n                    .unwrap_or(Placement::CenterAndResize),\n                floating_layer_placement: value\n                    .floating_layer_placement\n                    .unwrap_or(Placement::Center),\n                float_override_placement: value.float_override_placement.unwrap_or(Placement::None),\n                float_rule_placement: value.float_rule_placement.unwrap_or(Placement::None),\n            },\n            cross_monitor_move_behaviour: value\n                .cross_monitor_move_behaviour\n                .unwrap_or(MoveBehaviour::Swap),\n            cross_boundary_behaviour: value\n                .cross_boundary_behaviour\n                .unwrap_or(CrossBoundaryBehaviour::Monitor),\n            unmanaged_window_operation_behaviour: value\n                .unmanaged_window_operation_behaviour\n                .unwrap_or(OperationBehaviour::Op),\n            resize_delta: value.resize_delta.unwrap_or(DEFAULT_RESIZE_DELTA),\n            #[allow(deprecated)]\n            focus_follows_mouse: value.focus_follows_mouse,\n            mouse_follows_focus: value\n                .mouse_follows_focus\n                .unwrap_or(DEFAULT_MOUSE_FOLLOWS_FOCUS),\n            hotwatch: Hotwatch::new()?,\n            has_pending_raise_op: false,\n            pending_move_op: Arc::new(None),\n            already_moved_window_handles: Arc::new(Mutex::new(HashSet::new())),\n            uncloack_to_ignore: 0,\n            known_hwnds: HashMap::new(),\n        };\n\n        #[allow(deprecated)]\n        match value.focus_follows_mouse {\n            None => WindowsApi::disable_focus_follows_mouse()?,\n            Some(FocusFollowsMouseImplementation::Windows) => {\n                WindowsApi::enable_focus_follows_mouse()?;\n            }\n            Some(FocusFollowsMouseImplementation::Komorebi) => {}\n        };\n\n        let bytes = SocketMessage::ReloadStaticConfiguration(path.clone()).as_bytes()?;\n\n        wm.hotwatch.watch(path, move |event| match event.kind {\n            // Editing in Notepad sends a NoticeWrite while editing in (Neo)Vim sends\n            // a NoticeRemove, presumably because of the use of swap files?\n            EventKind::Modify(_) | EventKind::Remove(_) => {\n                let socket = DATA_DIR.join(\"komorebi.sock\");\n                let mut stream =\n                    UnixStream::connect(socket).expect(\"could not connect to komorebi.sock\");\n                stream\n                    .write_all(&bytes)\n                    .expect(\"could not write to komorebi.sock\");\n            }\n            _ => {}\n        })?;\n\n        Ok(wm)\n    }\n\n    pub fn postload(path: &PathBuf, wm: &Arc<Mutex<WindowManager>>) -> eyre::Result<()> {\n        let mut value = Self::read(path)?;\n        let mut wm = wm.lock();\n\n        let configs_with_preference: Vec<_> =\n            DISPLAY_INDEX_PREFERENCES.read().keys().copied().collect();\n        let mut configs_used = Vec::new();\n\n        let mut workspace_matching_rules = WORKSPACE_MATCHING_RULES.lock();\n        workspace_matching_rules.clear();\n        drop(workspace_matching_rules);\n\n        let monitor_count = wm.monitors().len();\n\n        let offset = wm.work_area_offset;\n        for (i, monitor) in wm.monitors_mut().iter_mut().enumerate() {\n            let preferred_config_idx = {\n                let display_index_preferences = DISPLAY_INDEX_PREFERENCES.read();\n\n                display_index_preferences.iter().find_map(|(c_idx, id)| {\n                    (monitor.serial_number_id.as_ref().is_some_and(|sn| sn == id)\n                        || monitor.device_id.eq(id))\n                    .then_some(*c_idx)\n                })\n            };\n            let idx = preferred_config_idx.or({\n                // Monitor without preferred config idx.\n                // Get index of first config that is not a preferred config of some other monitor\n                // and that has not been used yet. This might return `None` as well, in that case\n                // this monitor won't have a config tied to it and will use the default values.\n                let m_config_count = value\n                    .monitors\n                    .as_ref()\n                    .map(|ms| ms.len())\n                    .unwrap_or_default();\n                (0..m_config_count)\n                    .find(|i| !configs_with_preference.contains(i) && !configs_used.contains(i))\n            });\n            if let Some(monitor_config) = value\n                .monitors\n                .as_mut()\n                .and_then(|ms| idx.and_then(|i| ms.get_mut(i)))\n            {\n                if let Some(used_config_idx) = idx {\n                    configs_used.push(used_config_idx);\n                }\n\n                monitor.ensure_workspace_count(monitor_config.workspaces.len());\n                monitor.work_area_offset = monitor_config.work_area_offset;\n                monitor.window_based_work_area_offset =\n                    monitor_config.window_based_work_area_offset;\n                monitor.window_based_work_area_offset_limit = monitor_config\n                    .window_based_work_area_offset_limit\n                    .unwrap_or(1);\n                monitor.container_padding = monitor_config.container_padding;\n                monitor.workspace_padding = monitor_config.workspace_padding;\n                monitor.wallpaper = monitor_config.wallpaper.clone();\n                monitor.floating_layer_behaviour = monitor_config.floating_layer_behaviour;\n\n                monitor.update_workspaces_globals(offset);\n                for (j, ws) in monitor.workspaces_mut().iter_mut().enumerate() {\n                    if let Some(workspace_config) = monitor_config.workspaces.get_mut(j) {\n                        if monitor_count > 1\n                            && matches!(workspace_config.layout, Some(DefaultLayout::Scrolling))\n                        {\n                            tracing::warn!(\n                                \"scrolling layout is only supported for a single monitor; falling back to columns layout\"\n                            );\n                            workspace_config.layout = Some(DefaultLayout::Columns);\n                        }\n\n                        ws.load_static_config(workspace_config)?;\n                    }\n                }\n\n                // Check if this monitor config is the preferred config for this monitor and store\n                // a copy of the monitor itself on the monitor cache if it is.\n                if idx == preferred_config_idx {\n                    let id = monitor\n                        .serial_number_id\n                        .as_ref()\n                        .map_or(&monitor.device_id, |sn| sn);\n                    monitor_reconciliator::insert_in_monitor_cache(id, monitor.clone());\n                }\n\n                let mut workspace_matching_rules = WORKSPACE_MATCHING_RULES.lock();\n                for (j, ws) in monitor_config.workspaces.iter().enumerate() {\n                    if let Some(rules) = &ws.workspace_rules {\n                        for r in rules {\n                            workspace_matching_rules.push(WorkspaceMatchingRule {\n                                monitor_index: i,\n                                workspace_index: j,\n                                matching_rule: r.clone(),\n                                initial_only: false,\n                            });\n                        }\n                    }\n\n                    if let Some(rules) = &ws.initial_workspace_rules {\n                        for r in rules {\n                            workspace_matching_rules.push(WorkspaceMatchingRule {\n                                monitor_index: i,\n                                workspace_index: j,\n                                matching_rule: r.clone(),\n                                initial_only: true,\n                            });\n                        }\n                    }\n                }\n            }\n        }\n\n        // Check for configs that should be tied to a specific display that isn't loaded right now\n        // and cache a monitor with those configs with the specific `serial_number_id` so that when\n        // those devices are connected later we can use the correct config from the cache.\n        if configs_with_preference.len() > configs_used.len() {\n            for i in configs_with_preference\n                .iter()\n                .filter(|i| !configs_used.contains(i))\n            {\n                let id = {\n                    let display_index_preferences = DISPLAY_INDEX_PREFERENCES.read();\n                    display_index_preferences.get(i).cloned()\n                };\n                if let (Some(id), Some(monitor_config)) =\n                    (id, value.monitors.as_ref().and_then(|ms| ms.get(*i)))\n                {\n                    // The name, device, device_id and serial_number_id can be empty here since\n                    // once the monitor with this preferred index actually connects the\n                    // `load_monitor_information` function will update these fields.\n                    let mut m = monitor::new(\n                        0,\n                        Rect::default(),\n                        Rect::default(),\n                        \"\".into(),\n                        \"\".into(),\n                        \"\".into(),\n                        None,\n                    );\n\n                    m.ensure_workspace_count(monitor_config.workspaces.len());\n                    m.work_area_offset = monitor_config.work_area_offset;\n                    m.window_based_work_area_offset = monitor_config.window_based_work_area_offset;\n                    m.window_based_work_area_offset_limit = monitor_config\n                        .window_based_work_area_offset_limit\n                        .unwrap_or(1);\n                    m.container_padding = monitor_config.container_padding;\n                    m.workspace_padding = monitor_config.workspace_padding;\n                    m.floating_layer_behaviour = monitor_config.floating_layer_behaviour;\n\n                    m.update_workspaces_globals(offset);\n\n                    for (j, ws) in m.workspaces_mut().iter_mut().enumerate() {\n                        if let Some(workspace_config) = monitor_config.workspaces.get(j) {\n                            ws.load_static_config(workspace_config)?;\n                        }\n                    }\n\n                    monitor_reconciliator::insert_in_monitor_cache(&id, m);\n                }\n            }\n        }\n\n        wm.enforce_workspace_rules()?;\n\n        if value.border == Some(true) {\n            border_manager::BORDER_ENABLED.store(true, Ordering::SeqCst);\n        }\n\n        Ok(())\n    }\n\n    pub fn reload(path: &PathBuf, wm: &mut WindowManager) -> eyre::Result<()> {\n        let mut value = Self::read(path)?;\n\n        value.apply_globals()?;\n\n        let configs_with_preference: Vec<_> =\n            DISPLAY_INDEX_PREFERENCES.read().keys().copied().collect();\n        let mut configs_used = Vec::new();\n\n        let mut workspace_matching_rules = WORKSPACE_MATCHING_RULES.lock();\n        workspace_matching_rules.clear();\n        drop(workspace_matching_rules);\n\n        let offset = wm.work_area_offset;\n        for (i, monitor) in wm.monitors_mut().iter_mut().enumerate() {\n            let preferred_config_idx = {\n                let display_index_preferences = DISPLAY_INDEX_PREFERENCES.read();\n\n                display_index_preferences.iter().find_map(|(c_idx, id)| {\n                    (monitor.serial_number_id.as_ref().is_some_and(|sn| sn == id)\n                        || monitor.device_id.eq(id))\n                    .then_some(*c_idx)\n                })\n            };\n            let idx = preferred_config_idx.or({\n                // Monitor without preferred config idx.\n                // Get index of first config that is not a preferred config of some other monitor\n                // and that has not been used yet. This might return `None` as well, in that case\n                // this monitor won't have a config tied to it and will use the default values.\n                let m_config_count = value\n                    .monitors\n                    .as_ref()\n                    .map(|ms| ms.len())\n                    .unwrap_or_default();\n                (0..m_config_count)\n                    .find(|i| !configs_with_preference.contains(i) && !configs_used.contains(i))\n            });\n            if let Some(monitor_config) = value\n                .monitors\n                .as_ref()\n                .and_then(|ms| idx.and_then(|i| ms.get(i)))\n            {\n                if let Some(used_config_idx) = idx {\n                    configs_used.push(used_config_idx);\n                }\n\n                monitor.ensure_workspace_count(monitor_config.workspaces.len());\n                if monitor.work_area_offset.is_none() {\n                    monitor.work_area_offset = monitor_config.work_area_offset;\n                }\n                monitor.window_based_work_area_offset =\n                    monitor_config.window_based_work_area_offset;\n                monitor.window_based_work_area_offset_limit = monitor_config\n                    .window_based_work_area_offset_limit\n                    .unwrap_or(1);\n                monitor.container_padding = monitor_config.container_padding;\n                monitor.workspace_padding = monitor_config.workspace_padding;\n                monitor.wallpaper = monitor_config.wallpaper.clone();\n                monitor.floating_layer_behaviour = monitor_config.floating_layer_behaviour;\n\n                monitor.update_workspaces_globals(offset);\n\n                for (j, ws) in monitor.workspaces_mut().iter_mut().enumerate() {\n                    if let Some(workspace_config) = monitor_config.workspaces.get(j) {\n                        ws.load_static_config(workspace_config)?;\n                    }\n                }\n\n                // Check if this monitor config is the preferred config for this monitor and store\n                // a copy of the monitor itself on the monitor cache if it is.\n                if idx == preferred_config_idx {\n                    let id = monitor\n                        .serial_number_id\n                        .as_ref()\n                        .map_or(&monitor.device_id, |sn| sn);\n                    monitor_reconciliator::insert_in_monitor_cache(id, monitor.clone());\n                }\n\n                let mut workspace_matching_rules = WORKSPACE_MATCHING_RULES.lock();\n                for (j, ws) in monitor_config.workspaces.iter().enumerate() {\n                    if let Some(rules) = &ws.workspace_rules {\n                        for r in rules {\n                            workspace_matching_rules.push(WorkspaceMatchingRule {\n                                monitor_index: i,\n                                workspace_index: j,\n                                matching_rule: r.clone(),\n                                initial_only: false,\n                            });\n                        }\n                    }\n\n                    if let Some(rules) = &ws.initial_workspace_rules {\n                        for r in rules {\n                            workspace_matching_rules.push(WorkspaceMatchingRule {\n                                monitor_index: i,\n                                workspace_index: j,\n                                matching_rule: r.clone(),\n                                initial_only: true,\n                            });\n                        }\n                    }\n                }\n            }\n        }\n\n        // Check for configs that should be tied to a specific display that isn't loaded right now\n        // and cache a monitor with those configs with the specific `serial_number_id` so that when\n        // those devices are connected later we can use the correct config from the cache.\n        if configs_with_preference.len() > configs_used.len() {\n            for i in configs_with_preference\n                .iter()\n                .filter(|i| !configs_used.contains(i))\n            {\n                let id = {\n                    let display_index_preferences = DISPLAY_INDEX_PREFERENCES.read();\n                    display_index_preferences.get(i).cloned()\n                };\n                if let (Some(id), Some(monitor_config)) =\n                    (id, value.monitors.as_ref().and_then(|ms| ms.get(*i)))\n                {\n                    // The name, device, device_id and serial_number_id can be empty here since\n                    // once the monitor with this preferred index actually connects the\n                    // `load_monitor_information` function will update these fields.\n                    let mut m = monitor::new(\n                        0,\n                        Rect::default(),\n                        Rect::default(),\n                        \"\".into(),\n                        \"\".into(),\n                        \"\".into(),\n                        None,\n                    );\n\n                    m.ensure_workspace_count(monitor_config.workspaces.len());\n                    m.work_area_offset = monitor_config.work_area_offset;\n                    m.window_based_work_area_offset = monitor_config.window_based_work_area_offset;\n                    m.window_based_work_area_offset_limit = monitor_config\n                        .window_based_work_area_offset_limit\n                        .unwrap_or(1);\n                    m.container_padding = monitor_config.container_padding;\n                    m.workspace_padding = monitor_config.workspace_padding;\n                    m.floating_layer_behaviour = monitor_config.floating_layer_behaviour;\n\n                    m.update_workspaces_globals(offset);\n\n                    for (j, ws) in m.workspaces_mut().iter_mut().enumerate() {\n                        if let Some(workspace_config) = monitor_config.workspaces.get(j) {\n                            ws.load_static_config(workspace_config)?;\n                        }\n                    }\n\n                    monitor_reconciliator::insert_in_monitor_cache(&id, m);\n                }\n            }\n        }\n\n        wm.enforce_workspace_rules()?;\n\n        if let Some(border_enabled) = value.border {\n            border_manager::BORDER_ENABLED.store(border_enabled, Ordering::SeqCst);\n        }\n\n        wm.window_management_behaviour.current_behaviour =\n            value.window_container_behaviour.unwrap_or_default();\n        wm.window_management_behaviour.float_override = value.float_override.unwrap_or_default();\n        wm.window_management_behaviour.floating_layer_behaviour =\n            value.floating_layer_behaviour.unwrap_or_default();\n        wm.window_management_behaviour.toggle_float_placement = value\n            .toggle_float_placement\n            .unwrap_or(Placement::CenterAndResize);\n        wm.window_management_behaviour.floating_layer_placement =\n            value.floating_layer_placement.unwrap_or(Placement::Center);\n        wm.window_management_behaviour.float_override_placement =\n            value.float_override_placement.unwrap_or(Placement::None);\n        wm.window_management_behaviour.float_rule_placement =\n            value.float_rule_placement.unwrap_or(Placement::None);\n        wm.cross_monitor_move_behaviour = value.cross_monitor_move_behaviour.unwrap_or_default();\n        wm.cross_boundary_behaviour = value.cross_boundary_behaviour.unwrap_or_default();\n        wm.unmanaged_window_operation_behaviour = value\n            .unmanaged_window_operation_behaviour\n            .unwrap_or_default();\n        wm.resize_delta = value.resize_delta.unwrap_or(DEFAULT_RESIZE_DELTA);\n        wm.mouse_follows_focus = value\n            .mouse_follows_focus\n            .unwrap_or(DEFAULT_MOUSE_FOLLOWS_FOCUS);\n        wm.work_area_offset = value.global_work_area_offset;\n        #[allow(deprecated)]\n        {\n            wm.focus_follows_mouse = value.focus_follows_mouse;\n        }\n\n        match wm.focus_follows_mouse {\n            None => WindowsApi::disable_focus_follows_mouse()?,\n            Some(FocusFollowsMouseImplementation::Windows) => {\n                WindowsApi::enable_focus_follows_mouse()?;\n            }\n            Some(FocusFollowsMouseImplementation::Komorebi) => {}\n        };\n\n        let monitor_count = wm.monitors().len();\n\n        for i in 0..monitor_count {\n            wm.update_focused_workspace_by_monitor_idx(i)?;\n            let ws_idx = wm.focused_workspace_idx_for_monitor_idx(i)?;\n            wm.apply_wallpaper_for_monitor_workspace(i, ws_idx)?;\n        }\n\n        Ok(())\n    }\n}\n\nfn populate_option(\n    entry: &mut ApplicationConfiguration,\n    identifiers: &mut Vec<MatchingRule>,\n    regex_identifiers: &mut HashMap<String, Regex>,\n) -> eyre::Result<()> {\n    if entry.identifier.matching_strategy.is_none() {\n        entry.identifier.matching_strategy = Option::from(MatchingStrategy::Legacy);\n    }\n\n    let rule = MatchingRule::Simple(entry.identifier.clone());\n\n    if !identifiers.contains(&rule) {\n        identifiers.push(rule);\n\n        if matches!(\n            entry.identifier.matching_strategy,\n            Some(MatchingStrategy::Regex)\n        ) {\n            let re = Regex::new(&entry.identifier.id)?;\n            regex_identifiers.insert(entry.identifier.id.clone(), re);\n        }\n    }\n\n    Ok(())\n}\n\nfn populate_rules(\n    matching_rules: &mut Vec<MatchingRule>,\n    identifiers: &mut Vec<MatchingRule>,\n    regex_identifiers: &mut HashMap<String, Regex>,\n) -> eyre::Result<()> {\n    for matching_rule in matching_rules {\n        if !identifiers.contains(matching_rule) {\n            match matching_rule {\n                MatchingRule::Simple(simple) => {\n                    if simple.matching_strategy.is_none() {\n                        simple.matching_strategy = Option::from(MatchingStrategy::Legacy);\n                    }\n\n                    if matches!(simple.matching_strategy, Some(MatchingStrategy::Regex)) {\n                        let re = Regex::new(&simple.id)?;\n                        regex_identifiers.insert(simple.id.clone(), re);\n                    }\n                }\n                MatchingRule::Composite(composite) => {\n                    for rule in composite {\n                        if rule.matching_strategy.is_none() {\n                            rule.matching_strategy = Option::from(MatchingStrategy::Legacy);\n                        }\n\n                        if matches!(rule.matching_strategy, Some(MatchingStrategy::Regex)) {\n                            let re = Regex::new(&rule.id)?;\n                            regex_identifiers.insert(rule.id.clone(), re);\n                        }\n                    }\n                }\n            }\n            identifiers.push(matching_rule.clone());\n        }\n    }\n\n    Ok(())\n}\n\n#[allow(clippy::too_many_arguments)]\nfn handle_asc_file(\n    path: &PathBuf,\n    ignore_identifiers: &mut Vec<MatchingRule>,\n    object_name_change_identifiers: &mut Vec<MatchingRule>,\n    layered_identifiers: &mut Vec<MatchingRule>,\n    tray_and_multi_window_identifiers: &mut Vec<MatchingRule>,\n    manage_identifiers: &mut Vec<MatchingRule>,\n    floating_applications: &mut Vec<MatchingRule>,\n    transparency_blacklist: &mut Vec<MatchingRule>,\n    slow_application_identifiers: &mut Vec<MatchingRule>,\n    regex_identifiers: &mut HashMap<String, Regex>,\n) -> eyre::Result<()> {\n    match path.extension() {\n        None => {}\n        Some(ext) => match ext.to_string_lossy().to_string().as_str() {\n            \"yaml\" => {\n                tracing::info!(\"loading applications.yaml from: {}\", path.display());\n                let content = std::fs::read_to_string(path)?;\n                let asc = ApplicationConfigurationGenerator::load(&content)?;\n\n                for mut entry in asc {\n                    if let Some(rules) = &mut entry.ignore_identifiers {\n                        populate_rules(rules, ignore_identifiers, regex_identifiers)?;\n                    }\n\n                    if let Some(ref options) = entry.options {\n                        let options = options.clone();\n                        for o in options {\n                            match o {\n                                ApplicationOptions::ObjectNameChange => {\n                                    populate_option(\n                                        &mut entry,\n                                        object_name_change_identifiers,\n                                        regex_identifiers,\n                                    )?;\n                                }\n                                ApplicationOptions::Layered => {\n                                    populate_option(\n                                        &mut entry,\n                                        layered_identifiers,\n                                        regex_identifiers,\n                                    )?;\n                                }\n                                ApplicationOptions::TrayAndMultiWindow => {\n                                    populate_option(\n                                        &mut entry,\n                                        tray_and_multi_window_identifiers,\n                                        regex_identifiers,\n                                    )?;\n                                }\n                                ApplicationOptions::Force => {\n                                    populate_option(\n                                        &mut entry,\n                                        manage_identifiers,\n                                        regex_identifiers,\n                                    )?;\n                                }\n                                ApplicationOptions::BorderOverflow => {} // deprecated\n                            }\n                        }\n                    }\n                }\n            }\n            \"json\" => {\n                tracing::info!(\"loading applications.json from: {}\", path.display());\n                let mut asc = ApplicationSpecificConfiguration::load(path)?;\n\n                for entry in asc.values_mut() {\n                    match entry {\n                        AscApplicationRulesOrSchema::Schema(_) => {}\n                        AscApplicationRulesOrSchema::AscApplicationRules(entry) => {\n                            if let Some(rules) = &mut entry.ignore {\n                                populate_rules(rules, ignore_identifiers, regex_identifiers)?;\n                            }\n\n                            if let Some(rules) = &mut entry.manage {\n                                populate_rules(rules, manage_identifiers, regex_identifiers)?;\n                            }\n\n                            if let Some(rules) = &mut entry.floating {\n                                populate_rules(rules, floating_applications, regex_identifiers)?;\n                            }\n\n                            if let Some(rules) = &mut entry.transparency_ignore {\n                                populate_rules(rules, transparency_blacklist, regex_identifiers)?;\n                            }\n\n                            if let Some(rules) = &mut entry.tray_and_multi_window {\n                                populate_rules(\n                                    rules,\n                                    tray_and_multi_window_identifiers,\n                                    regex_identifiers,\n                                )?;\n                            }\n\n                            if let Some(rules) = &mut entry.layered {\n                                populate_rules(rules, layered_identifiers, regex_identifiers)?;\n                            }\n\n                            if let Some(rules) = &mut entry.object_name_change {\n                                populate_rules(\n                                    rules,\n                                    object_name_change_identifiers,\n                                    regex_identifiers,\n                                )?;\n                            }\n\n                            if let Some(rules) = &mut entry.slow_application {\n                                populate_rules(\n                                    rules,\n                                    slow_application_identifiers,\n                                    regex_identifiers,\n                                )?;\n                            }\n                        }\n                    }\n                }\n            }\n            _ => {}\n        },\n    }\n\n    Ok(())\n}\n\n#[cfg(test)]\nmod tests {\n    use std::path::PathBuf;\n\n    use crate::StaticConfig;\n    use crate::WorkspaceConfig;\n\n    #[test]\n    #[ignore = \"this fails on github actions due to rate limiting changes introduced in may 2025\"]\n    fn backwards_compat() {\n        let root = vec![\"0.1.17\", \"0.1.18\", \"0.1.19\"];\n        let docs = vec![\n            \"0.1.20\", \"0.1.21\", \"0.1.22\", \"0.1.23\", \"0.1.24\", \"0.1.25\", \"0.1.26\", \"0.1.27\",\n            \"0.1.28\", \"0.1.29\", \"0.1.30\", \"0.1.31\", \"0.1.32\", \"0.1.33\", \"0.1.34\", \"0.1.35\",\n            \"0.1.36\", \"0.1.37\", \"0.1.38\", \"0.1.39\",\n        ];\n\n        let mut versions = vec![];\n\n        let client = reqwest::blocking::Client::new();\n\n        for version in root {\n            let request = client.get(format!(\"https://raw.githubusercontent.com/LGUG2Z/komorebi/refs/tags/v{version}/komorebi.example.json\")).header(\"User-Agent\", \"komorebi-backwards-compat-test\").build().unwrap();\n            versions.push((version, client.execute(request).unwrap().text().unwrap()));\n        }\n\n        for version in docs {\n            let request = client.get(format!(\"https://raw.githubusercontent.com/LGUG2Z/komorebi/refs/tags/v{version}/docs/komorebi.example.json\")).header(\"User-Agent\", \"komorebi-backwards-compat-test\").build().unwrap();\n            versions.push((version, client.execute(request).unwrap().text().unwrap()));\n        }\n\n        for (version, config) in versions {\n            println!(\"{version}\");\n            StaticConfig::read_raw(&config).unwrap();\n        }\n    }\n\n    #[test]\n    fn deserialize_custom_layout_rules() {\n        // set an environment variable for testing\n        unsafe {\n            std::env::set_var(\"VAR\", \"VALUE\");\n        }\n\n        let config = r#\"\n        {\n            \"name\": \"Test\",\n            \"custom_layout_rules\": {\n                \"1\": \"path/to/dir\",\n                \"2\": \"path/to/%VAR%\"\n            }\n        }\n        \"#;\n        let config = serde_json::from_str::<WorkspaceConfig>(config).unwrap();\n\n        #[allow(deprecated)]\n        let custom_layout_rules = config.custom_layout_rules.unwrap();\n\n        assert_eq!(\n            custom_layout_rules.get(&1).unwrap(),\n            &PathBuf::from(\"path/to/dir\")\n        );\n        assert_eq!(\n            custom_layout_rules.get(&2).unwrap(),\n            &PathBuf::from(\"path/to/VALUE\")\n        );\n\n        let config = r#\"\n        {\n            \"name\": \"Test\",\n        }\n        \"#;\n\n        let config = serde_json::from_str::<WorkspaceConfig>(config).unwrap();\n        #[allow(deprecated)]\n        let custom_layout_rules = config.custom_layout_rules;\n        assert_eq!(custom_layout_rules, None);\n    }\n}\n"
  },
  {
    "path": "komorebi/src/styles.rs",
    "content": "use bitflags::bitflags;\nuse serde::Deserialize;\nuse serde::Serialize;\nuse windows::Win32::UI::WindowsAndMessaging::WS_BORDER;\nuse windows::Win32::UI::WindowsAndMessaging::WS_CAPTION;\nuse windows::Win32::UI::WindowsAndMessaging::WS_CHILD;\nuse windows::Win32::UI::WindowsAndMessaging::WS_CHILDWINDOW;\nuse windows::Win32::UI::WindowsAndMessaging::WS_CLIPCHILDREN;\nuse windows::Win32::UI::WindowsAndMessaging::WS_CLIPSIBLINGS;\nuse windows::Win32::UI::WindowsAndMessaging::WS_DISABLED;\nuse windows::Win32::UI::WindowsAndMessaging::WS_DLGFRAME;\nuse windows::Win32::UI::WindowsAndMessaging::WS_EX_ACCEPTFILES;\nuse windows::Win32::UI::WindowsAndMessaging::WS_EX_APPWINDOW;\nuse windows::Win32::UI::WindowsAndMessaging::WS_EX_CLIENTEDGE;\nuse windows::Win32::UI::WindowsAndMessaging::WS_EX_COMPOSITED;\nuse windows::Win32::UI::WindowsAndMessaging::WS_EX_CONTEXTHELP;\nuse windows::Win32::UI::WindowsAndMessaging::WS_EX_CONTROLPARENT;\nuse windows::Win32::UI::WindowsAndMessaging::WS_EX_DLGMODALFRAME;\nuse windows::Win32::UI::WindowsAndMessaging::WS_EX_LAYERED;\nuse windows::Win32::UI::WindowsAndMessaging::WS_EX_LAYOUTRTL;\nuse windows::Win32::UI::WindowsAndMessaging::WS_EX_LEFT;\nuse windows::Win32::UI::WindowsAndMessaging::WS_EX_LEFTSCROLLBAR;\nuse windows::Win32::UI::WindowsAndMessaging::WS_EX_LTRREADING;\nuse windows::Win32::UI::WindowsAndMessaging::WS_EX_MDICHILD;\nuse windows::Win32::UI::WindowsAndMessaging::WS_EX_NOACTIVATE;\nuse windows::Win32::UI::WindowsAndMessaging::WS_EX_NOINHERITLAYOUT;\nuse windows::Win32::UI::WindowsAndMessaging::WS_EX_NOPARENTNOTIFY;\nuse windows::Win32::UI::WindowsAndMessaging::WS_EX_NOREDIRECTIONBITMAP;\nuse windows::Win32::UI::WindowsAndMessaging::WS_EX_OVERLAPPEDWINDOW;\nuse windows::Win32::UI::WindowsAndMessaging::WS_EX_PALETTEWINDOW;\nuse windows::Win32::UI::WindowsAndMessaging::WS_EX_RIGHT;\nuse windows::Win32::UI::WindowsAndMessaging::WS_EX_RIGHTSCROLLBAR;\nuse windows::Win32::UI::WindowsAndMessaging::WS_EX_RTLREADING;\nuse windows::Win32::UI::WindowsAndMessaging::WS_EX_STATICEDGE;\nuse windows::Win32::UI::WindowsAndMessaging::WS_EX_TOOLWINDOW;\nuse windows::Win32::UI::WindowsAndMessaging::WS_EX_TOPMOST;\nuse windows::Win32::UI::WindowsAndMessaging::WS_EX_TRANSPARENT;\nuse windows::Win32::UI::WindowsAndMessaging::WS_EX_WINDOWEDGE;\nuse windows::Win32::UI::WindowsAndMessaging::WS_GROUP;\nuse windows::Win32::UI::WindowsAndMessaging::WS_HSCROLL;\nuse windows::Win32::UI::WindowsAndMessaging::WS_ICONIC;\nuse windows::Win32::UI::WindowsAndMessaging::WS_MAXIMIZE;\nuse windows::Win32::UI::WindowsAndMessaging::WS_MAXIMIZEBOX;\nuse windows::Win32::UI::WindowsAndMessaging::WS_MINIMIZE;\nuse windows::Win32::UI::WindowsAndMessaging::WS_MINIMIZEBOX;\nuse windows::Win32::UI::WindowsAndMessaging::WS_OVERLAPPED;\nuse windows::Win32::UI::WindowsAndMessaging::WS_OVERLAPPEDWINDOW;\nuse windows::Win32::UI::WindowsAndMessaging::WS_POPUP;\nuse windows::Win32::UI::WindowsAndMessaging::WS_POPUPWINDOW;\nuse windows::Win32::UI::WindowsAndMessaging::WS_SIZEBOX;\nuse windows::Win32::UI::WindowsAndMessaging::WS_SYSMENU;\nuse windows::Win32::UI::WindowsAndMessaging::WS_TABSTOP;\nuse windows::Win32::UI::WindowsAndMessaging::WS_THICKFRAME;\nuse windows::Win32::UI::WindowsAndMessaging::WS_TILED;\nuse windows::Win32::UI::WindowsAndMessaging::WS_TILEDWINDOW;\nuse windows::Win32::UI::WindowsAndMessaging::WS_VISIBLE;\nuse windows::Win32::UI::WindowsAndMessaging::WS_VSCROLL;\n\n// https://docs.microsoft.com/en-us/windows/win32/winmsg/window-styles\nbitflags! {\n    #[derive(Default, Debug, Copy, Clone, Serialize, Deserialize)]\n    pub struct WindowStyle: u32 {\n        const BORDER = WS_BORDER.0;\n        const CAPTION = WS_CAPTION.0;\n        const CHILD = WS_CHILD.0;\n        const CHILDWINDOW = WS_CHILDWINDOW.0;\n        const CLIPCHILDREN = WS_CLIPCHILDREN.0;\n        const CLIPSIBLINGS = WS_CLIPSIBLINGS.0;\n        const DISABLED = WS_DISABLED.0;\n        const DLGFRAME = WS_DLGFRAME.0;\n        const GROUP = WS_GROUP.0;\n        const HSCROLL = WS_HSCROLL.0;\n        const ICONIC = WS_ICONIC.0;\n        const MAXIMIZE = WS_MAXIMIZE.0;\n        const MAXIMIZEBOX = WS_MAXIMIZEBOX.0;\n        const MINIMIZE = WS_MINIMIZE.0;\n        const MINIMIZEBOX = WS_MINIMIZEBOX.0;\n        const OVERLAPPED = WS_OVERLAPPED.0;\n        const OVERLAPPEDWINDOW = WS_OVERLAPPEDWINDOW.0;\n        const POPUP = WS_POPUP.0;\n        const POPUPWINDOW = WS_POPUPWINDOW.0;\n        const SIZEBOX = WS_SIZEBOX.0;\n        const SYSMENU = WS_SYSMENU.0;\n        const TABSTOP = WS_TABSTOP.0;\n        const THICKFRAME = WS_THICKFRAME.0;\n        const TILED = WS_TILED.0;\n        const TILEDWINDOW = WS_TILEDWINDOW.0;\n        const VISIBLE = WS_VISIBLE.0;\n        const VSCROLL = WS_VSCROLL.0;\n    }\n}\n\n// https://docs.microsoft.com/en-us/windows/win32/winmsg/extended-window-styles\nbitflags! {\n    #[derive(Default, Debug, Copy, Clone, Serialize, Deserialize)]\n    pub struct ExtendedWindowStyle: u32 {\n        const ACCEPTFILES = WS_EX_ACCEPTFILES.0;\n        const APPWINDOW = WS_EX_APPWINDOW.0;\n        const CLIENTEDGE = WS_EX_CLIENTEDGE.0;\n        const COMPOSITED = WS_EX_COMPOSITED.0;\n        const CONTEXTHELP = WS_EX_CONTEXTHELP.0;\n        const CONTROLPARENT = WS_EX_CONTROLPARENT.0;\n        const DLGMODALFRAME = WS_EX_DLGMODALFRAME.0;\n        const LAYERED = WS_EX_LAYERED.0;\n        const LAYOUTRTL = WS_EX_LAYOUTRTL.0;\n        const LEFT = WS_EX_LEFT.0;\n        const LEFTSCROLLBAR = WS_EX_LEFTSCROLLBAR.0;\n        const LTRREADING = WS_EX_LTRREADING.0;\n        const MDICHILD = WS_EX_MDICHILD.0;\n        const NOACTIVATE = WS_EX_NOACTIVATE.0;\n        const NOINHERITLAYOUT = WS_EX_NOINHERITLAYOUT.0;\n        const NOPARENTNOTIFY = WS_EX_NOPARENTNOTIFY.0;\n        const NOREDIRECTIONBITMAP = WS_EX_NOREDIRECTIONBITMAP.0;\n        const OVERLAPPEDWINDOW = WS_EX_OVERLAPPEDWINDOW.0;\n        const PALETTEWINDOW = WS_EX_PALETTEWINDOW.0;\n        const RIGHT = WS_EX_RIGHT.0;\n        const RIGHTSCROLLBAR = WS_EX_RIGHTSCROLLBAR.0;\n        const RTLREADING = WS_EX_RTLREADING.0;\n        const STATICEDGE = WS_EX_STATICEDGE.0;\n        const TOOLWINDOW = WS_EX_TOOLWINDOW.0;\n        const TOPMOST = WS_EX_TOPMOST.0;\n        const TRANSPARENT = WS_EX_TRANSPARENT.0;\n        const WINDOWEDGE = WS_EX_WINDOWEDGE.0;\n    }\n}\n"
  },
  {
    "path": "komorebi/src/theme_manager.rs",
    "content": "#![deny(clippy::unwrap_used, clippy::expect_used)]\n\nuse crate::KomorebiTheme;\nuse crate::border_manager;\nuse crate::stackbar_manager;\nuse crate::stackbar_manager::STACKBAR_FOCUSED_TEXT_COLOUR;\nuse crate::stackbar_manager::STACKBAR_TAB_BACKGROUND_COLOUR;\nuse crate::stackbar_manager::STACKBAR_UNFOCUSED_TEXT_COLOUR;\nuse crossbeam_channel::Receiver;\nuse crossbeam_channel::Sender;\nuse crossbeam_utils::atomic::AtomicCell;\nuse komorebi_themes::Base16Wrapper;\nuse komorebi_themes::KomorebiThemeBase16 as Base16;\nuse komorebi_themes::KomorebiThemeCatppuccin as Catppuccin;\nuse komorebi_themes::KomorebiThemeCustom as Custom;\nuse komorebi_themes::colour::Colour;\nuse std::ops::Deref;\nuse std::sync::OnceLock;\nuse std::sync::atomic::Ordering;\n\npub struct Notification(KomorebiTheme);\n\npub static CURRENT_THEME: AtomicCell<Option<KomorebiTheme>> = AtomicCell::new(None);\n\nimpl Deref for Notification {\n    type Target = KomorebiTheme;\n\n    fn deref(&self) -> &Self::Target {\n        &self.0\n    }\n}\n\nstatic CHANNEL: OnceLock<(Sender<Notification>, Receiver<Notification>)> = OnceLock::new();\n\npub fn channel() -> &'static (Sender<Notification>, Receiver<Notification>) {\n    CHANNEL.get_or_init(|| crossbeam_channel::bounded(20))\n}\n\nfn event_tx() -> Sender<Notification> {\n    channel().0.clone()\n}\n\nfn event_rx() -> Receiver<Notification> {\n    channel().1.clone()\n}\n\n// Currently this should only be used for async focus updates, such as\n// when an animation finishes and we need to focus to set the cursor\n// position if the user has mouse follows focus enabled\npub fn send_notification(theme: KomorebiTheme) {\n    if event_tx().try_send(Notification(theme)).is_err() {\n        tracing::warn!(\"channel is full; dropping notification\")\n    }\n}\n\npub fn listen_for_notifications() {\n    std::thread::spawn(move || {\n        loop {\n            match handle_notifications() {\n                Ok(()) => {\n                    tracing::warn!(\"restarting finished thread\");\n                }\n                Err(error) => {\n                    tracing::warn!(\"restarting failed thread: {}\", error);\n                }\n            }\n        }\n    });\n}\n\npub fn handle_notifications() -> color_eyre::Result<()> {\n    tracing::info!(\"listening\");\n\n    let receiver = event_rx();\n\n    for notification in receiver {\n        let theme = &notification.0;\n\n        let (\n            single_border,\n            stack_border,\n            monocle_border,\n            floating_border,\n            unfocused_border,\n            unfocused_locked_border,\n            stackbar_focused_text,\n            stackbar_unfocused_text,\n            stackbar_background,\n        ) = match theme {\n            KomorebiTheme::Catppuccin(Catppuccin {\n                name,\n                single_border,\n                stack_border,\n                monocle_border,\n                floating_border,\n                unfocused_border,\n                unfocused_locked_border,\n                stackbar_focused_text,\n                stackbar_unfocused_text,\n                stackbar_background,\n                ..\n            }) => {\n                let single_border = single_border\n                    .unwrap_or(komorebi_themes::CatppuccinValue::Blue)\n                    .color32(name.as_theme());\n\n                let stack_border = stack_border\n                    .unwrap_or(komorebi_themes::CatppuccinValue::Green)\n                    .color32(name.as_theme());\n\n                let monocle_border = monocle_border\n                    .unwrap_or(komorebi_themes::CatppuccinValue::Pink)\n                    .color32(name.as_theme());\n\n                let floating_border = floating_border\n                    .unwrap_or(komorebi_themes::CatppuccinValue::Yellow)\n                    .color32(name.as_theme());\n\n                let unfocused_border = unfocused_border\n                    .unwrap_or(komorebi_themes::CatppuccinValue::Base)\n                    .color32(name.as_theme());\n\n                let unfocused_locked_border = unfocused_locked_border\n                    .unwrap_or(komorebi_themes::CatppuccinValue::Red)\n                    .color32(name.as_theme());\n\n                let stackbar_focused_text = stackbar_focused_text\n                    .unwrap_or(komorebi_themes::CatppuccinValue::Green)\n                    .color32(name.as_theme());\n\n                let stackbar_unfocused_text = stackbar_unfocused_text\n                    .unwrap_or(komorebi_themes::CatppuccinValue::Text)\n                    .color32(name.as_theme());\n\n                let stackbar_background = stackbar_background\n                    .unwrap_or(komorebi_themes::CatppuccinValue::Base)\n                    .color32(name.as_theme());\n\n                (\n                    single_border,\n                    stack_border,\n                    monocle_border,\n                    floating_border,\n                    unfocused_border,\n                    unfocused_locked_border,\n                    stackbar_focused_text,\n                    stackbar_unfocused_text,\n                    stackbar_background,\n                )\n            }\n            KomorebiTheme::Base16(Base16 {\n                name,\n                single_border,\n                stack_border,\n                monocle_border,\n                floating_border,\n                unfocused_border,\n                unfocused_locked_border,\n                stackbar_focused_text,\n                stackbar_unfocused_text,\n                stackbar_background,\n                ..\n            }) => {\n                let single_border = single_border\n                    .unwrap_or(komorebi_themes::Base16Value::Base0D)\n                    .color32(Base16Wrapper::Base16(*name));\n\n                let stack_border = stack_border\n                    .unwrap_or(komorebi_themes::Base16Value::Base0B)\n                    .color32(Base16Wrapper::Base16(*name));\n\n                let monocle_border = monocle_border\n                    .unwrap_or(komorebi_themes::Base16Value::Base0F)\n                    .color32(Base16Wrapper::Base16(*name));\n\n                let unfocused_border = unfocused_border\n                    .unwrap_or(komorebi_themes::Base16Value::Base01)\n                    .color32(Base16Wrapper::Base16(*name));\n\n                let unfocused_locked_border = unfocused_locked_border\n                    .unwrap_or(komorebi_themes::Base16Value::Base08)\n                    .color32(Base16Wrapper::Base16(*name));\n\n                let floating_border = floating_border\n                    .unwrap_or(komorebi_themes::Base16Value::Base09)\n                    .color32(Base16Wrapper::Base16(*name));\n\n                let stackbar_focused_text = stackbar_focused_text\n                    .unwrap_or(komorebi_themes::Base16Value::Base0B)\n                    .color32(Base16Wrapper::Base16(*name));\n\n                let stackbar_unfocused_text = stackbar_unfocused_text\n                    .unwrap_or(komorebi_themes::Base16Value::Base05)\n                    .color32(Base16Wrapper::Base16(*name));\n\n                let stackbar_background = stackbar_background\n                    .unwrap_or(komorebi_themes::Base16Value::Base01)\n                    .color32(Base16Wrapper::Base16(*name));\n\n                (\n                    single_border,\n                    stack_border,\n                    monocle_border,\n                    floating_border,\n                    unfocused_border,\n                    unfocused_locked_border,\n                    stackbar_focused_text,\n                    stackbar_unfocused_text,\n                    stackbar_background,\n                )\n            }\n            KomorebiTheme::Custom(Custom {\n                colours,\n                single_border,\n                stack_border,\n                monocle_border,\n                floating_border,\n                unfocused_border,\n                unfocused_locked_border,\n                stackbar_focused_text,\n                stackbar_unfocused_text,\n                stackbar_background,\n                ..\n            }) => {\n                let single_border = single_border\n                    .unwrap_or(komorebi_themes::Base16Value::Base0D)\n                    .color32(Base16Wrapper::Custom(colours.clone()));\n\n                let stack_border = stack_border\n                    .unwrap_or(komorebi_themes::Base16Value::Base0B)\n                    .color32(Base16Wrapper::Custom(colours.clone()));\n\n                let monocle_border = monocle_border\n                    .unwrap_or(komorebi_themes::Base16Value::Base0F)\n                    .color32(Base16Wrapper::Custom(colours.clone()));\n\n                let unfocused_border = unfocused_border\n                    .unwrap_or(komorebi_themes::Base16Value::Base01)\n                    .color32(Base16Wrapper::Custom(colours.clone()));\n\n                let unfocused_locked_border = unfocused_locked_border\n                    .unwrap_or(komorebi_themes::Base16Value::Base08)\n                    .color32(Base16Wrapper::Custom(colours.clone()));\n\n                let floating_border = floating_border\n                    .unwrap_or(komorebi_themes::Base16Value::Base09)\n                    .color32(Base16Wrapper::Custom(colours.clone()));\n\n                let stackbar_focused_text = stackbar_focused_text\n                    .unwrap_or(komorebi_themes::Base16Value::Base0B)\n                    .color32(Base16Wrapper::Custom(colours.clone()));\n\n                let stackbar_unfocused_text = stackbar_unfocused_text\n                    .unwrap_or(komorebi_themes::Base16Value::Base05)\n                    .color32(Base16Wrapper::Custom(colours.clone()));\n\n                let stackbar_background = stackbar_background\n                    .unwrap_or(komorebi_themes::Base16Value::Base01)\n                    .color32(Base16Wrapper::Custom(colours.clone()));\n\n                (\n                    single_border,\n                    stack_border,\n                    monocle_border,\n                    floating_border,\n                    unfocused_border,\n                    unfocused_locked_border,\n                    stackbar_focused_text,\n                    stackbar_unfocused_text,\n                    stackbar_background,\n                )\n            }\n        };\n\n        border_manager::FOCUSED.store(u32::from(Colour::from(single_border)), Ordering::SeqCst);\n        border_manager::MONOCLE.store(u32::from(Colour::from(monocle_border)), Ordering::SeqCst);\n        border_manager::STACK.store(u32::from(Colour::from(stack_border)), Ordering::SeqCst);\n        border_manager::FLOATING.store(u32::from(Colour::from(floating_border)), Ordering::SeqCst);\n        border_manager::UNFOCUSED\n            .store(u32::from(Colour::from(unfocused_border)), Ordering::SeqCst);\n        border_manager::UNFOCUSED_LOCKED.store(\n            u32::from(Colour::from(unfocused_locked_border)),\n            Ordering::SeqCst,\n        );\n\n        STACKBAR_TAB_BACKGROUND_COLOUR.store(\n            u32::from(Colour::from(stackbar_background)),\n            Ordering::SeqCst,\n        );\n\n        STACKBAR_FOCUSED_TEXT_COLOUR.store(\n            u32::from(Colour::from(stackbar_focused_text)),\n            Ordering::SeqCst,\n        );\n\n        STACKBAR_UNFOCUSED_TEXT_COLOUR.store(\n            u32::from(Colour::from(stackbar_unfocused_text)),\n            Ordering::SeqCst,\n        );\n\n        CURRENT_THEME.store(Some(notification.0));\n\n        border_manager::send_force_update();\n        stackbar_manager::send_notification();\n    }\n\n    Ok(())\n}\n"
  },
  {
    "path": "komorebi/src/transparency_manager.rs",
    "content": "#![deny(clippy::unwrap_used, clippy::expect_used)]\n\nuse crossbeam_channel::Receiver;\nuse crossbeam_channel::Sender;\nuse crossbeam_utils::atomic::AtomicConsume;\nuse parking_lot::Mutex;\nuse std::sync::Arc;\nuse std::sync::OnceLock;\nuse std::sync::atomic::AtomicBool;\nuse std::sync::atomic::AtomicU8;\n\nuse crate::REGEX_IDENTIFIERS;\nuse crate::TRANSPARENCY_BLACKLIST;\nuse crate::Window;\nuse crate::WindowManager;\nuse crate::WindowsApi;\nuse crate::should_act;\n\npub static TRANSPARENCY_ENABLED: AtomicBool = AtomicBool::new(false);\npub static TRANSPARENCY_ALPHA: AtomicU8 = AtomicU8::new(200);\n\nstatic KNOWN_HWNDS: OnceLock<Mutex<Vec<isize>>> = OnceLock::new();\n\npub struct Notification;\n\nstatic CHANNEL: OnceLock<(Sender<Notification>, Receiver<Notification>)> = OnceLock::new();\n\npub fn known_hwnds() -> Vec<isize> {\n    let known = KNOWN_HWNDS.get_or_init(|| Mutex::new(Vec::new())).lock();\n    known.iter().copied().collect()\n}\n\npub fn channel() -> &'static (Sender<Notification>, Receiver<Notification>) {\n    CHANNEL.get_or_init(|| crossbeam_channel::bounded(20))\n}\n\nfn event_tx() -> Sender<Notification> {\n    channel().0.clone()\n}\n\nfn event_rx() -> Receiver<Notification> {\n    channel().1.clone()\n}\n\npub fn send_notification() {\n    if event_tx().try_send(Notification).is_err() {\n        tracing::warn!(\"channel is full; dropping notification\")\n    }\n}\n\npub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) {\n    std::thread::spawn(move || {\n        loop {\n            match handle_notifications(wm.clone()) {\n                Ok(()) => {\n                    tracing::warn!(\"restarting finished thread\");\n                }\n                Err(error) => {\n                    tracing::warn!(\"restarting failed thread: {}\", error);\n                }\n            }\n        }\n    });\n}\n\npub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result<()> {\n    tracing::info!(\"listening\");\n\n    let receiver = event_rx();\n    event_tx().send(Notification)?;\n\n    'receiver: for _ in receiver {\n        let known_hwnds = KNOWN_HWNDS.get_or_init(|| Mutex::new(Vec::new()));\n        if !TRANSPARENCY_ENABLED.load_consume() {\n            for hwnd in known_hwnds.lock().iter() {\n                if let Err(error) = Window::from(*hwnd).opaque() {\n                    tracing::error!(\"failed to make window {hwnd} opaque: {error}\")\n                }\n            }\n\n            continue 'receiver;\n        }\n\n        known_hwnds.lock().clear();\n\n        // Check the wm state every time we receive a notification\n        let state = wm.lock();\n\n        let focused_monitor_idx = state.focused_monitor_idx();\n\n        'monitors: for (monitor_idx, m) in state.monitors.elements().iter().enumerate() {\n            let focused_workspace_idx = m.focused_workspace_idx();\n\n            'workspaces: for (workspace_idx, ws) in m.workspaces().iter().enumerate() {\n                // Only operate on the focused workspace of each monitor\n                // Workspaces with tiling disabled don't have transparent windows\n                if !ws.tile || workspace_idx != focused_workspace_idx {\n                    for window in ws.visible_windows().iter().flatten() {\n                        if let Err(error) = window.opaque() {\n                            let hwnd = window.hwnd;\n                            tracing::error!(\"failed to make window {hwnd} opaque: {error}\")\n                        }\n                    }\n\n                    continue 'workspaces;\n                }\n\n                // Monocle container is never transparent\n                if let Some(monocle) = &ws.monocle_container {\n                    if let Some(window) = monocle.focused_window() {\n                        if monitor_idx == focused_monitor_idx {\n                            if let Err(error) = window.opaque() {\n                                let hwnd = window.hwnd;\n                                tracing::error!(\n                                    \"failed to make monocle window {hwnd} opaque: {error}\"\n                                )\n                            }\n                        } else if let Err(error) = window.transparent() {\n                            let hwnd = window.hwnd;\n                            tracing::error!(\n                                \"failed to make monocle window {hwnd} transparent: {error}\"\n                            )\n                        }\n                    }\n\n                    continue 'monitors;\n                }\n\n                let foreground_hwnd = WindowsApi::foreground_window().unwrap_or_default();\n                let is_maximized = WindowsApi::is_zoomed(foreground_hwnd);\n\n                if is_maximized {\n                    if let Err(error) = Window::from(foreground_hwnd).opaque() {\n                        let hwnd = foreground_hwnd;\n                        tracing::error!(\"failed to make maximized window {hwnd} opaque: {error}\")\n                    }\n\n                    continue 'monitors;\n                }\n\n                let transparency_blacklist = TRANSPARENCY_BLACKLIST.lock();\n                let regex_identifiers = REGEX_IDENTIFIERS.lock();\n\n                for (idx, c) in ws.containers().iter().enumerate() {\n                    // Update the transparency for all containers on this workspace\n\n                    // If the window is not focused on the current workspace, or isn't on the focused monitor\n                    // make it transparent\n                    #[allow(clippy::collapsible_else_if)]\n                    if idx != ws.focused_container_idx() || monitor_idx != focused_monitor_idx {\n                        let focused_window_idx = c.focused_window_idx();\n                        for (window_idx, window) in c.windows().iter().enumerate() {\n                            if window_idx == focused_window_idx {\n                                let mut should_make_transparent = true;\n                                if !transparency_blacklist.is_empty()\n                                    && let (Ok(title), Ok(exe_name), Ok(class), Ok(path)) = (\n                                        window.title(),\n                                        window.exe(),\n                                        window.class(),\n                                        window.path(),\n                                    )\n                                {\n                                    let is_blacklisted = should_act(\n                                        &title,\n                                        &exe_name,\n                                        &class,\n                                        &path,\n                                        &transparency_blacklist,\n                                        &regex_identifiers,\n                                    )\n                                    .is_some();\n\n                                    should_make_transparent = !is_blacklisted;\n                                }\n\n                                if should_make_transparent {\n                                    match window.transparent() {\n                                        Err(error) => {\n                                            let hwnd = foreground_hwnd;\n                                            tracing::error!(\n                                                \"failed to make unfocused window {hwnd} transparent: {error}\"\n                                            )\n                                        }\n                                        Ok(..) => {\n                                            known_hwnds.lock().push(window.hwnd);\n                                        }\n                                    }\n                                }\n                            } else {\n                                // just in case, this is useful when people are clicking around\n                                // on unfocused stackbar tabs\n                                known_hwnds.lock().push(window.hwnd);\n                            }\n                        }\n                    // Otherwise, make it opaque\n                    } else {\n                        let focused_window_idx = c.focused_window_idx();\n                        for (window_idx, window) in c.windows().iter().enumerate() {\n                            if window_idx != focused_window_idx {\n                                known_hwnds.lock().push(window.hwnd);\n                            } else {\n                                if let Err(error) =\n                                    c.focused_window().copied().unwrap_or_default().opaque()\n                                {\n                                    let hwnd = foreground_hwnd;\n                                    tracing::error!(\n                                        \"failed to make focused window {hwnd} opaque: {error}\"\n                                    )\n                                }\n                            }\n                        }\n                    };\n                }\n            }\n        }\n    }\n\n    Ok(())\n}\n"
  },
  {
    "path": "komorebi/src/window.rs",
    "content": "use crate::AnimationStyle;\nuse crate::FLOATING_APPLICATIONS;\nuse crate::FLOATING_WINDOW_TOGGLE_ASPECT_RATIO;\nuse crate::HIDDEN_HWNDS;\nuse crate::HIDING_BEHAVIOUR;\nuse crate::IGNORE_IDENTIFIERS;\nuse crate::LAYERED_WHITELIST;\nuse crate::MANAGE_IDENTIFIERS;\nuse crate::NO_TITLEBAR;\nuse crate::PERMAIGNORE_CLASSES;\nuse crate::REGEX_IDENTIFIERS;\nuse crate::SLOW_APPLICATION_COMPENSATION_TIME;\nuse crate::SLOW_APPLICATION_IDENTIFIERS;\nuse crate::WSL2_UI_PROCESSES;\nuse crate::animation::ANIMATION_DURATION_GLOBAL;\nuse crate::animation::ANIMATION_DURATION_PER_ANIMATION;\nuse crate::animation::ANIMATION_ENABLED_GLOBAL;\nuse crate::animation::ANIMATION_ENABLED_PER_ANIMATION;\nuse crate::animation::ANIMATION_MANAGER;\nuse crate::animation::ANIMATION_STYLE_GLOBAL;\nuse crate::animation::ANIMATION_STYLE_PER_ANIMATION;\nuse crate::animation::AnimationEngine;\nuse crate::animation::RenderDispatcher;\nuse crate::animation::lerp::Lerp;\nuse crate::animation::prefix::AnimationPrefix;\nuse crate::animation::prefix::new_animation_key;\nuse crate::border_manager;\nuse crate::com::SetCloak;\nuse crate::core::ApplicationIdentifier;\nuse crate::core::HidingBehaviour;\nuse crate::core::Rect;\nuse crate::core::config_generation::IdWithIdentifier;\nuse crate::core::config_generation::MatchingRule;\nuse crate::core::config_generation::MatchingStrategy;\nuse crate::focus_manager;\nuse crate::stackbar_manager;\nuse crate::styles::ExtendedWindowStyle;\nuse crate::styles::WindowStyle;\nuse crate::transparency_manager;\nuse crate::window_manager_event::WindowManagerEvent;\nuse crate::windows_api;\nuse crate::windows_api::WindowsApi;\nuse color_eyre::eyre;\nuse crossbeam_utils::atomic::AtomicConsume;\nuse regex::Regex;\nuse serde::Deserialize;\nuse serde::Serialize;\nuse serde::Serializer;\nuse serde::ser::SerializeStruct;\nuse std::collections::HashMap;\nuse std::convert::TryFrom;\nuse std::fmt::Display;\nuse std::fmt::Formatter;\nuse std::fmt::Write as _;\nuse std::sync::atomic::AtomicI32;\nuse std::sync::atomic::Ordering;\nuse std::thread;\nuse std::time::Duration;\nuse strum::Display;\nuse strum::EnumString;\nuse windows::Win32::Foundation::HWND;\n\npub static MINIMUM_WIDTH: AtomicI32 = AtomicI32::new(0);\npub static MINIMUM_HEIGHT: AtomicI32 = AtomicI32::new(0);\n\n#[derive(Debug, Default, Clone, Copy, Deserialize, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\npub struct Window {\n    pub hwnd: isize,\n}\n\nimpl From<isize> for Window {\n    fn from(value: isize) -> Self {\n        Self { hwnd: value }\n    }\n}\n\nimpl From<HWND> for Window {\n    fn from(value: HWND) -> Self {\n        Self {\n            hwnd: value.0 as isize,\n        }\n    }\n}\n\n#[allow(clippy::module_name_repetitions)]\n#[derive(Debug, Clone, Serialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\npub struct WindowDetails {\n    pub title: String,\n    pub exe: String,\n    pub class: String,\n}\n\nimpl TryFrom<Window> for WindowDetails {\n    type Error = eyre::ErrReport;\n\n    fn try_from(value: Window) -> std::result::Result<Self, Self::Error> {\n        Ok(Self {\n            title: value.title()?,\n            exe: value.exe()?,\n            class: value.class()?,\n        })\n    }\n}\n\nimpl Display for Window {\n    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {\n        let mut display = format!(\"(hwnd: {}\", self.hwnd);\n\n        if let Ok(title) = self.title() {\n            write!(display, \", title: {title}\")?;\n        }\n\n        if let Ok(exe) = self.exe() {\n            write!(display, \", exe: {exe}\")?;\n        }\n\n        if let Ok(class) = self.class() {\n            write!(display, \", class: {class}\")?;\n        }\n\n        write!(display, \")\")?;\n\n        write!(f, \"{display}\")\n    }\n}\n\nimpl Serialize for Window {\n    fn serialize<S>(&self, serializer: S) -> eyre::Result<S::Ok, S::Error>\n    where\n        S: Serializer,\n    {\n        let mut state = serializer.serialize_struct(\"Window\", 5)?;\n        state.serialize_field(\"hwnd\", &self.hwnd)?;\n        state.serialize_field(\n            \"title\",\n            &self\n                .title()\n                .unwrap_or_else(|_| String::from(\"could not get window title\")),\n        )?;\n        state.serialize_field(\n            \"exe\",\n            &self\n                .exe()\n                .unwrap_or_else(|_| String::from(\"could not get window exe\")),\n        )?;\n        state.serialize_field(\n            \"class\",\n            &self\n                .class()\n                .unwrap_or_else(|_| String::from(\"could not get window class\")),\n        )?;\n        state.serialize_field(\n            \"rect\",\n            &WindowsApi::window_rect(self.hwnd).unwrap_or_default(),\n        )?;\n        state.end()\n    }\n}\n\nstruct MovementRenderDispatcher {\n    hwnd: isize,\n    start_rect: Rect,\n    target_rect: Rect,\n    top: bool,\n    style: AnimationStyle,\n}\n\nimpl MovementRenderDispatcher {\n    const PREFIX: AnimationPrefix = AnimationPrefix::Movement;\n\n    pub fn new(\n        hwnd: isize,\n        start_rect: Rect,\n        target_rect: Rect,\n        top: bool,\n        style: AnimationStyle,\n    ) -> Self {\n        Self {\n            hwnd,\n            start_rect,\n            target_rect,\n            top,\n            style,\n        }\n    }\n}\n\nimpl RenderDispatcher for MovementRenderDispatcher {\n    fn get_animation_key(&self) -> String {\n        new_animation_key(MovementRenderDispatcher::PREFIX, self.hwnd.to_string())\n    }\n\n    fn pre_render(&self) -> eyre::Result<()> {\n        stackbar_manager::STACKBAR_TEMPORARILY_DISABLED.store(true, Ordering::SeqCst);\n        stackbar_manager::send_notification();\n\n        Ok(())\n    }\n\n    fn render(&self, progress: f64) -> eyre::Result<()> {\n        let new_rect = self.start_rect.lerp(self.target_rect, progress, self.style);\n\n        // we don't check WINDOW_HANDLING_BEHAVIOUR here because animations\n        // are always run on a separate thread\n        WindowsApi::move_window(self.hwnd, &new_rect, false)?;\n        WindowsApi::invalidate_rect(self.hwnd, None, false);\n\n        Ok(())\n    }\n\n    fn post_render(&self) -> eyre::Result<()> {\n        // we don't add the async_window_pos flag here because animations\n        // are always run on a separate thread\n        WindowsApi::position_window(self.hwnd, &self.target_rect, self.top, false)?;\n        if ANIMATION_MANAGER\n            .lock()\n            .count_in_progress(MovementRenderDispatcher::PREFIX)\n            == 0\n        {\n            if WindowsApi::foreground_window().unwrap_or_default() == self.hwnd {\n                focus_manager::send_notification(self.hwnd)\n            }\n\n            stackbar_manager::STACKBAR_TEMPORARILY_DISABLED.store(false, Ordering::SeqCst);\n\n            stackbar_manager::send_notification();\n            transparency_manager::send_notification();\n        }\n\n        Ok(())\n    }\n}\n\nstruct TransparencyRenderDispatcher {\n    hwnd: isize,\n    start_opacity: u8,\n    target_opacity: u8,\n    style: AnimationStyle,\n    is_opaque: bool,\n}\n\nimpl TransparencyRenderDispatcher {\n    const PREFIX: AnimationPrefix = AnimationPrefix::Transparency;\n\n    pub fn new(\n        hwnd: isize,\n        is_opaque: bool,\n        start_opacity: u8,\n        target_opacity: u8,\n        style: AnimationStyle,\n    ) -> Self {\n        Self {\n            hwnd,\n            start_opacity,\n            target_opacity,\n            style,\n            is_opaque,\n        }\n    }\n}\n\nimpl RenderDispatcher for TransparencyRenderDispatcher {\n    fn get_animation_key(&self) -> String {\n        new_animation_key(TransparencyRenderDispatcher::PREFIX, self.hwnd.to_string())\n    }\n\n    fn pre_render(&self) -> eyre::Result<()> {\n        //transparent\n        if !self.is_opaque {\n            let window = Window::from(self.hwnd);\n            let mut ex_style = window.ex_style()?;\n            ex_style.insert(ExtendedWindowStyle::LAYERED);\n            window.update_ex_style(&ex_style)?;\n        }\n\n        Ok(())\n    }\n\n    fn render(&self, progress: f64) -> eyre::Result<()> {\n        WindowsApi::set_transparent(\n            self.hwnd,\n            self.start_opacity\n                .lerp(self.target_opacity, progress, self.style),\n        )\n    }\n\n    fn post_render(&self) -> eyre::Result<()> {\n        //opaque\n        if self.is_opaque {\n            let window = Window::from(self.hwnd);\n            let mut ex_style = window.ex_style()?;\n            ex_style.remove(ExtendedWindowStyle::LAYERED);\n            window.update_ex_style(&ex_style)?;\n        }\n\n        Ok(())\n    }\n}\n\n#[derive(Copy, Clone, Debug, Display, EnumString, Serialize, Deserialize, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n#[serde(untagged)]\n/// Aspect ratio for temporarily floating windows\npub enum AspectRatio {\n    /// Predefined aspect ratio\n    #[cfg_attr(feature = \"schemars\", schemars(title = \"Predefined\"))]\n    Predefined(PredefinedAspectRatio),\n    /// Custom W:H aspect ratio\n    #[cfg_attr(feature = \"schemars\", schemars(title = \"Custom\"))]\n    Custom(i32, i32),\n}\n\nimpl Default for AspectRatio {\n    fn default() -> Self {\n        AspectRatio::Predefined(PredefinedAspectRatio::default())\n    }\n}\n\n#[derive(Copy, Clone, Debug, Default, Display, EnumString, Serialize, Deserialize, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Predefined aspect ratio\npub enum PredefinedAspectRatio {\n    /// 21:9\n    Ultrawide,\n    /// 16:9\n    Widescreen,\n    /// 4:3\n    #[default]\n    Standard,\n}\n\nimpl AspectRatio {\n    pub fn width_and_height(self) -> (i32, i32) {\n        match self {\n            AspectRatio::Predefined(predefined) => match predefined {\n                PredefinedAspectRatio::Ultrawide => (21, 9),\n                PredefinedAspectRatio::Widescreen => (16, 9),\n                PredefinedAspectRatio::Standard => (4, 3),\n            },\n            AspectRatio::Custom(w, h) => (w, h),\n        }\n    }\n}\n\nimpl Window {\n    pub const fn hwnd(self) -> HWND {\n        HWND(windows_api::as_ptr!(self.hwnd))\n    }\n\n    pub fn move_to_area(&mut self, current_area: &Rect, target_area: &Rect) -> eyre::Result<()> {\n        let current_rect = WindowsApi::window_rect(self.hwnd)?;\n        let x_diff = target_area.left - current_area.left;\n        let y_diff = target_area.top - current_area.top;\n        let x_ratio = f32::abs((target_area.right as f32) / (current_area.right as f32));\n        let y_ratio = f32::abs((target_area.bottom as f32) / (current_area.bottom as f32));\n        let window_relative_x = current_rect.left - current_area.left;\n        let window_relative_y = current_rect.top - current_area.top;\n        let corrected_relative_x = (window_relative_x as f32 * x_ratio) as i32;\n        let corrected_relative_y = (window_relative_y as f32 * y_ratio) as i32;\n        let window_x = current_area.left + corrected_relative_x;\n        let window_y = current_area.top + corrected_relative_y;\n        let left = x_diff + window_x;\n        let top = y_diff + window_y;\n\n        let corrected_width = (current_rect.right as f32 * x_ratio) as i32;\n        let corrected_height = (current_rect.bottom as f32 * y_ratio) as i32;\n\n        let new_rect = Rect {\n            left,\n            top,\n            right: corrected_width,\n            bottom: corrected_height,\n        };\n\n        let is_maximized = &new_rect == target_area;\n        if is_maximized {\n            windows_api::WindowsApi::unmaximize_window(self.hwnd);\n            let animation_enabled = ANIMATION_ENABLED_PER_ANIMATION.lock();\n            let move_enabled = animation_enabled\n                .get(&MovementRenderDispatcher::PREFIX)\n                .is_some_and(|v| *v);\n            drop(animation_enabled);\n\n            if move_enabled || ANIMATION_ENABLED_GLOBAL.load(Ordering::SeqCst) {\n                let anim_count = ANIMATION_MANAGER\n                    .lock()\n                    .count_in_progress(MovementRenderDispatcher::PREFIX);\n                self.set_position(&new_rect, true)?;\n                let hwnd = self.hwnd;\n                // Wait for the animation to finish before maximizing the window again, otherwise\n                // we would be maximizing the window on the current monitor anyway\n                thread::spawn(move || {\n                    let mut new_anim_count = ANIMATION_MANAGER\n                        .lock()\n                        .count_in_progress(MovementRenderDispatcher::PREFIX);\n                    let mut max_wait = 2000; // Max waiting time. No one will be using an animation longer than 2s, right? RIGHT??? WHY?\n                    while new_anim_count > anim_count && max_wait > 0 {\n                        thread::sleep(Duration::from_millis(10));\n                        new_anim_count = ANIMATION_MANAGER\n                            .lock()\n                            .count_in_progress(MovementRenderDispatcher::PREFIX);\n                        max_wait -= 1;\n                    }\n                    windows_api::WindowsApi::maximize_window(hwnd);\n                });\n            } else {\n                self.set_position(&new_rect, true)?;\n                windows_api::WindowsApi::maximize_window(self.hwnd);\n            }\n        } else {\n            self.set_position(&new_rect, true)?;\n        }\n\n        Ok(())\n    }\n\n    pub fn center(&mut self, work_area: &Rect, resize: bool) -> eyre::Result<()> {\n        let (target_width, target_height) = if resize {\n            let (aspect_ratio_width, aspect_ratio_height) = FLOATING_WINDOW_TOGGLE_ASPECT_RATIO\n                .lock()\n                .width_and_height();\n            let target_height = work_area.bottom / 2;\n            let target_width = (target_height * aspect_ratio_width) / aspect_ratio_height;\n            (target_width, target_height)\n        } else {\n            let current_rect = WindowsApi::window_rect(self.hwnd)?;\n            (current_rect.right, current_rect.bottom)\n        };\n\n        let x = work_area.left + ((work_area.right - target_width) / 2);\n        let y = work_area.top + ((work_area.bottom - target_height) / 2);\n\n        self.set_position(\n            &Rect {\n                left: x,\n                top: y,\n                right: target_width,\n                bottom: target_height,\n            },\n            true,\n        )\n    }\n\n    pub fn set_position(&self, layout: &Rect, top: bool) -> eyre::Result<()> {\n        let window_rect = WindowsApi::window_rect(self.hwnd)?;\n\n        if window_rect.eq(layout) {\n            return Ok(());\n        }\n\n        let animation_enabled = ANIMATION_ENABLED_PER_ANIMATION.lock();\n        let move_enabled = animation_enabled.get(&MovementRenderDispatcher::PREFIX);\n\n        if move_enabled.is_some_and(|enabled| *enabled)\n            || ANIMATION_ENABLED_GLOBAL.load(Ordering::SeqCst)\n        {\n            let duration = Duration::from_millis(\n                *ANIMATION_DURATION_PER_ANIMATION\n                    .lock()\n                    .get(&MovementRenderDispatcher::PREFIX)\n                    .unwrap_or(&ANIMATION_DURATION_GLOBAL.load(Ordering::SeqCst)),\n            );\n            let style = *ANIMATION_STYLE_PER_ANIMATION\n                .lock()\n                .get(&MovementRenderDispatcher::PREFIX)\n                .unwrap_or(&ANIMATION_STYLE_GLOBAL.lock());\n\n            let render_dispatcher =\n                MovementRenderDispatcher::new(self.hwnd, window_rect, *layout, top, style);\n\n            AnimationEngine::animate(render_dispatcher, duration)\n        } else {\n            WindowsApi::position_window(self.hwnd, layout, top, true)\n        }\n    }\n\n    pub fn is_maximized(self) -> bool {\n        WindowsApi::is_zoomed(self.hwnd)\n    }\n\n    pub fn is_miminized(self) -> bool {\n        WindowsApi::is_iconic(self.hwnd)\n    }\n\n    pub fn is_visible(self) -> bool {\n        WindowsApi::is_window_visible(self.hwnd)\n    }\n\n    pub fn hide_with_border(self, hide_border: bool) {\n        let mut programmatically_hidden_hwnds = HIDDEN_HWNDS.lock();\n        if !programmatically_hidden_hwnds.contains(&self.hwnd) {\n            programmatically_hidden_hwnds.push(self.hwnd);\n        }\n\n        let hiding_behaviour = HIDING_BEHAVIOUR.lock();\n\n        #[allow(deprecated)]\n        match *hiding_behaviour {\n            HidingBehaviour::Hide => WindowsApi::hide_window(self.hwnd),\n            HidingBehaviour::Minimize => WindowsApi::minimize_window(self.hwnd),\n            HidingBehaviour::Cloak => SetCloak(self.hwnd(), 1, 2),\n        }\n        if hide_border {\n            border_manager::hide_border(self.hwnd);\n        }\n    }\n\n    pub fn hide(self) {\n        self.hide_with_border(true);\n    }\n\n    pub fn restore_with_border(self, restore_border: bool) {\n        let mut programmatically_hidden_hwnds = HIDDEN_HWNDS.lock();\n        if let Some(idx) = programmatically_hidden_hwnds\n            .iter()\n            .position(|&hwnd| hwnd == self.hwnd)\n        {\n            programmatically_hidden_hwnds.remove(idx);\n        }\n\n        let hiding_behaviour = HIDING_BEHAVIOUR.lock();\n\n        #[allow(deprecated)]\n        match *hiding_behaviour {\n            HidingBehaviour::Hide | HidingBehaviour::Minimize => {\n                WindowsApi::restore_window(self.hwnd);\n            }\n            HidingBehaviour::Cloak => SetCloak(self.hwnd(), 1, 0),\n        }\n        if restore_border {\n            border_manager::show_border(self.hwnd);\n        }\n    }\n\n    pub fn restore(self) {\n        self.restore_with_border(true);\n    }\n\n    pub fn minimize(self) {\n        let exe = self.exe().unwrap_or_default();\n        if !exe.contains(\"komorebi-bar\") {\n            WindowsApi::minimize_window(self.hwnd);\n        }\n    }\n\n    pub fn close(self) -> eyre::Result<()> {\n        WindowsApi::close_window(self.hwnd)\n    }\n\n    pub fn maximize(self) {\n        let mut programmatically_hidden_hwnds = HIDDEN_HWNDS.lock();\n        if let Some(idx) = programmatically_hidden_hwnds\n            .iter()\n            .position(|&hwnd| hwnd == self.hwnd)\n        {\n            programmatically_hidden_hwnds.remove(idx);\n        }\n\n        WindowsApi::maximize_window(self.hwnd);\n    }\n\n    pub fn unmaximize(self) {\n        let mut programmatically_hidden_hwnds = HIDDEN_HWNDS.lock();\n        if let Some(idx) = programmatically_hidden_hwnds\n            .iter()\n            .position(|&hwnd| hwnd == self.hwnd)\n        {\n            programmatically_hidden_hwnds.remove(idx);\n        }\n\n        WindowsApi::unmaximize_window(self.hwnd);\n    }\n\n    pub fn focus(self, mouse_follows_focus: bool) -> eyre::Result<()> {\n        // If the target window is already focused, do nothing.\n        if let Ok(ihwnd) = WindowsApi::foreground_window()\n            && ihwnd == self.hwnd\n        {\n            // Center cursor in Window\n            if mouse_follows_focus {\n                WindowsApi::center_cursor_in_rect(&WindowsApi::window_rect(self.hwnd)?)?;\n            }\n\n            return Ok(());\n        }\n\n        WindowsApi::raise_and_focus_window(self.hwnd)?;\n\n        // Center cursor in Window\n        if mouse_follows_focus {\n            WindowsApi::center_cursor_in_rect(&WindowsApi::window_rect(self.hwnd)?)?;\n        }\n\n        Ok(())\n    }\n\n    pub fn is_focused(self) -> bool {\n        WindowsApi::foreground_window().unwrap_or_default() == self.hwnd\n    }\n\n    pub fn transparent(self) -> eyre::Result<()> {\n        let animation_enabled = ANIMATION_ENABLED_PER_ANIMATION.lock();\n        let transparent_enabled = animation_enabled.get(&TransparencyRenderDispatcher::PREFIX);\n\n        if transparent_enabled.is_some_and(|enabled| *enabled)\n            || ANIMATION_ENABLED_GLOBAL.load(Ordering::SeqCst)\n        {\n            let duration = Duration::from_millis(\n                *ANIMATION_DURATION_PER_ANIMATION\n                    .lock()\n                    .get(&TransparencyRenderDispatcher::PREFIX)\n                    .unwrap_or(&ANIMATION_DURATION_GLOBAL.load(Ordering::SeqCst)),\n            );\n            let style = *ANIMATION_STYLE_PER_ANIMATION\n                .lock()\n                .get(&TransparencyRenderDispatcher::PREFIX)\n                .unwrap_or(&ANIMATION_STYLE_GLOBAL.lock());\n\n            let render_dispatcher = TransparencyRenderDispatcher::new(\n                self.hwnd,\n                false,\n                WindowsApi::get_transparent(self.hwnd).unwrap_or(255),\n                transparency_manager::TRANSPARENCY_ALPHA.load_consume(),\n                style,\n            );\n\n            AnimationEngine::animate(render_dispatcher, duration)\n        } else {\n            let mut ex_style = self.ex_style()?;\n            ex_style.insert(ExtendedWindowStyle::LAYERED);\n            self.update_ex_style(&ex_style)?;\n            WindowsApi::set_transparent(\n                self.hwnd,\n                transparency_manager::TRANSPARENCY_ALPHA.load_consume(),\n            )\n        }\n    }\n\n    pub fn opaque(self) -> eyre::Result<()> {\n        let animation_enabled = ANIMATION_ENABLED_PER_ANIMATION.lock();\n        let transparent_enabled = animation_enabled.get(&TransparencyRenderDispatcher::PREFIX);\n\n        if transparent_enabled.is_some_and(|enabled| *enabled)\n            || ANIMATION_ENABLED_GLOBAL.load(Ordering::SeqCst)\n        {\n            let duration = Duration::from_millis(\n                *ANIMATION_DURATION_PER_ANIMATION\n                    .lock()\n                    .get(&TransparencyRenderDispatcher::PREFIX)\n                    .unwrap_or(&ANIMATION_DURATION_GLOBAL.load(Ordering::SeqCst)),\n            );\n            let style = *ANIMATION_STYLE_PER_ANIMATION\n                .lock()\n                .get(&TransparencyRenderDispatcher::PREFIX)\n                .unwrap_or(&ANIMATION_STYLE_GLOBAL.lock());\n\n            let render_dispatcher = TransparencyRenderDispatcher::new(\n                self.hwnd,\n                true,\n                WindowsApi::get_transparent(self.hwnd)\n                    .unwrap_or(transparency_manager::TRANSPARENCY_ALPHA.load_consume()),\n                255,\n                style,\n            );\n\n            AnimationEngine::animate(render_dispatcher, duration)\n        } else {\n            let mut ex_style = self.ex_style()?;\n            ex_style.remove(ExtendedWindowStyle::LAYERED);\n            self.update_ex_style(&ex_style)\n        }\n    }\n\n    pub fn set_accent(self, colour: u32) -> eyre::Result<()> {\n        WindowsApi::set_window_accent(self.hwnd, Some(colour))\n    }\n\n    pub fn remove_accent(self) -> eyre::Result<()> {\n        WindowsApi::set_window_accent(self.hwnd, None)\n    }\n\n    #[cfg(target_pointer_width = \"64\")]\n    pub fn update_style(self, style: &WindowStyle) -> eyre::Result<()> {\n        WindowsApi::update_style(self.hwnd, isize::try_from(style.bits())?)\n    }\n\n    #[cfg(target_pointer_width = \"32\")]\n    pub fn update_style(self, style: &WindowStyle) -> eyre::Result<()> {\n        WindowsApi::update_style(self.hwnd, i32::try_from(style.bits())?)\n    }\n\n    #[cfg(target_pointer_width = \"64\")]\n    pub fn update_ex_style(self, style: &ExtendedWindowStyle) -> eyre::Result<()> {\n        WindowsApi::update_ex_style(self.hwnd, isize::try_from(style.bits())?)\n    }\n\n    #[cfg(target_pointer_width = \"32\")]\n    pub fn update_ex_style(self, style: &ExtendedWindowStyle) -> eyre::Result<()> {\n        WindowsApi::update_ex_style(self.hwnd, i32::try_from(style.bits())?)\n    }\n\n    pub fn style(self) -> eyre::Result<WindowStyle> {\n        let bits = u32::try_from(WindowsApi::gwl_style(self.hwnd)?)?;\n        Ok(WindowStyle::from_bits_truncate(bits))\n    }\n\n    pub fn ex_style(self) -> eyre::Result<ExtendedWindowStyle> {\n        let bits = u32::try_from(WindowsApi::gwl_ex_style(self.hwnd)?)?;\n        Ok(ExtendedWindowStyle::from_bits_truncate(bits))\n    }\n\n    pub fn title(self) -> eyre::Result<String> {\n        WindowsApi::window_text_w(self.hwnd)\n    }\n\n    pub fn path(self) -> eyre::Result<String> {\n        let (process_id, _) = WindowsApi::window_thread_process_id(self.hwnd);\n        let handle = WindowsApi::process_handle(process_id)?;\n        let path = WindowsApi::exe_path(handle);\n        WindowsApi::close_process(handle)?;\n        path\n    }\n\n    pub fn exe(self) -> eyre::Result<String> {\n        let (process_id, _) = WindowsApi::window_thread_process_id(self.hwnd);\n        let handle = WindowsApi::process_handle(process_id)?;\n        let exe = WindowsApi::exe(handle);\n        WindowsApi::close_process(handle)?;\n        exe\n    }\n\n    pub fn process_id(self) -> u32 {\n        let (process_id, _) = WindowsApi::window_thread_process_id(self.hwnd);\n        process_id\n    }\n\n    pub fn class(self) -> eyre::Result<String> {\n        WindowsApi::real_window_class_w(self.hwnd)\n    }\n\n    pub fn is_cloaked(self) -> eyre::Result<bool> {\n        WindowsApi::is_window_cloaked(self.hwnd)\n    }\n\n    pub fn is_window(self) -> bool {\n        WindowsApi::is_window(self.hwnd)\n    }\n\n    pub fn remove_title_bar(self) -> eyre::Result<()> {\n        let mut style = self.style()?;\n        style.remove(WindowStyle::CAPTION);\n        style.remove(WindowStyle::THICKFRAME);\n        self.update_style(&style)\n    }\n\n    pub fn add_title_bar(self) -> eyre::Result<()> {\n        let mut style = self.style()?;\n        style.insert(WindowStyle::CAPTION);\n        style.insert(WindowStyle::THICKFRAME);\n        self.update_style(&style)\n    }\n\n    /// Raise the window to the top of the Z order, but do not activate or focus\n    /// it. Use raise_and_focus_window to activate and focus a window.\n    /// It also checks if there is a border attached to this window and if it is\n    /// it raises it as well.\n    pub fn raise(self) -> eyre::Result<()> {\n        WindowsApi::raise_window(self.hwnd)?;\n        if let Some(border_info) = crate::border_manager::window_border(self.hwnd) {\n            WindowsApi::raise_window(border_info.border_hwnd)?;\n        }\n        Ok(())\n    }\n\n    /// Lower the window to the bottom of the Z order, but do not activate or focus\n    /// it.\n    /// It also checks if there is a border attached to this window and if it is\n    /// it lowers it as well.\n    pub fn lower(self) -> eyre::Result<()> {\n        WindowsApi::lower_window(self.hwnd)?;\n        if let Some(border_info) = crate::border_manager::window_border(self.hwnd) {\n            WindowsApi::lower_window(border_info.border_hwnd)?;\n        }\n        Ok(())\n    }\n\n    #[tracing::instrument(fields(exe, title), skip(debug))]\n    pub fn should_manage(\n        self,\n        event: Option<WindowManagerEvent>,\n        debug: &mut RuleDebug,\n    ) -> eyre::Result<bool> {\n        if !self.is_window() {\n            return Ok(false);\n        }\n\n        debug.is_window = true;\n\n        let rect = WindowsApi::window_rect(self.hwnd).unwrap_or_default();\n\n        if rect.right < MINIMUM_WIDTH.load(Ordering::SeqCst) {\n            return Ok(false);\n        }\n\n        debug.has_minimum_width = true;\n\n        if rect.bottom < MINIMUM_HEIGHT.load(Ordering::SeqCst) {\n            return Ok(false);\n        }\n\n        debug.has_minimum_height = true;\n\n        if self.title().is_err() {\n            return Ok(false);\n        }\n\n        debug.has_title = true;\n\n        let is_cloaked = self.is_cloaked().unwrap_or_default();\n\n        debug.is_cloaked = is_cloaked;\n\n        let mut allow_cloaked = false;\n\n        if let Some(event) = event\n            && matches!(\n                event,\n                WindowManagerEvent::Hide(_, _) | WindowManagerEvent::Cloak(_, _)\n            )\n        {\n            allow_cloaked = true;\n        }\n\n        debug.allow_cloaked = allow_cloaked;\n\n        match (allow_cloaked, is_cloaked) {\n            // If allowing cloaked windows, we don't need to check the cloaked status\n            (true, _) |\n            // If not allowing cloaked windows, we need to ensure the window is not cloaked\n            (false, false) => {\n                if let (Ok(title), Ok(exe_name), Ok(class), Ok(path)) = (self.title(), self.exe(), self.class(), self.path()) {\n                    debug.title = Some(title.clone());\n                    debug.exe_name = Some(exe_name.clone());\n                    debug.class = Some(class.clone());\n                    debug.path = Some(path.clone());\n                    // calls for styles can fail quite often for events with windows that aren't really \"windows\"\n                    // since we have moved up calls of should_manage to the beginning of the process_event handler,\n                    // we should handle failures here gracefully to be able to continue the execution of process_event\n                    if let (Ok(style), Ok(ex_style)) = (&self.style(), &self.ex_style()) {\n                        debug.window_style = Some(*style);\n                        debug.extended_window_style = Some(*ex_style);\n                        let eligible = window_is_eligible(self.hwnd, &title, &exe_name, &class, &path, style, ex_style, event, debug);\n                        debug.should_manage = eligible;\n                        return Ok(eligible);\n                    }\n                }\n            }\n            _ => {}\n        }\n\n        Ok(false)\n    }\n}\n\n#[derive(Debug, Default, Serialize, Deserialize)]\npub struct RuleDebug {\n    pub should_manage: bool,\n    pub is_window: bool,\n    pub has_minimum_width: bool,\n    pub has_minimum_height: bool,\n    pub has_title: bool,\n    pub is_cloaked: bool,\n    pub allow_cloaked: bool,\n    pub allow_layered_transparency: bool,\n    pub window_style: Option<WindowStyle>,\n    pub extended_window_style: Option<ExtendedWindowStyle>,\n    pub title: Option<String>,\n    pub exe_name: Option<String>,\n    pub class: Option<String>,\n    pub path: Option<String>,\n    pub matches_permaignore_class: Option<String>,\n    pub matches_ignore_identifier: Option<MatchingRule>,\n    pub matches_managed_override: Option<MatchingRule>,\n    pub matches_layered_whitelist: Option<MatchingRule>,\n    pub matches_floating_applications: Option<MatchingRule>,\n    pub matches_wsl2_gui: Option<String>,\n    pub matches_no_titlebar: Option<MatchingRule>,\n}\n\n#[allow(clippy::too_many_arguments)]\nfn window_is_eligible(\n    hwnd: isize,\n    title: &String,\n    exe_name: &String,\n    class: &String,\n    path: &str,\n    style: &WindowStyle,\n    ex_style: &ExtendedWindowStyle,\n    event: Option<WindowManagerEvent>,\n    debug: &mut RuleDebug,\n) -> bool {\n    {\n        let permaignore_classes = PERMAIGNORE_CLASSES.lock();\n        if permaignore_classes.contains(class) {\n            debug.matches_permaignore_class = Some(class.clone());\n            return false;\n        }\n    }\n\n    let regex_identifiers = REGEX_IDENTIFIERS.lock();\n\n    let ignore_identifiers = IGNORE_IDENTIFIERS.lock();\n    let should_ignore = if let Some(rule) = should_act(\n        title,\n        exe_name,\n        class,\n        path,\n        &ignore_identifiers,\n        &regex_identifiers,\n    ) {\n        debug.matches_ignore_identifier = Some(rule);\n        true\n    } else {\n        false\n    };\n\n    let manage_identifiers = MANAGE_IDENTIFIERS.lock();\n    let managed_override = if let Some(rule) = should_act(\n        title,\n        exe_name,\n        class,\n        path,\n        &manage_identifiers,\n        &regex_identifiers,\n    ) {\n        debug.matches_managed_override = Some(rule);\n        true\n    } else {\n        false\n    };\n\n    let floating_identifiers = FLOATING_APPLICATIONS.lock();\n    if let Some(rule) = should_act(\n        title,\n        exe_name,\n        class,\n        path,\n        &floating_identifiers,\n        &regex_identifiers,\n    ) {\n        debug.matches_floating_applications = Some(rule);\n    }\n\n    if should_ignore && !managed_override {\n        return false;\n    }\n\n    let layered_whitelist = LAYERED_WHITELIST.lock();\n    let mut allow_layered = if let Some(rule) = should_act(\n        title,\n        exe_name,\n        class,\n        path,\n        &layered_whitelist,\n        &regex_identifiers,\n    ) {\n        debug.matches_layered_whitelist = Some(rule);\n        true\n    } else {\n        false\n    };\n\n    let known_layered_hwnds = transparency_manager::known_hwnds();\n\n    allow_layered = if known_layered_hwnds.contains(&hwnd)\n        // we always want to process hide events for windows with transparency, even on other\n        // monitors, because we don't want to be left with ghost tiles\n        || matches!(event, Some(WindowManagerEvent::Hide(_, _)))\n    {\n        debug.allow_layered_transparency = true;\n        true\n    } else {\n        allow_layered\n    };\n\n    let allow_wsl2_gui = {\n        let wsl2_ui_processes = WSL2_UI_PROCESSES.lock();\n        let allow = wsl2_ui_processes.contains(exe_name);\n        if allow {\n            debug.matches_wsl2_gui = Some(exe_name.clone())\n        }\n\n        allow\n    };\n\n    let titlebars_removed = NO_TITLEBAR.lock();\n    let allow_titlebar_removed = if let Some(rule) = should_act(\n        title,\n        exe_name,\n        class,\n        path,\n        &titlebars_removed,\n        &regex_identifiers,\n    ) {\n        debug.matches_no_titlebar = Some(rule);\n        true\n    } else {\n        false\n    };\n\n    {\n        let slow_application_identifiers = SLOW_APPLICATION_IDENTIFIERS.lock();\n        let should_sleep = should_act(\n            title,\n            exe_name,\n            class,\n            path,\n            &slow_application_identifiers,\n            &regex_identifiers,\n        )\n        .is_some();\n\n        if should_sleep {\n            std::thread::sleep(Duration::from_millis(\n                SLOW_APPLICATION_COMPENSATION_TIME.load(Ordering::SeqCst),\n            ));\n        }\n    }\n\n    if (allow_wsl2_gui || allow_titlebar_removed || style.contains(WindowStyle::CAPTION) && ex_style.contains(ExtendedWindowStyle::WINDOWEDGE))\n        && !ex_style.contains(ExtendedWindowStyle::DLGMODALFRAME)\n        // Get a lot of dupe events coming through that make the redrawing go crazy\n        // on FocusChange events if I don't filter out this one. But, if we are\n        // allowing a specific layered window on the whitelist (like Steam), it should\n        // pass this check\n        && (allow_layered || !ex_style.contains(ExtendedWindowStyle::LAYERED))\n        || managed_override\n    {\n        return true;\n    } else if let Some(event) = event {\n        tracing::debug!(\n            \"ignoring (exe: {}, title: {}, event: {})\",\n            exe_name,\n            title,\n            event\n        );\n    }\n\n    false\n}\n\n#[allow(clippy::cognitive_complexity, clippy::too_many_lines)]\npub fn should_act(\n    title: &str,\n    exe_name: &str,\n    class: &str,\n    path: &str,\n    identifiers: &[MatchingRule],\n    regex_identifiers: &HashMap<String, Regex>,\n) -> Option<MatchingRule> {\n    let mut matching_rule = None;\n    for rule in identifiers {\n        match rule {\n            MatchingRule::Simple(identifier) => {\n                if should_act_individual(\n                    title,\n                    exe_name,\n                    class,\n                    path,\n                    identifier,\n                    regex_identifiers,\n                ) {\n                    matching_rule = Some(rule.clone());\n                };\n            }\n            MatchingRule::Composite(identifiers) => {\n                let mut composite_results = vec![];\n                for identifier in identifiers {\n                    composite_results.push(should_act_individual(\n                        title,\n                        exe_name,\n                        class,\n                        path,\n                        identifier,\n                        regex_identifiers,\n                    ));\n                }\n\n                if composite_results.iter().all(|&x| x) {\n                    matching_rule = Some(rule.clone());\n                }\n            }\n        }\n    }\n\n    matching_rule\n}\n\npub fn should_act_individual(\n    title: &str,\n    exe_name: &str,\n    class: &str,\n    path: &str,\n    identifier: &IdWithIdentifier,\n    regex_identifiers: &HashMap<String, Regex>,\n) -> bool {\n    let mut should_act = false;\n\n    match identifier.matching_strategy {\n        None | Some(MatchingStrategy::Legacy) => match identifier.kind {\n            ApplicationIdentifier::Title => {\n                if title.starts_with(&identifier.id) || title.ends_with(&identifier.id) {\n                    should_act = true;\n                }\n            }\n            ApplicationIdentifier::Class => {\n                if class.starts_with(&identifier.id) || class.ends_with(&identifier.id) {\n                    should_act = true;\n                }\n            }\n            ApplicationIdentifier::Exe => {\n                if exe_name.eq(&identifier.id) {\n                    should_act = true;\n                }\n            }\n            ApplicationIdentifier::Path => {\n                if path.eq(&identifier.id) {\n                    should_act = true;\n                }\n            }\n        },\n        Some(MatchingStrategy::Equals) => match identifier.kind {\n            ApplicationIdentifier::Title => {\n                if title.eq(&identifier.id) {\n                    should_act = true;\n                }\n            }\n            ApplicationIdentifier::Class => {\n                if class.eq(&identifier.id) {\n                    should_act = true;\n                }\n            }\n            ApplicationIdentifier::Exe => {\n                if exe_name.eq(&identifier.id) {\n                    should_act = true;\n                }\n            }\n            ApplicationIdentifier::Path => {\n                if path.eq(&identifier.id) {\n                    should_act = true;\n                }\n            }\n        },\n        Some(MatchingStrategy::DoesNotEqual) => match identifier.kind {\n            ApplicationIdentifier::Title => {\n                if !title.eq(&identifier.id) {\n                    should_act = true;\n                }\n            }\n            ApplicationIdentifier::Class => {\n                if !class.eq(&identifier.id) {\n                    should_act = true;\n                }\n            }\n            ApplicationIdentifier::Exe => {\n                if !exe_name.eq(&identifier.id) {\n                    should_act = true;\n                }\n            }\n            ApplicationIdentifier::Path => {\n                if !path.eq(&identifier.id) {\n                    should_act = true;\n                }\n            }\n        },\n        Some(MatchingStrategy::StartsWith) => match identifier.kind {\n            ApplicationIdentifier::Title => {\n                if title.starts_with(&identifier.id) {\n                    should_act = true;\n                }\n            }\n            ApplicationIdentifier::Class => {\n                if class.starts_with(&identifier.id) {\n                    should_act = true;\n                }\n            }\n            ApplicationIdentifier::Exe => {\n                if exe_name.starts_with(&identifier.id) {\n                    should_act = true;\n                }\n            }\n            ApplicationIdentifier::Path => {\n                if path.starts_with(&identifier.id) {\n                    should_act = true;\n                }\n            }\n        },\n        Some(MatchingStrategy::DoesNotStartWith) => match identifier.kind {\n            ApplicationIdentifier::Title => {\n                if !title.starts_with(&identifier.id) {\n                    should_act = true;\n                }\n            }\n            ApplicationIdentifier::Class => {\n                if !class.starts_with(&identifier.id) {\n                    should_act = true;\n                }\n            }\n            ApplicationIdentifier::Exe => {\n                if !exe_name.starts_with(&identifier.id) {\n                    should_act = true;\n                }\n            }\n            ApplicationIdentifier::Path => {\n                if !path.starts_with(&identifier.id) {\n                    should_act = true;\n                }\n            }\n        },\n        Some(MatchingStrategy::EndsWith) => match identifier.kind {\n            ApplicationIdentifier::Title => {\n                if title.ends_with(&identifier.id) {\n                    should_act = true;\n                }\n            }\n            ApplicationIdentifier::Class => {\n                if class.ends_with(&identifier.id) {\n                    should_act = true;\n                }\n            }\n            ApplicationIdentifier::Exe => {\n                if exe_name.ends_with(&identifier.id) {\n                    should_act = true;\n                }\n            }\n            ApplicationIdentifier::Path => {\n                if path.ends_with(&identifier.id) {\n                    should_act = true;\n                }\n            }\n        },\n        Some(MatchingStrategy::DoesNotEndWith) => match identifier.kind {\n            ApplicationIdentifier::Title => {\n                if !title.ends_with(&identifier.id) {\n                    should_act = true;\n                }\n            }\n            ApplicationIdentifier::Class => {\n                if !class.ends_with(&identifier.id) {\n                    should_act = true;\n                }\n            }\n            ApplicationIdentifier::Exe => {\n                if !exe_name.ends_with(&identifier.id) {\n                    should_act = true;\n                }\n            }\n            ApplicationIdentifier::Path => {\n                if !path.ends_with(&identifier.id) {\n                    should_act = true;\n                }\n            }\n        },\n        Some(MatchingStrategy::Contains) => match identifier.kind {\n            ApplicationIdentifier::Title => {\n                if title.contains(&identifier.id) {\n                    should_act = true;\n                }\n            }\n            ApplicationIdentifier::Class => {\n                if class.contains(&identifier.id) {\n                    should_act = true;\n                }\n            }\n            ApplicationIdentifier::Exe => {\n                if exe_name.contains(&identifier.id) {\n                    should_act = true;\n                }\n            }\n            ApplicationIdentifier::Path => {\n                if path.contains(&identifier.id) {\n                    should_act = true;\n                }\n            }\n        },\n        Some(MatchingStrategy::DoesNotContain) => match identifier.kind {\n            ApplicationIdentifier::Title => {\n                if !title.contains(&identifier.id) {\n                    should_act = true;\n                }\n            }\n            ApplicationIdentifier::Class => {\n                if !class.contains(&identifier.id) {\n                    should_act = true;\n                }\n            }\n            ApplicationIdentifier::Exe => {\n                if !exe_name.contains(&identifier.id) {\n                    should_act = true;\n                }\n            }\n            ApplicationIdentifier::Path => {\n                if !path.contains(&identifier.id) {\n                    should_act = true;\n                }\n            }\n        },\n        Some(MatchingStrategy::Regex) => match identifier.kind {\n            ApplicationIdentifier::Title => {\n                if let Some(re) = regex_identifiers.get(&identifier.id)\n                    && re.is_match(title)\n                {\n                    should_act = true;\n                }\n            }\n            ApplicationIdentifier::Class => {\n                if let Some(re) = regex_identifiers.get(&identifier.id)\n                    && re.is_match(class)\n                {\n                    should_act = true;\n                }\n            }\n            ApplicationIdentifier::Exe => {\n                if let Some(re) = regex_identifiers.get(&identifier.id)\n                    && re.is_match(exe_name)\n                {\n                    should_act = true;\n                }\n            }\n            ApplicationIdentifier::Path => {\n                if let Some(re) = regex_identifiers.get(&identifier.id)\n                    && re.is_match(path)\n                {\n                    should_act = true;\n                }\n            }\n        },\n    }\n\n    should_act\n}\n"
  },
  {
    "path": "komorebi/src/window_manager.rs",
    "content": "use std::collections::HashMap;\nuse std::collections::HashSet;\nuse std::collections::VecDeque;\nuse std::env::temp_dir;\nuse std::fs::OpenOptions;\nuse std::io::ErrorKind;\nuse std::net::Shutdown;\nuse std::num::NonZeroUsize;\nuse std::path::Path;\nuse std::path::PathBuf;\nuse std::sync::Arc;\nuse std::sync::atomic::Ordering;\n\nuse color_eyre::eyre;\nuse color_eyre::eyre::OptionExt;\nuse color_eyre::eyre::bail;\nuse crossbeam_channel::Receiver;\nuse hotwatch::EventKind;\nuse hotwatch::Hotwatch;\nuse hotwatch::notify::ErrorKind as NotifyErrorKind;\nuse parking_lot::Mutex;\nuse uds_windows::UnixListener;\nuse uds_windows::UnixStream;\n\nuse crate::animation::ANIMATION_ENABLED_GLOBAL;\nuse crate::animation::ANIMATION_ENABLED_PER_ANIMATION;\nuse crate::animation::AnimationEngine;\nuse crate::core::Arrangement;\nuse crate::core::Axis;\nuse crate::core::BorderImplementation;\nuse crate::core::CustomLayout;\nuse crate::core::CycleDirection;\nuse crate::core::DefaultLayout;\nuse crate::core::FocusFollowsMouseImplementation;\nuse crate::core::Layout;\nuse crate::core::MoveBehaviour;\nuse crate::core::OperationBehaviour;\nuse crate::core::OperationDirection;\nuse crate::core::Rect;\nuse crate::core::Sizing;\nuse crate::core::WindowContainerBehaviour;\nuse crate::core::WindowManagementBehaviour;\nuse crate::core::config_generation::MatchingRule;\n\nuse crate::CrossBoundaryBehaviour;\nuse crate::DATA_DIR;\nuse crate::HOME_DIR;\nuse crate::NO_TITLEBAR;\nuse crate::REGEX_IDENTIFIERS;\nuse crate::SUBSCRIPTION_SOCKETS;\nuse crate::WORKSPACE_MATCHING_RULES;\nuse crate::border_manager;\nuse crate::border_manager::BORDER_OFFSET;\nuse crate::border_manager::BORDER_WIDTH;\nuse crate::container::Container;\nuse crate::current_virtual_desktop;\nuse crate::load_configuration;\nuse crate::monitor::Monitor;\nuse crate::ring::Ring;\nuse crate::should_act;\nuse crate::should_act_individual;\nuse crate::state::State;\nuse crate::static_config::StaticConfig;\nuse crate::transparency_manager;\nuse crate::window::Window;\nuse crate::window_manager_event::WindowManagerEvent;\nuse crate::windows_api::WindowsApi;\nuse crate::winevent_listener;\nuse crate::workspace::Workspace;\nuse crate::workspace::WorkspaceLayer;\n\n#[derive(Debug)]\npub struct WindowManager {\n    pub monitors: Ring<Monitor>,\n    pub monitor_usr_idx_map: HashMap<usize, usize>,\n    pub incoming_events: Receiver<WindowManagerEvent>,\n    pub command_listener: UnixListener,\n    pub is_paused: bool,\n    pub work_area_offset: Option<Rect>,\n    pub resize_delta: i32,\n    pub window_management_behaviour: WindowManagementBehaviour,\n    pub cross_monitor_move_behaviour: MoveBehaviour,\n    pub cross_boundary_behaviour: CrossBoundaryBehaviour,\n    pub unmanaged_window_operation_behaviour: OperationBehaviour,\n    pub focus_follows_mouse: Option<FocusFollowsMouseImplementation>,\n    pub mouse_follows_focus: bool,\n    pub hotwatch: Hotwatch,\n    pub virtual_desktop_id: Option<Vec<u8>>,\n    pub has_pending_raise_op: bool,\n    pub pending_move_op: Arc<Option<(usize, usize, isize)>>,\n    pub already_moved_window_handles: Arc<Mutex<HashSet<isize>>>,\n    pub uncloack_to_ignore: usize,\n    /// Maps each known window hwnd to the (monitor, workspace) index pair managing it\n    pub known_hwnds: HashMap<isize, (usize, usize)>,\n}\n\nimpl AsRef<Self> for WindowManager {\n    fn as_ref(&self) -> &Self {\n        self\n    }\n}\n\nimpl_ring_elements!(WindowManager, Monitor);\n\n#[derive(Debug, Clone, Copy)]\nstruct EnforceWorkspaceRuleOp {\n    hwnd: isize,\n    origin_monitor_idx: usize,\n    origin_workspace_idx: usize,\n    target_monitor_idx: usize,\n    target_workspace_idx: usize,\n    floating: bool,\n}\nimpl EnforceWorkspaceRuleOp {\n    const fn is_origin(&self, monitor_idx: usize, workspace_idx: usize) -> bool {\n        self.origin_monitor_idx == monitor_idx && self.origin_workspace_idx == workspace_idx\n    }\n\n    const fn is_target(&self, monitor_idx: usize, workspace_idx: usize) -> bool {\n        self.target_monitor_idx == monitor_idx && self.target_workspace_idx == workspace_idx\n    }\n\n    const fn is_enforced(&self) -> bool {\n        (self.origin_monitor_idx == self.target_monitor_idx)\n            && (self.origin_workspace_idx == self.target_workspace_idx)\n    }\n}\n\nimpl WindowManager {\n    #[tracing::instrument]\n    pub fn new(\n        incoming: Receiver<WindowManagerEvent>,\n        custom_socket_path: Option<PathBuf>,\n    ) -> eyre::Result<Self> {\n        let socket = custom_socket_path.unwrap_or_else(|| DATA_DIR.join(\"komorebi.sock\"));\n\n        match std::fs::remove_file(&socket) {\n            Ok(()) => {}\n            Err(error) => match error.kind() {\n                // Doing this because ::exists() doesn't work reliably on Windows via IntelliJ\n                ErrorKind::NotFound => {}\n                _ => {\n                    return Err(error.into());\n                }\n            },\n        };\n\n        let listener = UnixListener::bind(&socket)?;\n\n        Ok(Self {\n            monitors: Ring::default(),\n            monitor_usr_idx_map: HashMap::new(),\n            incoming_events: incoming,\n            command_listener: listener,\n            is_paused: false,\n            virtual_desktop_id: current_virtual_desktop(),\n            work_area_offset: None,\n            window_management_behaviour: WindowManagementBehaviour::default(),\n            cross_monitor_move_behaviour: MoveBehaviour::Swap,\n            cross_boundary_behaviour: CrossBoundaryBehaviour::Monitor,\n            unmanaged_window_operation_behaviour: OperationBehaviour::Op,\n            resize_delta: 50,\n            focus_follows_mouse: None,\n            mouse_follows_focus: true,\n            hotwatch: Hotwatch::new()?,\n            has_pending_raise_op: false,\n            pending_move_op: Arc::new(None),\n            already_moved_window_handles: Arc::new(Mutex::new(HashSet::new())),\n            uncloack_to_ignore: 0,\n            known_hwnds: HashMap::new(),\n        })\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn init(&mut self) -> eyre::Result<()> {\n        tracing::info!(\"initialising\");\n        WindowsApi::load_monitor_information(self)?;\n        WindowsApi::load_workspace_information(&mut self.monitors)\n    }\n\n    #[tracing::instrument(skip(self, state))]\n    pub fn apply_state(&mut self, state: State) {\n        let mut can_apply = true;\n\n        let state_monitors_len = state.monitors.elements().len();\n        let current_monitors_len = self.monitors.elements().len();\n        if state_monitors_len != current_monitors_len {\n            tracing::warn!(\n                \"cannot apply state from {}; state file has {state_monitors_len} monitors, but only {current_monitors_len} are currently connected\",\n                temp_dir().join(\"komorebi.state.json\").to_string_lossy()\n            );\n\n            return;\n        }\n\n        for monitor in state.monitors.elements() {\n            for workspace in monitor.workspaces() {\n                for container in workspace.containers() {\n                    for window in container.windows() {\n                        if window.exe().is_err() {\n                            can_apply = false;\n                            break;\n                        }\n                    }\n                }\n\n                if let Some(window) = workspace.maximized_window\n                    && window.exe().is_err()\n                {\n                    can_apply = false;\n                    break;\n                }\n\n                if let Some(container) = &workspace.monocle_container {\n                    for window in container.windows() {\n                        if window.exe().is_err() {\n                            can_apply = false;\n                            break;\n                        }\n                    }\n                }\n\n                for window in workspace.floating_windows() {\n                    if window.exe().is_err() {\n                        can_apply = false;\n                        break;\n                    }\n                }\n            }\n        }\n\n        if can_apply {\n            tracing::info!(\n                \"applying state from {}\",\n                temp_dir().join(\"komorebi.state.json\").to_string_lossy()\n            );\n\n            let offset = self.work_area_offset;\n            let mouse_follows_focus = self.mouse_follows_focus;\n            for (monitor_idx, monitor) in self.monitors_mut().iter_mut().enumerate() {\n                let mut focused_workspace = 0;\n                for (workspace_idx, workspace) in monitor.workspaces_mut().iter_mut().enumerate() {\n                    if let Some(state_monitor) = state.monitors.elements().get(monitor_idx)\n                        && let Some(state_workspace) = state_monitor.workspaces().get(workspace_idx)\n                    {\n                        // to make sure padding and layout_options changes get applied for users after a quick restart\n                        let container_padding = workspace.container_padding;\n                        let workspace_padding = workspace.workspace_padding;\n                        let layout_options = workspace.layout_options;\n\n                        *workspace = state_workspace.clone();\n\n                        workspace.container_padding = container_padding;\n                        workspace.workspace_padding = workspace_padding;\n                        workspace.layout_options = layout_options;\n\n                        if state_monitor.focused_workspace_idx() == workspace_idx {\n                            focused_workspace = workspace_idx;\n                        }\n                    }\n                }\n\n                if let Err(error) = monitor.focus_workspace(focused_workspace) {\n                    tracing::warn!(\n                        \"cannot focus workspace '{focused_workspace}' on monitor '{monitor_idx}' from {}: {}\",\n                        temp_dir().join(\"komorebi.state.json\").to_string_lossy(),\n                        error,\n                    );\n                }\n\n                if let Err(error) = monitor.load_focused_workspace(mouse_follows_focus) {\n                    tracing::warn!(\n                        \"cannot load focused workspace '{focused_workspace}' on monitor '{monitor_idx}' from {}: {}\",\n                        temp_dir().join(\"komorebi.state.json\").to_string_lossy(),\n                        error,\n                    );\n                }\n\n                if let Err(error) = monitor.update_focused_workspace(offset) {\n                    tracing::warn!(\n                        \"cannot update workspace '{focused_workspace}' on monitor '{monitor_idx}' from {}: {}\",\n                        temp_dir().join(\"komorebi.state.json\").to_string_lossy(),\n                        error,\n                    );\n                }\n            }\n\n            let focused_monitor_idx = state.monitors.focused_idx();\n            let focused_workspace_idx = state\n                .monitors\n                .elements()\n                .get(focused_monitor_idx)\n                .map(|m| m.focused_workspace_idx())\n                .unwrap_or_default();\n\n            if let Err(error) = self.focus_monitor(focused_monitor_idx) {\n                tracing::warn!(\n                    \"cannot focus monitor '{focused_monitor_idx}' from {}: {}\",\n                    temp_dir().join(\"komorebi.state.json\").to_string_lossy(),\n                    error,\n                );\n            }\n\n            if let Err(error) = self.focus_workspace(focused_workspace_idx) {\n                tracing::warn!(\n                    \"cannot focus workspace '{focused_workspace_idx}' on monitor '{focused_monitor_idx}' from {}: {}\",\n                    temp_dir().join(\"komorebi.state.json\").to_string_lossy(),\n                    error,\n                );\n            }\n\n            if let Err(error) = self.update_focused_workspace(true, true) {\n                tracing::warn!(\n                    \"cannot update focused workspace '{focused_workspace_idx}' on monitor '{focused_monitor_idx}' from {}: {}\",\n                    temp_dir().join(\"komorebi.state.json\").to_string_lossy(),\n                    error,\n                );\n            }\n        } else {\n            tracing::warn!(\n                \"cannot apply state from {}; some windows referenced in the state file no longer exist\",\n                temp_dir().join(\"komorebi.state.json\").to_string_lossy()\n            );\n        }\n    }\n\n    #[tracing::instrument]\n    pub fn reload_configuration() {\n        tracing::info!(\"reloading configuration\");\n        std::thread::spawn(|| load_configuration().expect(\"could not load configuration\"));\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn reload_static_configuration(&mut self, pathbuf: &PathBuf) -> eyre::Result<()> {\n        tracing::info!(\"reloading static configuration\");\n        StaticConfig::reload(pathbuf, self)\n    }\n\n    pub fn window_management_behaviour(\n        &self,\n        monitor_idx: usize,\n        workspace_idx: usize,\n    ) -> WindowManagementBehaviour {\n        if let Some(monitor) = self.monitors().get(monitor_idx)\n            && let Some(workspace) = monitor.workspaces().get(workspace_idx)\n        {\n            let current_behaviour = if let Some(behaviour) = workspace.window_container_behaviour {\n                if workspace.containers().is_empty()\n                    && matches!(behaviour, WindowContainerBehaviour::Append)\n                {\n                    // You can't append to an empty workspace\n                    WindowContainerBehaviour::Create\n                } else {\n                    behaviour\n                }\n            } else if workspace.containers().is_empty()\n                && matches!(\n                    self.window_management_behaviour.current_behaviour,\n                    WindowContainerBehaviour::Append\n                )\n            {\n                // You can't append to an empty workspace\n                WindowContainerBehaviour::Create\n            } else {\n                self.window_management_behaviour.current_behaviour\n            };\n\n            let float_override = if let Some(float_override) = workspace.float_override {\n                float_override\n            } else {\n                self.window_management_behaviour.float_override\n            };\n\n            let floating_layer_behaviour =\n                if let Some(behaviour) = workspace.floating_layer_behaviour {\n                    behaviour\n                } else {\n                    monitor\n                        .floating_layer_behaviour\n                        .unwrap_or(self.window_management_behaviour.floating_layer_behaviour)\n                };\n\n            // If the workspace layer is `Floating` and the floating layer behaviour should\n            // float then change floating_layer_override to true so that new windows spawn\n            // as floating\n            let floating_layer_override = matches!(workspace.layer, WorkspaceLayer::Floating)\n                && floating_layer_behaviour.should_float();\n\n            return WindowManagementBehaviour {\n                current_behaviour,\n                float_override,\n                floating_layer_override,\n                floating_layer_behaviour,\n                toggle_float_placement: self.window_management_behaviour.toggle_float_placement,\n                floating_layer_placement: self.window_management_behaviour.floating_layer_placement,\n                float_override_placement: self.window_management_behaviour.float_override_placement,\n                float_rule_placement: self.window_management_behaviour.float_rule_placement,\n            };\n        }\n\n        WindowManagementBehaviour {\n            current_behaviour: WindowContainerBehaviour::Create,\n            float_override: self.window_management_behaviour.float_override,\n            floating_layer_override: self.window_management_behaviour.floating_layer_override,\n            floating_layer_behaviour: self.window_management_behaviour.floating_layer_behaviour,\n            toggle_float_placement: self.window_management_behaviour.toggle_float_placement,\n            floating_layer_placement: self.window_management_behaviour.floating_layer_placement,\n            float_override_placement: self.window_management_behaviour.float_override_placement,\n            float_rule_placement: self.window_management_behaviour.float_rule_placement,\n        }\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn watch_configuration(&mut self, enable: bool) -> eyre::Result<()> {\n        let config_pwsh = HOME_DIR.join(\"komorebi.ps1\");\n        let config_ahk = HOME_DIR.join(\"komorebi.ahk\");\n\n        if config_pwsh.exists() {\n            self.configure_watcher(enable, config_pwsh)?;\n        } else if config_ahk.exists() {\n            self.configure_watcher(enable, config_ahk)?;\n        }\n\n        Ok(())\n    }\n\n    fn configure_watcher(&mut self, enable: bool, config: PathBuf) -> eyre::Result<()> {\n        if enable {\n            tracing::info!(\"watching configuration for changes: {}\", config.display());\n            // Always make absolutely sure that there isn't an already existing watch, because\n            // hotwatch allows multiple watches to be registered for the same path\n            match self.hotwatch.unwatch(&config) {\n                Ok(()) => {}\n                Err(error) => match error {\n                    hotwatch::Error::Notify(ref notify_error) => match notify_error.kind {\n                        NotifyErrorKind::WatchNotFound => {}\n                        _ => return Err(error.into()),\n                    },\n                    error @ hotwatch::Error::Io(_) => return Err(error.into()),\n                },\n            }\n\n            self.hotwatch.watch(config, |event| match event.kind {\n                // Editing in Notepad sends a NoticeWrite while editing in (Neo)Vim sends\n                // a NoticeRemove, presumably because of the use of swap files?\n                EventKind::Modify(_) | EventKind::Remove(_) => {\n                    std::thread::spawn(|| {\n                        load_configuration().expect(\"could not load configuration\");\n                    });\n                }\n                _ => {}\n            })?;\n        } else {\n            tracing::info!(\n                \"no longer watching configuration for changes: {}\",\n                config.display()\n            );\n\n            self.hotwatch.unwatch(config)?;\n        };\n\n        Ok(())\n    }\n\n    pub fn monitor_idx_in_direction(&self, direction: OperationDirection) -> Option<usize> {\n        let current_monitor_size = self.focused_monitor_size().ok()?;\n\n        for (idx, monitor) in self.monitors.elements().iter().enumerate() {\n            match direction {\n                OperationDirection::Left => {\n                    if monitor.size.left + monitor.size.right == current_monitor_size.left {\n                        return Option::from(idx);\n                    }\n                }\n                OperationDirection::Right => {\n                    if current_monitor_size.right + current_monitor_size.left == monitor.size.left {\n                        return Option::from(idx);\n                    }\n                }\n                OperationDirection::Up => {\n                    if monitor.size.top + monitor.size.bottom == current_monitor_size.top {\n                        return Option::from(idx);\n                    }\n                }\n                OperationDirection::Down => {\n                    if current_monitor_size.top + current_monitor_size.bottom == monitor.size.top {\n                        return Option::from(idx);\n                    }\n                }\n            }\n        }\n\n        None\n    }\n\n    /// Calculates the direction of a move across monitors given a specific monitor index\n    pub fn direction_from_monitor_idx(\n        &self,\n        target_monitor_idx: usize,\n    ) -> Option<OperationDirection> {\n        let current_monitor_idx = self.focused_monitor_idx();\n        if current_monitor_idx == target_monitor_idx {\n            return None;\n        }\n\n        let current_monitor_size = self.focused_monitor_size().ok()?;\n        let target_monitor_size = self.monitors().get(target_monitor_idx)?.size;\n\n        if target_monitor_size.left + target_monitor_size.right == current_monitor_size.left {\n            return Some(OperationDirection::Left);\n        }\n        if current_monitor_size.right + current_monitor_size.left == target_monitor_size.left {\n            return Some(OperationDirection::Right);\n        }\n        if target_monitor_size.top + target_monitor_size.bottom == current_monitor_size.top {\n            return Some(OperationDirection::Up);\n        }\n        if current_monitor_size.top + current_monitor_size.bottom == target_monitor_size.top {\n            return Some(OperationDirection::Down);\n        }\n\n        None\n    }\n\n    #[allow(clippy::too_many_arguments)]\n    #[tracing::instrument(skip(self), level = \"debug\")]\n    fn add_window_handle_to_move_based_on_workspace_rule(\n        &self,\n        window_title: &String,\n        hwnd: isize,\n        origin_monitor_idx: usize,\n        origin_workspace_idx: usize,\n        target_monitor_idx: usize,\n        target_workspace_idx: usize,\n        floating: bool,\n        to_move: &mut Vec<EnforceWorkspaceRuleOp>,\n    ) {\n        tracing::trace!(\n            \"{} should be on monitor {}, workspace {}\",\n            window_title,\n            target_monitor_idx,\n            target_workspace_idx\n        );\n\n        // Create an operation outline and save it for later in the fn\n        to_move.push(EnforceWorkspaceRuleOp {\n            hwnd,\n            origin_monitor_idx,\n            origin_workspace_idx,\n            target_monitor_idx,\n            target_workspace_idx,\n            floating,\n        });\n    }\n\n    #[tracing::instrument(skip(self), level = \"debug\")]\n    pub fn enforce_workspace_rules(&mut self) -> eyre::Result<()> {\n        let mut to_move = vec![];\n\n        let focused_monitor_idx = self.focused_monitor_idx();\n        let focused_workspace_idx = self\n            .monitors()\n            .get(focused_monitor_idx)\n            .ok_or_eyre(\"there is no monitor with that index\")?\n            .focused_workspace_idx();\n\n        // scope mutex locks to avoid deadlock if should_update_focused_workspace evaluates to true\n        // at the end of this function\n        {\n            let workspace_matching_rules = WORKSPACE_MATCHING_RULES.lock();\n            let regex_identifiers = REGEX_IDENTIFIERS.lock();\n            // Go through all the monitors and workspaces\n            for (i, monitor) in self.monitors().iter().enumerate() {\n                for (j, workspace) in monitor.workspaces().iter().enumerate() {\n                    // And all the visible windows (at the top of a container)\n                    for window in workspace.visible_windows().into_iter().flatten() {\n                        let mut already_moved_window_handles =\n                            self.already_moved_window_handles.lock();\n\n                        if let (Ok(exe_name), Ok(title), Ok(class), Ok(path)) =\n                            (window.exe(), window.title(), window.class(), window.path())\n                        {\n                            for rule in &*workspace_matching_rules {\n                                let matched = match &rule.matching_rule {\n                                    MatchingRule::Simple(r) => should_act_individual(\n                                        &title,\n                                        &exe_name,\n                                        &class,\n                                        &path,\n                                        r,\n                                        &regex_identifiers,\n                                    ),\n                                    MatchingRule::Composite(r) => {\n                                        let mut composite_results = vec![];\n                                        for identifier in r {\n                                            composite_results.push(should_act_individual(\n                                                &title,\n                                                &exe_name,\n                                                &class,\n                                                &path,\n                                                identifier,\n                                                &regex_identifiers,\n                                            ));\n                                        }\n\n                                        composite_results.iter().all(|&x| x)\n                                    }\n                                };\n\n                                if matched {\n                                    let floating = workspace.floating_windows().contains(window);\n\n                                    if rule.initial_only {\n                                        if !already_moved_window_handles.contains(&window.hwnd) {\n                                            already_moved_window_handles.insert(window.hwnd);\n\n                                            self.add_window_handle_to_move_based_on_workspace_rule(\n                                                &window.title()?,\n                                                window.hwnd,\n                                                i,\n                                                j,\n                                                rule.monitor_index,\n                                                rule.workspace_index,\n                                                floating,\n                                                &mut to_move,\n                                            );\n                                        }\n                                    } else {\n                                        self.add_window_handle_to_move_based_on_workspace_rule(\n                                            &window.title()?,\n                                            window.hwnd,\n                                            i,\n                                            j,\n                                            rule.monitor_index,\n                                            rule.workspace_index,\n                                            floating,\n                                            &mut to_move,\n                                        );\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        // Only retain operations where the target is not the current workspace\n        to_move.retain(|op| !op.is_target(focused_monitor_idx, focused_workspace_idx));\n        // Only retain operations where the rule has not already been enforced\n        to_move.retain(|op| !op.is_enforced());\n\n        let mut should_update_focused_workspace = false;\n\n        // Parse the operation and remove any windows that are not placed according to their rules\n        for op in &to_move {\n            let target_area = self\n                .monitors_mut()\n                .get_mut(op.target_monitor_idx)\n                .ok_or_eyre(\"there is no monitor with that index\")?\n                .work_area_size;\n\n            let origin_monitor = self\n                .monitors_mut()\n                .get_mut(op.origin_monitor_idx)\n                .ok_or_eyre(\"there is no monitor with that index\")?;\n\n            let origin_area = origin_monitor.work_area_size;\n\n            let origin_workspace = origin_monitor\n                .workspaces_mut()\n                .get_mut(op.origin_workspace_idx)\n                .ok_or_eyre(\"there is no workspace with that index\")?;\n\n            let mut window = Window::from(op.hwnd);\n\n            // If it is a floating window move it to the target area\n            if op.floating {\n                window.move_to_area(&origin_area, &target_area)?;\n            }\n\n            // Hide the window we are about to remove if it is on the currently focused workspace\n            if op.is_origin(focused_monitor_idx, focused_workspace_idx) {\n                window.hide();\n                should_update_focused_workspace = true;\n            }\n\n            origin_workspace.remove_window(op.hwnd)?;\n        }\n\n        // Parse the operation again and associate those removed windows with the workspace that\n        // their rules have defined for them\n        for op in &to_move {\n            let target_monitor = self\n                .monitors_mut()\n                .get_mut(op.target_monitor_idx)\n                .ok_or_eyre(\"there is no monitor with that index\")?;\n\n            // The very first time this fn is called, the workspace might not even exist yet\n            if target_monitor\n                .workspaces()\n                .get(op.target_workspace_idx)\n                .is_none()\n            {\n                // If it doesn't, let's make sure it does for the next step\n                target_monitor.ensure_workspace_count(op.target_workspace_idx + 1);\n            }\n\n            let target_workspace = target_monitor\n                .workspaces_mut()\n                .get_mut(op.target_workspace_idx)\n                .ok_or_eyre(\"there is no workspace with that index\")?;\n\n            if op.floating {\n                target_workspace\n                    .floating_windows_mut()\n                    .push_back(Window::from(op.hwnd));\n            } else {\n                //TODO(alex-ds13): should this take into account the target workspace\n                //`window_container_behaviour`?\n                //In the case above a floating window should always be moved as floating,\n                //because it was set as so either manually by the user or by a\n                //`floating_applications` rule so it should stay that way. But a tiled window\n                //when moving to another workspace by a `workspace_rule` should honor that\n                //workspace `window_container_behaviour` in my opinion! Maybe this should be done\n                //on the `new_container_for_window` function instead.\n                target_workspace.new_container_for_window(Window::from(op.hwnd));\n            }\n        }\n\n        // Only re-tile the focused workspace if we need to\n        if should_update_focused_workspace {\n            self.update_focused_workspace(false, false)?;\n        }\n\n        Ok(())\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn retile_all(&mut self, preserve_resize_dimensions: bool) -> eyre::Result<()> {\n        let offset = self.work_area_offset;\n\n        for monitor in self.monitors_mut() {\n            let offset = if monitor.work_area_offset.is_some() {\n                monitor.work_area_offset\n            } else {\n                offset\n            };\n\n            let focused_workspace_idx = monitor.focused_workspace_idx();\n            monitor.update_workspace_globals(focused_workspace_idx, offset);\n\n            let hmonitor = monitor.id;\n            let monitor_wp = monitor.wallpaper.clone();\n            let workspace = monitor\n                .focused_workspace_mut()\n                .ok_or_eyre(\"there is no workspace\")?;\n\n            // Reset any resize adjustments if we want to force a retile\n            if !preserve_resize_dimensions {\n                for resize in &mut workspace.resize_dimensions {\n                    *resize = None;\n                }\n            }\n\n            if (workspace.wallpaper.is_some() || monitor_wp.is_some())\n                && let Err(error) = workspace.apply_wallpaper(hmonitor, &monitor_wp)\n            {\n                tracing::error!(\"failed to apply wallpaper: {}\", error);\n            }\n\n            workspace.update()?;\n        }\n\n        Ok(())\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn manage_focused_window(&mut self) -> eyre::Result<()> {\n        let hwnd = WindowsApi::foreground_window()?;\n        let event = WindowManagerEvent::Manage(Window::from(hwnd));\n        Ok(winevent_listener::event_tx().send(event)?)\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn unmanage_focused_window(&mut self) -> eyre::Result<()> {\n        let hwnd = WindowsApi::foreground_window()?;\n        let event = WindowManagerEvent::Unmanage(Window::from(hwnd));\n        Ok(winevent_listener::event_tx().send(event)?)\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn raise_window_at_cursor_pos(&mut self) -> eyre::Result<()> {\n        let mut hwnd = None;\n\n        let workspace = self.focused_workspace()?;\n        // first check the focused workspace\n        if let Some(container_idx) = workspace.container_idx_from_current_point()\n            && let Some(container) = workspace.containers().get(container_idx)\n            && let Some(window) = container.focused_window()\n        {\n            hwnd = Some(window.hwnd);\n        }\n\n        // then check all workspaces\n        if hwnd.is_none() {\n            for monitor in self.monitors() {\n                for ws in monitor.workspaces() {\n                    if let Some(container_idx) = ws.container_idx_from_current_point()\n                        && let Some(container) = ws.containers().get(container_idx)\n                        && let Some(window) = container.focused_window()\n                    {\n                        hwnd = Some(window.hwnd);\n                    }\n                }\n            }\n        }\n\n        // finally try matching the other way using a hwnd returned from the cursor pos\n        if hwnd.is_none() {\n            let cursor_pos_hwnd = WindowsApi::window_at_cursor_pos()?;\n\n            for monitor in self.monitors() {\n                for ws in monitor.workspaces() {\n                    if ws.container_for_window(cursor_pos_hwnd).is_some() {\n                        hwnd = Some(cursor_pos_hwnd);\n                    }\n                }\n            }\n        }\n\n        if let Some(hwnd) = hwnd {\n            if self.has_pending_raise_op\n                    || self.focused_window()?.hwnd == hwnd\n                    // Sometimes we need this check, because the focus may have been given by a click\n                    // to a non-window such as the taskbar or system tray, and komorebi doesn't know that\n                    // the focused window of the workspace is not actually focused by the OS at that point\n                    || WindowsApi::foreground_window()? == hwnd\n            {\n                return Ok(());\n            }\n\n            let event = WindowManagerEvent::Raise(Window::from(hwnd));\n            self.has_pending_raise_op = true;\n            winevent_listener::event_tx().send(event)?;\n        } else {\n            tracing::debug!(\n                \"not raising unknown window: {}\",\n                Window::from(WindowsApi::window_at_cursor_pos()?)\n            );\n        }\n\n        Ok(())\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn transfer_window(\n        &mut self,\n        origin: (usize, usize, isize),\n        target: (usize, usize, usize),\n    ) -> eyre::Result<()> {\n        let (origin_monitor_idx, origin_workspace_idx, w_hwnd) = origin;\n        let (target_monitor_idx, target_workspace_idx, target_container_idx) = target;\n\n        let origin_workspace = self\n            .monitors_mut()\n            .get_mut(origin_monitor_idx)\n            .ok_or_eyre(\"cannot get monitor idx\")?\n            .workspaces_mut()\n            .get_mut(origin_workspace_idx)\n            .ok_or_eyre(\"cannot get workspace idx\")?;\n\n        let origin_container_idx = origin_workspace\n            .container_for_window(w_hwnd)\n            .and_then(|c| origin_workspace.containers().iter().position(|cc| cc == c));\n\n        if let Some(origin_container_idx) = origin_container_idx {\n            // Moving normal container window\n            self.transfer_container(\n                (\n                    origin_monitor_idx,\n                    origin_workspace_idx,\n                    origin_container_idx,\n                ),\n                (\n                    target_monitor_idx,\n                    target_workspace_idx,\n                    target_container_idx,\n                ),\n            )?;\n        } else if let Some(idx) = origin_workspace\n            .floating_windows()\n            .iter()\n            .position(|w| w.hwnd == w_hwnd)\n        {\n            // Moving floating window\n            // There is no need to physically move the floating window between areas with\n            // `move_to_area` because the user already did that, so we only need to transfer the\n            // window to the target `floating_windows`\n            if let Some(floating_window) = origin_workspace.floating_windows_mut().remove(idx) {\n                let target_workspace = self\n                    .monitors_mut()\n                    .get_mut(target_monitor_idx)\n                    .ok_or_eyre(\"there is no monitor at this idx\")?\n                    .focused_workspace_mut()\n                    .ok_or_eyre(\"there is no focused workspace for this monitor\")?;\n\n                target_workspace\n                    .floating_windows_mut()\n                    .push_back(floating_window);\n            }\n        } else if origin_workspace\n            .monocle_container\n            .as_ref()\n            .and_then(|monocle| monocle.focused_window().map(|w| w.hwnd == w_hwnd))\n            .unwrap_or_default()\n        {\n            // Moving monocle container\n            if let Some(monocle_idx) = origin_workspace.monocle_container_restore_idx {\n                let origin_workspace = self\n                    .monitors_mut()\n                    .get_mut(origin_monitor_idx)\n                    .ok_or_eyre(\"there is no monitor at this idx\")?\n                    .workspaces_mut()\n                    .get_mut(origin_workspace_idx)\n                    .ok_or_eyre(\"there is no workspace for this monitor\")?;\n                let mut uncloack_amount = 0;\n                for container in origin_workspace.containers_mut() {\n                    container.restore();\n                    uncloack_amount += 1;\n                }\n                origin_workspace.reintegrate_monocle_container()?;\n\n                self.transfer_container(\n                    (origin_monitor_idx, origin_workspace_idx, monocle_idx),\n                    (\n                        target_monitor_idx,\n                        target_workspace_idx,\n                        target_container_idx,\n                    ),\n                )?;\n                // After we restore the origin workspace, some windows that were cloacked\n                // by the monocle might now be uncloacked which would trigger a workspace\n                // reconciliation since the focused monitor would be different from origin.\n                // That workspace reconciliation would focus the window on the origin monitor.\n                // So we need to ignore the uncloak events produced by the origin workspace\n                // restore to avoid that issue.\n                self.uncloack_to_ignore = uncloack_amount;\n            }\n        } else if origin_workspace\n            .maximized_window\n            .as_ref()\n            .map(|max| max.hwnd == w_hwnd)\n            .unwrap_or_default()\n        {\n            // Moving maximized_window\n            if let Some(maximized_idx) = origin_workspace.maximized_window_restore_idx {\n                self.focus_monitor(origin_monitor_idx)?;\n                let origin_monitor = self\n                    .focused_monitor_mut()\n                    .ok_or_eyre(\"there is no origin monitor\")?;\n                origin_monitor.focus_workspace(origin_workspace_idx)?;\n                self.unmaximize_window()?;\n                self.focus_monitor(target_monitor_idx)?;\n                let target_monitor = self\n                    .focused_monitor_mut()\n                    .ok_or_eyre(\"there is no target monitor\")?;\n                target_monitor.focus_workspace(target_workspace_idx)?;\n\n                self.transfer_container(\n                    (origin_monitor_idx, origin_workspace_idx, maximized_idx),\n                    (\n                        target_monitor_idx,\n                        target_workspace_idx,\n                        target_container_idx,\n                    ),\n                )?;\n            }\n        }\n        Ok(())\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn transfer_container(\n        &mut self,\n        origin: (usize, usize, usize),\n        target: (usize, usize, usize),\n    ) -> eyre::Result<()> {\n        let (origin_monitor_idx, origin_workspace_idx, origin_container_idx) = origin;\n        let (target_monitor_idx, target_workspace_idx, target_container_idx) = target;\n\n        let origin_container = self\n            .monitors_mut()\n            .get_mut(origin_monitor_idx)\n            .ok_or_eyre(\"there is no monitor at this index\")?\n            .workspaces_mut()\n            .get_mut(origin_workspace_idx)\n            .ok_or_eyre(\"there is no workspace at this index\")?\n            .remove_container(origin_container_idx)\n            .ok_or_eyre(\"there is no container at this index\")?;\n\n        let target_workspace = self\n            .monitors_mut()\n            .get_mut(target_monitor_idx)\n            .ok_or_eyre(\"there is no monitor at this index\")?\n            .workspaces_mut()\n            .get_mut(target_workspace_idx)\n            .ok_or_eyre(\"there is no workspace at this index\")?;\n\n        target_workspace\n            .containers_mut()\n            .insert(target_container_idx, origin_container);\n\n        target_workspace.focus_container(target_container_idx);\n\n        Ok(())\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn swap_containers(\n        &mut self,\n        origin: (usize, usize, usize),\n        target: (usize, usize, usize),\n    ) -> eyre::Result<()> {\n        let (origin_monitor_idx, origin_workspace_idx, origin_container_idx) = origin;\n        let (target_monitor_idx, target_workspace_idx, target_container_idx) = target;\n\n        let origin_container_is_valid = self\n            .monitors_mut()\n            .get_mut(origin_monitor_idx)\n            .ok_or_eyre(\"there is no monitor at this index\")?\n            .workspaces_mut()\n            .get_mut(origin_workspace_idx)\n            .ok_or_eyre(\"there is no workspace at this index\")?\n            .containers()\n            .get(origin_container_idx)\n            .is_some();\n\n        let target_container_is_valid = self\n            .monitors_mut()\n            .get_mut(target_monitor_idx)\n            .ok_or_eyre(\"there is no monitor at this index\")?\n            .workspaces_mut()\n            .get_mut(target_workspace_idx)\n            .ok_or_eyre(\"there is no workspace at this index\")?\n            .containers()\n            .get(origin_container_idx)\n            .is_some();\n\n        if origin_container_is_valid && target_container_is_valid {\n            let origin_container = self\n                .monitors_mut()\n                .get_mut(origin_monitor_idx)\n                .ok_or_eyre(\"there is no monitor at this index\")?\n                .workspaces_mut()\n                .get_mut(origin_workspace_idx)\n                .ok_or_eyre(\"there is no workspace at this index\")?\n                .remove_container(origin_container_idx)\n                .ok_or_eyre(\"there is no container at this index\")?;\n\n            let target_container = self\n                .monitors_mut()\n                .get_mut(target_monitor_idx)\n                .ok_or_eyre(\"there is no monitor at this index\")?\n                .workspaces_mut()\n                .get_mut(target_workspace_idx)\n                .ok_or_eyre(\"there is no workspace at this index\")?\n                .remove_container(target_container_idx);\n\n            self.monitors_mut()\n                .get_mut(target_monitor_idx)\n                .ok_or_eyre(\"there is no monitor at this index\")?\n                .workspaces_mut()\n                .get_mut(target_workspace_idx)\n                .ok_or_eyre(\"there is no workspace at this index\")?\n                .containers_mut()\n                .insert(target_container_idx, origin_container);\n\n            if let Some(target_container) = target_container {\n                self.monitors_mut()\n                    .get_mut(origin_monitor_idx)\n                    .ok_or_eyre(\"there is no monitor at this index\")?\n                    .workspaces_mut()\n                    .get_mut(origin_workspace_idx)\n                    .ok_or_eyre(\"there is no workspace at this index\")?\n                    .containers_mut()\n                    .insert(origin_container_idx, target_container);\n            }\n        }\n\n        Ok(())\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn update_focused_workspace(\n        &mut self,\n        follow_focus: bool,\n        trigger_focus: bool,\n    ) -> eyre::Result<()> {\n        tracing::info!(\"updating\");\n\n        let offset = self.work_area_offset;\n\n        self.focused_monitor_mut()\n            .ok_or_eyre(\"there is no monitor\")?\n            .update_focused_workspace(offset)?;\n\n        if follow_focus {\n            if let Some(window) = self.focused_workspace()?.maximized_window {\n                if trigger_focus {\n                    window.focus(self.mouse_follows_focus)?;\n                }\n            } else if let Some(container) = &self.focused_workspace()?.monocle_container {\n                if let Some(window) = container.focused_window()\n                    && trigger_focus\n                {\n                    window.focus(self.mouse_follows_focus)?;\n                }\n            } else if let Ok(window) = self.focused_window_mut() {\n                if trigger_focus {\n                    window.focus(self.mouse_follows_focus)?;\n                }\n            } else {\n                let desktop_window = Window::from(WindowsApi::desktop_window()?);\n\n                let rect = self.focused_monitor_size()?;\n                WindowsApi::center_cursor_in_rect(&rect)?;\n\n                match WindowsApi::raise_and_focus_window(desktop_window.hwnd) {\n                    Ok(()) => {}\n                    Err(error) => {\n                        tracing::warn!(\"{} {}:{}\", error, file!(), line!());\n                    }\n                }\n            }\n        } else {\n            if self.focused_workspace()?.is_empty() {\n                let desktop_window = Window::from(WindowsApi::desktop_window()?);\n\n                match WindowsApi::raise_and_focus_window(desktop_window.hwnd) {\n                    Ok(()) => {}\n                    Err(error) => {\n                        tracing::warn!(\"{} {}:{}\", error, file!(), line!());\n                    }\n                }\n            }\n\n            // if we passed false for follow_focus and there is a container on the workspace\n            if self.focused_container_mut().is_ok() {\n                // and we have a stack with >1 windows\n                if self.focused_container_mut()?.windows().len() > 1\n                    // and we don't have a maxed window\n                    && self.focused_workspace()?.maximized_window.is_none()\n                    // and we don't have a monocle container\n                    && self.focused_workspace()?.monocle_container.is_none()\n                    // and we don't have any floating windows that should show on top\n                    && self.focused_workspace()?.floating_windows().is_empty()\n                    && let Ok(window) = self.focused_window_mut()\n                        && trigger_focus\n                {\n                    window.focus(self.mouse_follows_focus)?;\n                }\n            }\n        }\n\n        Ok(())\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn resize_window(\n        &mut self,\n        direction: OperationDirection,\n        sizing: Sizing,\n        delta: i32,\n        update: bool,\n    ) -> eyre::Result<()> {\n        let mouse_follows_focus = self.mouse_follows_focus;\n        let mut focused_monitor_work_area = self.focused_monitor_work_area()?;\n        let workspace = self.focused_workspace_mut()?;\n\n        match workspace.layer {\n            WorkspaceLayer::Floating => {\n                let workspace = self.focused_workspace()?;\n                let focused_hwnd = WindowsApi::foreground_window()?;\n\n                let border_offset = BORDER_OFFSET.load(Ordering::SeqCst);\n                let border_width = BORDER_WIDTH.load(Ordering::SeqCst);\n                focused_monitor_work_area.left += border_offset;\n                focused_monitor_work_area.left += border_width;\n                focused_monitor_work_area.top += border_offset;\n                focused_monitor_work_area.top += border_width;\n                focused_monitor_work_area.right -= border_offset * 2;\n                focused_monitor_work_area.right -= border_width * 2;\n                focused_monitor_work_area.bottom -= border_offset * 2;\n                focused_monitor_work_area.bottom -= border_width * 2;\n\n                for window in workspace.floating_windows().iter() {\n                    if window.hwnd == focused_hwnd {\n                        let mut rect = WindowsApi::window_rect(window.hwnd)?;\n                        match (direction, sizing) {\n                            (OperationDirection::Left, Sizing::Increase) => {\n                                if rect.left - delta < focused_monitor_work_area.left {\n                                    rect.left = focused_monitor_work_area.left;\n                                } else {\n                                    rect.left -= delta;\n                                }\n                            }\n                            (OperationDirection::Left, Sizing::Decrease) => {\n                                rect.left += delta;\n                            }\n                            (OperationDirection::Right, Sizing::Increase) => {\n                                if rect.left + rect.right + delta * 2\n                                    > focused_monitor_work_area.left\n                                        + focused_monitor_work_area.right\n                                {\n                                    rect.right = focused_monitor_work_area.left\n                                        + focused_monitor_work_area.right\n                                        - rect.left;\n                                } else {\n                                    rect.right += delta * 2;\n                                }\n                            }\n                            (OperationDirection::Right, Sizing::Decrease) => {\n                                rect.right -= delta * 2;\n                            }\n                            (OperationDirection::Up, Sizing::Increase) => {\n                                if rect.top - delta < focused_monitor_work_area.top {\n                                    rect.top = focused_monitor_work_area.top;\n                                } else {\n                                    rect.top -= delta;\n                                }\n                            }\n                            (OperationDirection::Up, Sizing::Decrease) => {\n                                rect.top += delta;\n                            }\n                            (OperationDirection::Down, Sizing::Increase) => {\n                                if rect.top + rect.bottom + delta * 2\n                                    > focused_monitor_work_area.top\n                                        + focused_monitor_work_area.bottom\n                                {\n                                    rect.bottom = focused_monitor_work_area.top\n                                        + focused_monitor_work_area.bottom\n                                        - rect.top;\n                                } else {\n                                    rect.bottom += delta * 2;\n                                }\n                            }\n                            (OperationDirection::Down, Sizing::Decrease) => {\n                                rect.bottom -= delta * 2;\n                            }\n                        }\n\n                        WindowsApi::position_window(window.hwnd, &rect, false, true)?;\n                        if mouse_follows_focus {\n                            WindowsApi::center_cursor_in_rect(&rect)?;\n                        }\n\n                        break;\n                    }\n                }\n            }\n            WorkspaceLayer::Tiling => {\n                match workspace.layout {\n                    Layout::Default(layout) => {\n                        tracing::info!(\"resizing window\");\n                        let len = NonZeroUsize::new(workspace.containers().len())\n                            .ok_or_eyre(\"there must be at least one container\")?;\n                        let focused_idx = workspace.focused_container_idx();\n                        let focused_idx_resize = workspace\n                            .resize_dimensions\n                            .get(focused_idx)\n                            .ok_or_eyre(\"there is no resize adjustment for this container\")?;\n\n                        if direction\n                            .destination(\n                                workspace.layout.as_boxed_direction().as_ref(),\n                                workspace.layout_flip,\n                                focused_idx,\n                                len,\n                                workspace.layout_options,\n                            )\n                            .is_some()\n                        {\n                            let unaltered = layout.calculate(\n                                &focused_monitor_work_area,\n                                len,\n                                workspace.container_padding,\n                                workspace.layout_flip,\n                                &[],\n                                workspace.focused_container_idx(),\n                                workspace.layout_options,\n                                &workspace.latest_layout,\n                            );\n\n                            let mut direction = direction;\n\n                            // We only ever want to operate on the unflipped Rect positions when resizing, then we\n                            // can flip them however they need to be flipped once the resizing has been done\n                            if let Some(flip) = workspace.layout_flip {\n                                match flip {\n                                    Axis::Horizontal => {\n                                        if matches!(direction, OperationDirection::Left)\n                                            || matches!(direction, OperationDirection::Right)\n                                        {\n                                            direction = direction.opposite();\n                                        }\n                                    }\n                                    Axis::Vertical => {\n                                        if matches!(direction, OperationDirection::Up)\n                                            || matches!(direction, OperationDirection::Down)\n                                        {\n                                            direction = direction.opposite();\n                                        }\n                                    }\n                                    Axis::HorizontalAndVertical => direction = direction.opposite(),\n                                }\n                            }\n\n                            let resize = layout.resize(\n                                unaltered\n                                    .get(focused_idx)\n                                    .ok_or_eyre(\"there is no last layout\")?,\n                                focused_idx_resize,\n                                direction,\n                                sizing,\n                                delta,\n                            );\n\n                            workspace.resize_dimensions[focused_idx] = resize;\n\n                            return if update {\n                                self.update_focused_workspace(false, false)\n                            } else {\n                                Ok(())\n                            };\n                        }\n\n                        tracing::warn!(\"cannot resize container in this direction\");\n                    }\n                    Layout::Custom(_) => {\n                        tracing::warn!(\"containers cannot be resized when using custom layouts\");\n                    }\n                }\n            }\n        }\n\n        Ok(())\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn stop(&mut self, ignore_restore: bool) -> eyre::Result<()> {\n        tracing::info!(\n            \"received stop command, restoring all hidden windows and terminating process\"\n        );\n\n        let state = &State::from(&*self);\n        std::fs::write(\n            temp_dir().join(\"komorebi.state.json\"),\n            serde_json::to_string_pretty(&state)?,\n        )?;\n\n        ANIMATION_ENABLED_PER_ANIMATION.lock().clear();\n        ANIMATION_ENABLED_GLOBAL.store(false, Ordering::SeqCst);\n        self.restore_all_windows(ignore_restore)?;\n        AnimationEngine::wait_for_all_animations();\n\n        if WindowsApi::focus_follows_mouse()? {\n            WindowsApi::disable_focus_follows_mouse()?;\n        }\n\n        let sockets = SUBSCRIPTION_SOCKETS.lock();\n        for path in (*sockets).values() {\n            if let Ok(stream) = UnixStream::connect(path) {\n                stream.shutdown(Shutdown::Both)?;\n            }\n        }\n\n        let socket = DATA_DIR.join(\"komorebi.sock\");\n        let _ = std::fs::remove_file(socket);\n\n        std::process::exit(0)\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn restore_all_windows(&mut self, ignore_restore: bool) -> eyre::Result<()> {\n        tracing::info!(\"restoring all hidden windows\");\n\n        let no_titlebar = NO_TITLEBAR.lock();\n        let regex_identifiers = REGEX_IDENTIFIERS.lock();\n        let known_transparent_hwnds = transparency_manager::known_hwnds();\n        let border_implementation = border_manager::IMPLEMENTATION.load();\n\n        for monitor in self.monitors_mut() {\n            for workspace in monitor.workspaces_mut() {\n                if let Some(monocle) = &workspace.monocle_container {\n                    for window in monocle.windows() {\n                        if matches!(border_implementation, BorderImplementation::Windows) {\n                            window.remove_accent()?;\n                        }\n                    }\n                }\n\n                for containers in workspace.containers_mut() {\n                    for window in containers.windows_mut() {\n                        let should_remove_titlebar_for_window = should_act(\n                            &window.title().unwrap_or_default(),\n                            &window.exe().unwrap_or_default(),\n                            &window.class().unwrap_or_default(),\n                            &window.path().unwrap_or_default(),\n                            &no_titlebar,\n                            &regex_identifiers,\n                        )\n                        .is_some();\n\n                        if should_remove_titlebar_for_window {\n                            window.add_title_bar()?;\n                        }\n\n                        if known_transparent_hwnds.contains(&window.hwnd) {\n                            window.opaque()?;\n                        }\n\n                        if matches!(border_implementation, BorderImplementation::Windows) {\n                            window.remove_accent()?;\n                        }\n\n                        if !ignore_restore {\n                            window.restore();\n                        }\n                    }\n                }\n            }\n        }\n\n        Ok(())\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn remove_all_accents(&mut self) -> eyre::Result<()> {\n        tracing::info!(\"removing all window accents\");\n\n        for monitor in self.monitors() {\n            for workspace in monitor.workspaces() {\n                if let Some(monocle) = &workspace.monocle_container {\n                    for window in monocle.windows() {\n                        window.remove_accent()?\n                    }\n                }\n\n                for containers in workspace.containers() {\n                    for window in containers.windows() {\n                        window.remove_accent()?;\n                    }\n                }\n            }\n        }\n\n        Ok(())\n    }\n\n    #[tracing::instrument(skip(self))]\n    fn handle_unmanaged_window_behaviour(&self) -> eyre::Result<()> {\n        if matches!(\n            self.unmanaged_window_operation_behaviour,\n            OperationBehaviour::NoOp\n        ) {\n            let workspace = self.focused_workspace()?;\n            let focused_hwnd = WindowsApi::foreground_window()?;\n            if !workspace.contains_managed_window(focused_hwnd) {\n                bail!(\"ignoring commands while active window is not managed by komorebi\");\n            }\n        }\n\n        Ok(())\n    }\n\n    /// Check for an existing wallpaper definition on the workspace/monitor index pair and apply it\n    /// if it exists\n    #[tracing::instrument(skip(self))]\n    pub fn apply_wallpaper_for_monitor_workspace(\n        &mut self,\n        monitor_idx: usize,\n        workspace_idx: usize,\n    ) -> eyre::Result<()> {\n        let monitor = self\n            .monitors_mut()\n            .get_mut(monitor_idx)\n            .ok_or_eyre(\"there is no monitor\")?;\n\n        let hmonitor = monitor.id;\n        let monitor_wp = monitor.wallpaper.clone();\n\n        let workspace = monitor\n            .workspaces()\n            .get(workspace_idx)\n            .ok_or_eyre(\"there is no workspace\")?;\n\n        workspace.apply_wallpaper(hmonitor, &monitor_wp)\n    }\n\n    pub fn update_focused_workspace_by_monitor_idx(&mut self, idx: usize) -> eyre::Result<()> {\n        let offset = self.work_area_offset;\n\n        self.monitors_mut()\n            .get_mut(idx)\n            .ok_or_eyre(\"there is no monitor\")?\n            .update_focused_workspace(offset)\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn swap_monitor_workspaces(\n        &mut self,\n        first_idx: usize,\n        second_idx: usize,\n    ) -> eyre::Result<()> {\n        tracing::info!(\"swaping monitors\");\n        if first_idx == second_idx {\n            return Ok(());\n        }\n        let mouse_follows_focus = self.mouse_follows_focus;\n        let offset = self.work_area_offset;\n        let first_focused_workspace = {\n            let first_monitor = self\n                .monitors()\n                .get(first_idx)\n                .ok_or_eyre(\"There is no monitor\")?;\n            first_monitor.focused_workspace_idx()\n        };\n\n        let second_focused_workspace = {\n            let second_monitor = self\n                .monitors()\n                .get(second_idx)\n                .ok_or_eyre(\"There is no monitor\")?;\n            second_monitor.focused_workspace_idx()\n        };\n\n        // Swap workspaces between the first and second monitors\n\n        let first_workspaces = self\n            .monitors_mut()\n            .get_mut(first_idx)\n            .ok_or_eyre(\"There is no monitor\")?\n            .remove_workspaces();\n\n        let second_workspaces = self\n            .monitors_mut()\n            .get_mut(second_idx)\n            .ok_or_eyre(\"There is no monitor\")?\n            .remove_workspaces();\n\n        self.monitors_mut()\n            .get_mut(first_idx)\n            .ok_or_eyre(\"There is no monitor\")?\n            .workspaces_mut()\n            .extend(second_workspaces);\n\n        self.monitors_mut()\n            .get_mut(second_idx)\n            .ok_or_eyre(\"There is no monitor\")?\n            .workspaces_mut()\n            .extend(first_workspaces);\n\n        // Set the focused workspaces for the first and second monitors\n        if let Some(first_monitor) = self.monitors_mut().get_mut(first_idx) {\n            first_monitor.update_workspaces_globals(offset);\n            first_monitor.focus_workspace(second_focused_workspace)?;\n            first_monitor.load_focused_workspace(mouse_follows_focus)?;\n        }\n\n        if let Some(second_monitor) = self.monitors_mut().get_mut(second_idx) {\n            second_monitor.update_workspaces_globals(offset);\n            second_monitor.focus_workspace(first_focused_workspace)?;\n            second_monitor.load_focused_workspace(mouse_follows_focus)?;\n        }\n\n        self.update_focused_workspace_by_monitor_idx(second_idx)?;\n        self.update_focused_workspace_by_monitor_idx(first_idx)\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn swap_focused_monitor(&mut self, idx: usize) -> eyre::Result<()> {\n        tracing::info!(\"swapping focused monitor\");\n\n        let focused_monitor_idx = self.focused_monitor_idx();\n        let mouse_follows_focus = self.mouse_follows_focus;\n\n        self.swap_monitor_workspaces(focused_monitor_idx, idx)?;\n\n        self.update_focused_workspace(mouse_follows_focus, true)\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn move_container_to_monitor(\n        &mut self,\n        monitor_idx: usize,\n        workspace_idx: Option<usize>,\n        follow: bool,\n        move_direction: Option<OperationDirection>,\n    ) -> eyre::Result<()> {\n        self.handle_unmanaged_window_behaviour()?;\n\n        tracing::info!(\"moving container\");\n\n        let focused_monitor_idx = self.focused_monitor_idx();\n\n        if focused_monitor_idx == monitor_idx\n            && let Some(workspace_idx) = workspace_idx\n        {\n            return self.move_container_to_workspace(workspace_idx, follow, None);\n        }\n\n        let offset = self.work_area_offset;\n        let mouse_follows_focus = self.mouse_follows_focus;\n\n        let monitor = self\n            .focused_monitor_mut()\n            .ok_or_eyre(\"there is no monitor\")?;\n\n        let current_area = monitor.work_area_size;\n\n        let workspace = monitor\n            .focused_workspace_mut()\n            .ok_or_eyre(\"there is no workspace\")?;\n\n        if workspace.maximized_window.is_some() {\n            bail!(\"cannot move native maximized window to another monitor or workspace\");\n        }\n\n        let foreground_hwnd = WindowsApi::foreground_window()?;\n        let floating_window_index = workspace\n            .floating_windows()\n            .iter()\n            .position(|w| w.hwnd == foreground_hwnd);\n\n        let floating_window =\n            floating_window_index.and_then(|idx| workspace.floating_windows_mut().remove(idx));\n        let container = if floating_window_index.is_none() {\n            Some(\n                workspace\n                    .remove_focused_container()\n                    .ok_or_eyre(\"there is no container\")?,\n            )\n        } else {\n            None\n        };\n        monitor.update_focused_workspace(offset)?;\n\n        let target_monitor = self\n            .monitors_mut()\n            .get_mut(monitor_idx)\n            .ok_or_eyre(\"there is no monitor\")?;\n\n        let mut should_load_workspace = false;\n        if let Some(workspace_idx) = workspace_idx\n            && workspace_idx != target_monitor.focused_workspace_idx()\n        {\n            target_monitor.focus_workspace(workspace_idx)?;\n            should_load_workspace = true;\n        }\n        let target_workspace = target_monitor\n            .focused_workspace_mut()\n            .ok_or_eyre(\"there is no focused workspace on target monitor\")?;\n\n        if target_workspace.monocle_container.is_some() {\n            for container in target_workspace.containers_mut() {\n                container.restore();\n            }\n\n            for window in target_workspace.floating_windows_mut() {\n                window.restore();\n            }\n\n            target_workspace.reintegrate_monocle_container()?;\n        }\n\n        if let Some(window) = floating_window {\n            target_workspace.floating_windows_mut().push_back(window);\n            target_workspace.layer = WorkspaceLayer::Floating;\n            Window::from(window.hwnd)\n                .move_to_area(&current_area, &target_monitor.work_area_size)?;\n        } else if let Some(container) = container {\n            let container_hwnds = container\n                .windows()\n                .iter()\n                .map(|w| w.hwnd)\n                .collect::<Vec<_>>();\n\n            target_workspace.layer = WorkspaceLayer::Tiling;\n\n            if let Some(direction) = move_direction {\n                target_monitor.add_container_with_direction(container, workspace_idx, direction)?;\n            } else {\n                target_monitor.add_container(container, workspace_idx)?;\n            }\n\n            if let Some(workspace) = target_monitor.focused_workspace()\n                && !workspace.tile\n            {\n                for hwnd in container_hwnds {\n                    Window::from(hwnd)\n                        .move_to_area(&current_area, &target_monitor.work_area_size)?;\n                }\n            }\n        } else {\n            bail!(\"failed to find a window to move\");\n        }\n\n        if should_load_workspace {\n            target_monitor.load_focused_workspace(mouse_follows_focus)?;\n        }\n        target_monitor.update_focused_workspace(offset)?;\n\n        // this second one is for DPI changes when the target is another monitor\n        // if we don't do this the layout on the other monitor could look funny\n        // until it is interacted with again\n        target_monitor.update_focused_workspace(offset)?;\n\n        if follow {\n            self.focus_monitor(monitor_idx)?;\n        }\n\n        self.update_focused_workspace(self.mouse_follows_focus, true)?;\n\n        Ok(())\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn move_container_to_workspace(\n        &mut self,\n        idx: usize,\n        follow: bool,\n        direction: Option<OperationDirection>,\n    ) -> eyre::Result<()> {\n        self.handle_unmanaged_window_behaviour()?;\n\n        tracing::info!(\"moving container\");\n\n        let mouse_follows_focus = self.mouse_follows_focus;\n        let monitor = self\n            .focused_monitor_mut()\n            .ok_or_eyre(\"there is no monitor\")?;\n\n        monitor.move_container_to_workspace(idx, follow, direction)?;\n        monitor.load_focused_workspace(mouse_follows_focus)?;\n\n        self.update_focused_workspace(mouse_follows_focus, true)?;\n\n        Ok(())\n    }\n\n    pub fn remove_focused_workspace(&mut self) -> Option<Workspace> {\n        let focused_monitor: &mut Monitor = self.focused_monitor_mut()?;\n        let focused_workspace_idx = focused_monitor.focused_workspace_idx();\n        let workspace = focused_monitor.remove_workspace_by_idx(focused_workspace_idx);\n        if let Err(error) = focused_monitor.focus_workspace(focused_workspace_idx.saturating_sub(1))\n        {\n            tracing::error!(\n                \"Error focusing previous workspace while removing the focused workspace: {}\",\n                error\n            );\n        }\n        workspace\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn move_workspace_to_monitor(&mut self, idx: usize) -> eyre::Result<()> {\n        tracing::info!(\"moving workspace\");\n        let mouse_follows_focus = self.mouse_follows_focus;\n        let offset = self.work_area_offset;\n        let workspace = self\n            .remove_focused_workspace()\n            .ok_or_eyre(\"there is no workspace\")?;\n\n        {\n            let target_monitor: &mut Monitor = self\n                .monitors_mut()\n                .get_mut(idx)\n                .ok_or_eyre(\"there is no monitor\")?;\n\n            target_monitor.workspaces_mut().push_back(workspace);\n            target_monitor.update_workspaces_globals(offset);\n            target_monitor.focus_workspace(target_monitor.workspaces().len().saturating_sub(1))?;\n            target_monitor.load_focused_workspace(mouse_follows_focus)?;\n        }\n\n        self.focus_monitor(idx)?;\n        self.update_focused_workspace(mouse_follows_focus, true)\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn focus_floating_window_in_direction(\n        &mut self,\n        direction: OperationDirection,\n    ) -> eyre::Result<()> {\n        let mouse_follows_focus = self.mouse_follows_focus;\n        let focused_workspace = self.focused_workspace_mut()?;\n\n        let mut target_idx = None;\n        let len = focused_workspace.floating_windows().len();\n\n        if len > 1 {\n            let focused_hwnd = WindowsApi::foreground_window()?;\n            let focused_rect = WindowsApi::window_rect(focused_hwnd)?;\n            match direction {\n                OperationDirection::Left => {\n                    let mut windows_in_direction = focused_workspace\n                        .floating_windows()\n                        .iter()\n                        .enumerate()\n                        .flat_map(|(idx, w)| {\n                            (w.hwnd != focused_hwnd)\n                                .then_some(WindowsApi::window_rect(w.hwnd).ok().map(|r| (idx, r)))\n                        })\n                        .flatten()\n                        .flat_map(|(idx, r)| {\n                            (r.left < focused_rect.left)\n                                .then_some((idx, i32::abs(r.left - focused_rect.left)))\n                        })\n                        .collect::<Vec<_>>();\n\n                    // Sort by distance to focused\n                    windows_in_direction.sort_by_key(|(_, d)| (*d as f32 * 1000.0).trunc() as i32);\n\n                    if let Some((idx, _)) = windows_in_direction.first() {\n                        target_idx = Some(*idx);\n                    }\n                }\n                OperationDirection::Right => {\n                    let mut windows_in_direction = focused_workspace\n                        .floating_windows()\n                        .iter()\n                        .enumerate()\n                        .flat_map(|(idx, w)| {\n                            (w.hwnd != focused_hwnd)\n                                .then_some(WindowsApi::window_rect(w.hwnd).ok().map(|r| (idx, r)))\n                        })\n                        .flatten()\n                        .flat_map(|(idx, r)| {\n                            (r.left > focused_rect.left)\n                                .then_some((idx, i32::abs(r.left - focused_rect.left)))\n                        })\n                        .collect::<Vec<_>>();\n\n                    // Sort by distance to focused\n                    windows_in_direction.sort_by_key(|(_, d)| (*d as f32 * 1000.0).trunc() as i32);\n\n                    if let Some((idx, _)) = windows_in_direction.first() {\n                        target_idx = Some(*idx);\n                    }\n                }\n                OperationDirection::Up => {\n                    let mut windows_in_direction = focused_workspace\n                        .floating_windows()\n                        .iter()\n                        .enumerate()\n                        .flat_map(|(idx, w)| {\n                            (w.hwnd != focused_hwnd)\n                                .then_some(WindowsApi::window_rect(w.hwnd).ok().map(|r| (idx, r)))\n                        })\n                        .flatten()\n                        .flat_map(|(idx, r)| {\n                            (r.top < focused_rect.top)\n                                .then_some((idx, i32::abs(r.top - focused_rect.top)))\n                        })\n                        .collect::<Vec<_>>();\n\n                    // Sort by distance to focused\n                    windows_in_direction.sort_by_key(|(_, d)| (*d as f32 * 1000.0).trunc() as i32);\n\n                    if let Some((idx, _)) = windows_in_direction.first() {\n                        target_idx = Some(*idx);\n                    }\n                }\n                OperationDirection::Down => {\n                    let mut windows_in_direction = focused_workspace\n                        .floating_windows()\n                        .iter()\n                        .enumerate()\n                        .flat_map(|(idx, w)| {\n                            (w.hwnd != focused_hwnd)\n                                .then_some(WindowsApi::window_rect(w.hwnd).ok().map(|r| (idx, r)))\n                        })\n                        .flatten()\n                        .flat_map(|(idx, r)| {\n                            (r.top > focused_rect.top)\n                                .then_some((idx, i32::abs(r.top - focused_rect.top)))\n                        })\n                        .collect::<Vec<_>>();\n\n                    // Sort by distance to focused\n                    windows_in_direction.sort_by_key(|(_, d)| (*d as f32 * 1000.0).trunc() as i32);\n\n                    if let Some((idx, _)) = windows_in_direction.first() {\n                        target_idx = Some(*idx);\n                    }\n                }\n            };\n        }\n\n        if let Some(idx) = target_idx {\n            focused_workspace.floating_windows.focus(idx);\n            if let Some(window) = focused_workspace.floating_windows().get(idx) {\n                window.focus(mouse_follows_focus)?;\n            }\n            return Ok(());\n        }\n\n        let mut cross_monitor_monocle_or_max = false;\n\n        let workspace_idx = self.focused_workspace_idx()?;\n\n        // this is for when we are scrolling across workspaces like PaperWM\n        if matches!(\n            self.cross_boundary_behaviour,\n            CrossBoundaryBehaviour::Workspace\n        ) && matches!(\n            direction,\n            OperationDirection::Left | OperationDirection::Right\n        ) {\n            let workspace_count = if let Some(monitor) = self.focused_monitor() {\n                monitor.workspaces().len()\n            } else {\n                1\n            };\n\n            let next_idx = match direction {\n                OperationDirection::Left => match workspace_idx {\n                    0 => workspace_count - 1,\n                    n => n - 1,\n                },\n                OperationDirection::Right => match workspace_idx {\n                    n if n == workspace_count - 1 => 0,\n                    n => n + 1,\n                },\n                _ => workspace_idx,\n            };\n\n            self.focus_workspace(next_idx)?;\n\n            if let Ok(focused_workspace) = self.focused_workspace_mut()\n                && focused_workspace.monocle_container.is_none()\n            {\n                match direction {\n                    OperationDirection::Left => match focused_workspace.layout {\n                        Layout::Default(layout) => {\n                            let target_index =\n                                layout.rightmost_index(focused_workspace.containers().len());\n                            focused_workspace.focus_container(target_index);\n                        }\n                        Layout::Custom(_) => {\n                            focused_workspace.focus_container(\n                                focused_workspace.containers().len().saturating_sub(1),\n                            );\n                        }\n                    },\n                    OperationDirection::Right => match focused_workspace.layout {\n                        Layout::Default(layout) => {\n                            let target_index =\n                                layout.leftmost_index(focused_workspace.containers().len());\n                            focused_workspace.focus_container(target_index);\n                        }\n                        Layout::Custom(_) => {\n                            focused_workspace.focus_container(0);\n                        }\n                    },\n                    _ => {}\n                };\n            }\n\n            return Ok(());\n        }\n\n        // if there is no floating_window in that direction for this workspace\n        let monitor_idx = self\n            .monitor_idx_in_direction(direction)\n            .ok_or_eyre(\"there is no container or monitor in this direction\")?;\n\n        self.focus_monitor(monitor_idx)?;\n        let mouse_follows_focus = self.mouse_follows_focus;\n\n        if let Ok(focused_workspace) = self.focused_workspace_mut() {\n            if let Some(window) = focused_workspace.maximized_window {\n                window.focus(mouse_follows_focus)?;\n                cross_monitor_monocle_or_max = true;\n            } else if let Some(monocle) = &focused_workspace.monocle_container {\n                if let Some(window) = monocle.focused_window() {\n                    window.focus(mouse_follows_focus)?;\n                    cross_monitor_monocle_or_max = true;\n                }\n            } else if focused_workspace.layer == WorkspaceLayer::Tiling {\n                match direction {\n                    OperationDirection::Left => match focused_workspace.layout {\n                        Layout::Default(layout) => {\n                            let target_index =\n                                layout.rightmost_index(focused_workspace.containers().len());\n                            focused_workspace.focus_container(target_index);\n                        }\n                        Layout::Custom(_) => {\n                            focused_workspace.focus_container(\n                                focused_workspace.containers().len().saturating_sub(1),\n                            );\n                        }\n                    },\n                    OperationDirection::Right => match focused_workspace.layout {\n                        Layout::Default(layout) => {\n                            let target_index =\n                                layout.leftmost_index(focused_workspace.containers().len());\n                            focused_workspace.focus_container(target_index);\n                        }\n                        Layout::Custom(_) => {\n                            focused_workspace.focus_container(0);\n                        }\n                    },\n                    _ => {}\n                };\n            }\n        }\n\n        if !cross_monitor_monocle_or_max {\n            let ws = self.focused_workspace_mut()?;\n            if ws.is_empty() {\n                // This is to remove focus from the previous monitor\n                let desktop_window = Window::from(WindowsApi::desktop_window()?);\n\n                match WindowsApi::raise_and_focus_window(desktop_window.hwnd) {\n                    Ok(()) => {}\n                    Err(error) => {\n                        tracing::warn!(\"{} {}:{}\", error, file!(), line!());\n                    }\n                }\n            } else if ws.layer == WorkspaceLayer::Floating && !ws.floating_windows().is_empty() {\n                if let Some(window) = ws.focused_floating_window() {\n                    window.focus(self.mouse_follows_focus)?;\n                }\n            } else {\n                ws.layer = WorkspaceLayer::Tiling;\n                if let Ok(focused_window) = self.focused_window() {\n                    focused_window.focus(self.mouse_follows_focus)?;\n                }\n            }\n        }\n\n        Ok(())\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn preselect_container_in_direction(\n        &mut self,\n        direction: OperationDirection,\n    ) -> eyre::Result<()> {\n        let workspace = self.focused_workspace_mut()?;\n        let focused_idx = workspace.focused_container_idx();\n\n        if matches!(workspace.layout, Layout::Default(DefaultLayout::Grid)) {\n            tracing::warn!(\"preselection is not supported on the grid layout\");\n            return Ok(());\n        }\n\n        tracing::info!(\"preselecting container\");\n\n        let new_idx =\n            if workspace.maximized_window.is_some() || workspace.monocle_container.is_some() {\n                None\n            } else {\n                workspace.new_idx_for_direction(direction)\n            };\n\n        match new_idx {\n            Some(new_idx) => {\n                let adjusted_idx = match direction {\n                    OperationDirection::Left | OperationDirection::Up => {\n                        if focused_idx.abs_diff(new_idx) == 1 {\n                            new_idx + 1\n                        } else {\n                            new_idx\n                        }\n                    }\n                    _ => new_idx,\n                };\n\n                workspace.preselect_container_idx(adjusted_idx);\n            }\n            None => {\n                tracing::debug!(\n                    \"this is not a valid preselection direction from the current position\"\n                )\n            }\n        }\n\n        Ok(())\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn focus_container_in_direction(\n        &mut self,\n        direction: OperationDirection,\n    ) -> eyre::Result<()> {\n        self.handle_unmanaged_window_behaviour()?;\n\n        let workspace = self.focused_workspace()?;\n        let workspace_idx = self.focused_workspace_idx()?;\n\n        tracing::info!(\"focusing container\");\n\n        if workspace.monocle_container.is_some() {\n            let cycle_direction = match direction {\n                OperationDirection::Left | OperationDirection::Down => CycleDirection::Previous,\n                OperationDirection::Right | OperationDirection::Up => CycleDirection::Next,\n            };\n            return self.cycle_monocle(cycle_direction);\n        }\n\n        let new_idx = if workspace.maximized_window.is_some() {\n            None\n        } else {\n            workspace.new_idx_for_direction(direction)\n        };\n\n        let mut cross_monitor_monocle_or_max = false;\n\n        // this is for when we are scrolling across workspaces like PaperWM\n        if new_idx.is_none()\n            && matches!(\n                self.cross_boundary_behaviour,\n                CrossBoundaryBehaviour::Workspace\n            )\n            && matches!(\n                direction,\n                OperationDirection::Left | OperationDirection::Right\n            )\n        {\n            let workspace_count = if let Some(monitor) = self.focused_monitor() {\n                monitor.workspaces().len()\n            } else {\n                1\n            };\n\n            let next_idx = match direction {\n                OperationDirection::Left => match workspace_idx {\n                    0 => workspace_count - 1,\n                    n => n - 1,\n                },\n                OperationDirection::Right => match workspace_idx {\n                    n if n == workspace_count - 1 => 0,\n                    n => n + 1,\n                },\n                _ => workspace_idx,\n            };\n\n            self.focus_workspace(next_idx)?;\n\n            if let Ok(focused_workspace) = self.focused_workspace_mut()\n                && focused_workspace.monocle_container.is_none()\n            {\n                match direction {\n                    OperationDirection::Left => match focused_workspace.layout {\n                        Layout::Default(layout) => {\n                            let target_index =\n                                layout.rightmost_index(focused_workspace.containers().len());\n                            focused_workspace.focus_container(target_index);\n                        }\n                        Layout::Custom(_) => {\n                            focused_workspace.focus_container(\n                                focused_workspace.containers().len().saturating_sub(1),\n                            );\n                        }\n                    },\n                    OperationDirection::Right => match focused_workspace.layout {\n                        Layout::Default(layout) => {\n                            let target_index =\n                                layout.leftmost_index(focused_workspace.containers().len());\n                            focused_workspace.focus_container(target_index);\n                        }\n                        Layout::Custom(_) => {\n                            focused_workspace.focus_container(0);\n                        }\n                    },\n                    _ => {}\n                };\n            }\n\n            return Ok(());\n        }\n\n        // if there is no container in that direction for this workspace\n        match new_idx {\n            None => {\n                let monitor_idx = self\n                    .monitor_idx_in_direction(direction)\n                    .ok_or_eyre(\"there is no container or monitor in this direction\")?;\n\n                self.focus_monitor(monitor_idx)?;\n                let mouse_follows_focus = self.mouse_follows_focus;\n\n                if let Ok(focused_workspace) = self.focused_workspace_mut() {\n                    if let Some(window) = focused_workspace.maximized_window {\n                        window.focus(mouse_follows_focus)?;\n                        cross_monitor_monocle_or_max = true;\n                    } else if let Some(monocle) = &focused_workspace.monocle_container {\n                        if let Some(window) = monocle.focused_window() {\n                            window.focus(mouse_follows_focus)?;\n                            cross_monitor_monocle_or_max = true;\n                        }\n                    } else if focused_workspace.layer == WorkspaceLayer::Tiling {\n                        match direction {\n                            OperationDirection::Left => match focused_workspace.layout {\n                                Layout::Default(layout) => {\n                                    let target_index = layout\n                                        .rightmost_index(focused_workspace.containers().len());\n                                    focused_workspace.focus_container(target_index);\n                                }\n                                Layout::Custom(_) => {\n                                    focused_workspace.focus_container(\n                                        focused_workspace.containers().len().saturating_sub(1),\n                                    );\n                                }\n                            },\n                            OperationDirection::Right => match focused_workspace.layout {\n                                Layout::Default(layout) => {\n                                    let target_index =\n                                        layout.leftmost_index(focused_workspace.containers().len());\n                                    focused_workspace.focus_container(target_index);\n                                }\n                                Layout::Custom(_) => {\n                                    focused_workspace.focus_container(0);\n                                }\n                            },\n                            _ => {}\n                        };\n                    }\n                }\n            }\n            Some(idx) => {\n                let workspace = self.focused_workspace_mut()?;\n                workspace.focus_container(idx);\n            }\n        }\n\n        if !cross_monitor_monocle_or_max {\n            let ws = self.focused_workspace_mut()?;\n            if ws.is_empty() {\n                // This is to remove focus from the previous monitor\n                let desktop_window = Window::from(WindowsApi::desktop_window()?);\n\n                match WindowsApi::raise_and_focus_window(desktop_window.hwnd) {\n                    Ok(()) => {}\n                    Err(error) => {\n                        tracing::warn!(\"{} {}:{}\", error, file!(), line!());\n                    }\n                }\n            } else if ws.layer == WorkspaceLayer::Floating && !ws.floating_windows().is_empty() {\n                if let Some(window) = ws.focused_floating_window() {\n                    window.focus(self.mouse_follows_focus)?;\n                }\n            } else {\n                ws.layer = WorkspaceLayer::Tiling;\n                if let Ok(focused_window) = self.focused_window() {\n                    focused_window.focus(self.mouse_follows_focus)?;\n                }\n            }\n        }\n\n        Ok(())\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn move_floating_window_in_direction(\n        &mut self,\n        direction: OperationDirection,\n    ) -> eyre::Result<()> {\n        let mouse_follows_focus = self.mouse_follows_focus;\n\n        let mut focused_monitor_work_area = self.focused_monitor_work_area()?;\n        let border_offset = BORDER_OFFSET.load(Ordering::SeqCst);\n        let border_width = BORDER_WIDTH.load(Ordering::SeqCst);\n        focused_monitor_work_area.left += border_offset;\n        focused_monitor_work_area.left += border_width;\n        focused_monitor_work_area.top += border_offset;\n        focused_monitor_work_area.top += border_width;\n        focused_monitor_work_area.right -= border_offset * 2;\n        focused_monitor_work_area.right -= border_width * 2;\n        focused_monitor_work_area.bottom -= border_offset * 2;\n        focused_monitor_work_area.bottom -= border_width * 2;\n\n        let focused_workspace = self.focused_workspace()?;\n        let delta = self.resize_delta;\n\n        let focused_hwnd = WindowsApi::foreground_window()?;\n        for window in focused_workspace.floating_windows().iter() {\n            if window.hwnd == focused_hwnd {\n                let mut rect = WindowsApi::window_rect(window.hwnd)?;\n                match direction {\n                    OperationDirection::Left => {\n                        if rect.left - delta < focused_monitor_work_area.left {\n                            rect.left = focused_monitor_work_area.left;\n                        } else {\n                            rect.left -= delta;\n                        }\n                    }\n                    OperationDirection::Right => {\n                        if rect.left + delta + rect.right\n                            > focused_monitor_work_area.left + focused_monitor_work_area.right\n                        {\n                            rect.left = focused_monitor_work_area.left\n                                + focused_monitor_work_area.right\n                                - rect.right;\n                        } else {\n                            rect.left += delta;\n                        }\n                    }\n                    OperationDirection::Up => {\n                        if rect.top - delta < focused_monitor_work_area.top {\n                            rect.top = focused_monitor_work_area.top;\n                        } else {\n                            rect.top -= delta;\n                        }\n                    }\n                    OperationDirection::Down => {\n                        if rect.top + delta + rect.bottom\n                            > focused_monitor_work_area.top + focused_monitor_work_area.bottom\n                        {\n                            rect.top = focused_monitor_work_area.top\n                                + focused_monitor_work_area.bottom\n                                - rect.bottom;\n                        } else {\n                            rect.top += delta;\n                        }\n                    }\n                }\n\n                WindowsApi::position_window(window.hwnd, &rect, false, true)?;\n                if mouse_follows_focus {\n                    WindowsApi::center_cursor_in_rect(&rect)?;\n                }\n\n                break;\n            }\n        }\n\n        Ok(())\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn move_container_in_direction(\n        &mut self,\n        direction: OperationDirection,\n    ) -> eyre::Result<()> {\n        self.handle_unmanaged_window_behaviour()?;\n\n        let workspace = self.focused_workspace()?;\n        let workspace_idx = self.focused_workspace_idx()?;\n\n        // removing this messes up the monitor / container / window index somewhere\n        // and results in the wrong window getting moved across the monitor boundary\n        if workspace.is_focused_window_monocle_or_maximized()? {\n            bail!(\"ignoring command while active window is in monocle mode or maximized\");\n        }\n\n        tracing::info!(\"moving container\");\n\n        let origin_container_idx = workspace.focused_container_idx();\n        let origin_monitor_idx = self.focused_monitor_idx();\n        let target_container_idx = workspace.new_idx_for_direction(direction);\n\n        // this is for when we are scrolling across workspaces like PaperWM\n        if target_container_idx.is_none()\n            && matches!(\n                self.cross_boundary_behaviour,\n                CrossBoundaryBehaviour::Workspace\n            )\n            && matches!(\n                direction,\n                OperationDirection::Left | OperationDirection::Right\n            )\n        {\n            let workspace_count = if let Some(monitor) = self.focused_monitor() {\n                monitor.workspaces().len()\n            } else {\n                1\n            };\n\n            let next_idx = match direction {\n                OperationDirection::Left => match workspace_idx {\n                    0 => workspace_count - 1,\n                    n => n - 1,\n                },\n                OperationDirection::Right => match workspace_idx {\n                    n if n == workspace_count - 1 => 0,\n                    n => n + 1,\n                },\n                _ => workspace_idx,\n            };\n\n            // passing the direction here is how we handle whether to insert at the front\n            // or the back of the container vecdeque in the target workspace\n            self.move_container_to_workspace(next_idx, true, Some(direction))?;\n            self.update_focused_workspace(self.mouse_follows_focus, true)?;\n\n            return Ok(());\n        }\n\n        match target_container_idx {\n            // If there is nowhere to move on the current workspace, try to move it onto the monitor\n            // in that direction if there is one\n            None => {\n                // Don't do anything if the user has set the MoveBehaviour to NoOp\n                if matches!(self.cross_monitor_move_behaviour, MoveBehaviour::NoOp) {\n                    return Ok(());\n                }\n\n                let target_monitor_idx = self\n                    .monitor_idx_in_direction(direction)\n                    .ok_or_eyre(\"there is no container or monitor in this direction\")?;\n\n                {\n                    // actually move the container to target monitor using the direction\n                    self.move_container_to_monitor(\n                        target_monitor_idx,\n                        None,\n                        true,\n                        Some(direction),\n                    )?;\n\n                    // focus the target monitor\n                    self.focus_monitor(target_monitor_idx)?;\n\n                    // unset monocle container on target workspace if there is one\n                    let mut target_workspace_has_monocle = false;\n                    if let Ok(target_workspace) = self.focused_workspace()\n                        && target_workspace.monocle_container.is_some()\n                    {\n                        target_workspace_has_monocle = true;\n                    }\n\n                    if target_workspace_has_monocle {\n                        self.toggle_monocle()?;\n                    }\n\n                    // get a mutable ref to the focused workspace on the target monitor\n                    let target_workspace = self.focused_workspace_mut()?;\n\n                    // if there is only one container on the target workspace after the insertion\n                    // it means that there won't be one swapped back, so we have to decrement the\n                    // focused position\n                    if target_workspace.containers().len() == 1 {\n                        let origin_workspace =\n                            self.focused_workspace_for_monitor_idx_mut(origin_monitor_idx)?;\n\n                        origin_workspace.focus_container(\n                            origin_workspace.focused_container_idx().saturating_sub(1),\n                        );\n                    }\n                }\n\n                // if our MoveBehaviour is Swap, let's try to send back the window container\n                // whose position which just took over\n                if matches!(self.cross_monitor_move_behaviour, MoveBehaviour::Swap) {\n                    {\n                        let target_workspace = self.focused_workspace_mut()?;\n\n                        // if the target workspace doesn't have more than one container, this means it\n                        // was previously empty, by only doing the second part of the swap when there is\n                        // more than one container, we can fall back to a \"move\" if there is nothing to\n                        // swap with on the target monitor\n                        if target_workspace.containers().len() > 1 {\n                            // remove the container from the target monitor workspace\n                            let target_container = target_workspace\n                                // this is now focused_container_idx + 1 because we have inserted our origin container\n                                .remove_container_by_idx(\n                                    target_workspace.focused_container_idx() + 1,\n                                )\n                                .ok_or_eyre(\"could not remove container at given target index\")?;\n\n                            let origin_workspace =\n                                self.focused_workspace_for_monitor_idx_mut(origin_monitor_idx)?;\n\n                            // insert the container from the target monitor workspace into the origin monitor workspace\n                            // at the same position from which our origin container was removed\n                            origin_workspace\n                                .insert_container_at_idx(origin_container_idx, target_container);\n                        }\n                    }\n                }\n\n                // make sure to update the origin monitor workspace layout because it is no\n                // longer focused so it won't get updated at the end of this fn\n                let offset = self.work_area_offset;\n\n                self.monitors_mut()\n                    .get_mut(origin_monitor_idx)\n                    .ok_or_eyre(\"there is no monitor at this index\")?\n                    .update_focused_workspace(offset)?;\n\n                let a = self\n                    .focused_monitor()\n                    .ok_or_eyre(\"there is no monitor focused monitor\")?\n                    .id;\n                let b = self\n                    .monitors_mut()\n                    .get_mut(origin_monitor_idx)\n                    .ok_or_eyre(\"there is no monitor at this index\")?\n                    .id;\n\n                if !WindowsApi::monitors_have_same_dpi(a, b)? {\n                    self.update_focused_workspace(self.mouse_follows_focus, true)?;\n                }\n            }\n            Some(new_idx) => {\n                let workspace = self.focused_workspace_mut()?;\n                workspace.swap_containers(origin_container_idx, new_idx);\n                workspace.focus_container(new_idx);\n            }\n        }\n\n        self.update_focused_workspace(self.mouse_follows_focus, true)?;\n\n        Ok(())\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn focus_floating_window_in_cycle_direction(\n        &mut self,\n        direction: CycleDirection,\n    ) -> eyre::Result<()> {\n        let mouse_follows_focus = self.mouse_follows_focus;\n        let focused_workspace = self.focused_workspace()?;\n\n        let mut target_idx = None;\n        let len = focused_workspace.floating_windows().len();\n\n        if len > 1 {\n            let focused_hwnd = WindowsApi::foreground_window()?;\n            for (idx, window) in focused_workspace.floating_windows().iter().enumerate() {\n                if window.hwnd == focused_hwnd {\n                    match direction {\n                        CycleDirection::Previous => {\n                            if idx == 0 {\n                                target_idx = Some(len - 1)\n                            } else {\n                                target_idx = Some(idx - 1)\n                            }\n                        }\n                        CycleDirection::Next => {\n                            if idx == len - 1 {\n                                target_idx = Some(0)\n                            } else {\n                                target_idx = Some(idx - 1)\n                            }\n                        }\n                    }\n                }\n            }\n\n            if target_idx.is_none() {\n                target_idx = Some(0);\n            }\n        }\n\n        if let Some(idx) = target_idx\n            && let Some(window) = focused_workspace.floating_windows().get(idx)\n        {\n            window.focus(mouse_follows_focus)?;\n        }\n\n        Ok(())\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn focus_container_in_cycle_direction(\n        &mut self,\n        direction: CycleDirection,\n    ) -> eyre::Result<()> {\n        self.handle_unmanaged_window_behaviour()?;\n\n        tracing::info!(\"focusing container\");\n        let mut maximize_next = false;\n        let mut monocle_next = false;\n\n        if self.focused_workspace_mut()?.maximized_window.is_some() {\n            maximize_next = true;\n            self.unmaximize_window()?;\n        }\n\n        if self.focused_workspace_mut()?.monocle_container.is_some() {\n            monocle_next = true;\n            self.monocle_off()?;\n        }\n\n        let workspace = self.focused_workspace_mut()?;\n\n        let new_idx = workspace\n            .new_idx_for_cycle_direction(direction)\n            .ok_or_eyre(\"this is not a valid direction from the current position\")?;\n\n        workspace.focus_container(new_idx);\n\n        if maximize_next {\n            self.toggle_maximize()?;\n        } else if monocle_next {\n            self.toggle_monocle()?;\n        } else {\n            self.focused_window_mut()?.focus(self.mouse_follows_focus)?;\n        }\n\n        Ok(())\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn move_container_in_cycle_direction(\n        &mut self,\n        direction: CycleDirection,\n    ) -> eyre::Result<()> {\n        self.handle_unmanaged_window_behaviour()?;\n\n        let workspace = self.focused_workspace_mut()?;\n        if workspace.is_focused_window_monocle_or_maximized()? {\n            bail!(\"ignoring command while active window is in monocle mode or maximized\");\n        }\n\n        tracing::info!(\"moving container\");\n\n        let current_idx = workspace.focused_container_idx();\n        let new_idx = workspace\n            .new_idx_for_cycle_direction(direction)\n            .ok_or_eyre(\"this is not a valid direction from the current position\")?;\n\n        workspace.swap_containers(current_idx, new_idx);\n        workspace.focus_container(new_idx);\n        self.update_focused_workspace(self.mouse_follows_focus, true)\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn cycle_container_window_in_direction(\n        &mut self,\n        direction: CycleDirection,\n    ) -> eyre::Result<()> {\n        self.handle_unmanaged_window_behaviour()?;\n\n        tracing::info!(\"cycling container windows\");\n\n        let container =\n            if let Some(container) = &mut self.focused_workspace_mut()?.monocle_container {\n                container\n            } else {\n                self.focused_container_mut()?\n            };\n\n        let len = NonZeroUsize::new(container.windows().len())\n            .ok_or_eyre(\"there must be at least one window in a container\")?;\n\n        if len.get() == 1 {\n            bail!(\"there is only one window in this container\");\n        }\n\n        let current_idx = container.focused_window_idx();\n        let next_idx = direction.next_idx(current_idx, len);\n\n        container.focus_window(next_idx);\n        container.load_focused_window();\n\n        if let Some(window) = container.focused_window() {\n            window.focus(self.mouse_follows_focus)?;\n        }\n\n        self.update_focused_workspace(self.mouse_follows_focus, true)\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn cycle_container_window_index_in_direction(\n        &mut self,\n        direction: CycleDirection,\n    ) -> eyre::Result<()> {\n        self.handle_unmanaged_window_behaviour()?;\n\n        tracing::info!(\"cycling container window index\");\n\n        let container =\n            if let Some(container) = &mut self.focused_workspace_mut()?.monocle_container {\n                container\n            } else {\n                self.focused_container_mut()?\n            };\n\n        let len = NonZeroUsize::new(container.windows().len())\n            .ok_or_eyre(\"there must be at least one window in a container\")?;\n\n        if len.get() == 1 {\n            bail!(\"there is only one window in this container\");\n        }\n\n        let current_idx = container.focused_window_idx();\n        let next_idx = direction.next_idx(current_idx, len);\n        container.windows_mut().swap(current_idx, next_idx);\n\n        container.focus_window(next_idx);\n        container.load_focused_window();\n\n        if let Some(window) = container.focused_window() {\n            window.focus(self.mouse_follows_focus)?;\n        }\n\n        self.update_focused_workspace(self.mouse_follows_focus, true)\n    }\n    #[tracing::instrument(skip(self))]\n    pub fn focus_container_window(&mut self, idx: usize) -> eyre::Result<()> {\n        self.handle_unmanaged_window_behaviour()?;\n\n        tracing::info!(\"focusing container window at index {idx}\");\n\n        let container =\n            if let Some(container) = &mut self.focused_workspace_mut()?.monocle_container {\n                container\n            } else {\n                self.focused_container_mut()?\n            };\n\n        let len = NonZeroUsize::new(container.windows().len())\n            .ok_or_eyre(\"there must be at least one window in a container\")?;\n\n        if len.get() == 1 && idx != 0 {\n            bail!(\"there is only one window in this container\");\n        }\n\n        if container.windows().get(idx).is_none() {\n            bail!(\"there is no window in this container at index {idx}\");\n        }\n\n        container.focus_window(idx);\n        container.load_focused_window();\n\n        if let Some(window) = container.focused_window() {\n            window.focus(self.mouse_follows_focus)?;\n        }\n\n        self.update_focused_workspace(self.mouse_follows_focus, true)\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn stack_all(&mut self) -> eyre::Result<()> {\n        self.handle_unmanaged_window_behaviour()?;\n        tracing::info!(\"stacking all windows on workspace\");\n\n        let workspace = self.focused_workspace_mut()?;\n\n        let mut focused_hwnd = None;\n        if let Some(container) = workspace.focused_container()\n            && let Some(window) = container.focused_window()\n        {\n            focused_hwnd = Some(window.hwnd);\n        }\n\n        let workspace_hwnds =\n            workspace\n                .containers()\n                .iter()\n                .fold(VecDeque::new(), |mut hwnds, c| {\n                    hwnds.extend(c.windows().clone());\n                    hwnds\n                });\n\n        let mut container = Container::default();\n        *container.windows_mut() = workspace_hwnds;\n        *workspace.containers_mut() = VecDeque::from([container]);\n        workspace.focus_container(0);\n\n        if let Some(hwnd) = focused_hwnd\n            && let Some(c) = workspace.focused_container_mut()\n            && let Some(w_idx_to_focus) = c.idx_for_window(hwnd)\n        {\n            c.focus_window(w_idx_to_focus);\n            c.load_focused_window();\n        }\n        self.update_focused_workspace(self.mouse_follows_focus, true)\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn unstack_all(&mut self, update_workspace: bool) -> eyre::Result<()> {\n        self.handle_unmanaged_window_behaviour()?;\n        tracing::info!(\"unstacking all windows in container\");\n\n        let workspace = self.focused_workspace_mut()?;\n\n        let mut focused_hwnd = None;\n        if let Some(container) = workspace.focused_container()\n            && let Some(window) = container.focused_window()\n        {\n            focused_hwnd = Some(window.hwnd);\n        }\n\n        let initial_focused_container_index = workspace.focused_container_idx();\n        let mut focused_container = workspace.focused_container().cloned();\n\n        while let Some(focused) = &focused_container {\n            if focused.windows().len() > 1 {\n                workspace.new_container_for_focused_window()?;\n                workspace.focus_container(initial_focused_container_index);\n                focused_container = workspace.focused_container().cloned();\n            } else {\n                focused_container = None;\n            }\n        }\n\n        if let Some(hwnd) = focused_hwnd {\n            workspace.focus_container_by_window(hwnd)?;\n        }\n\n        if update_workspace {\n            self.update_focused_workspace(self.mouse_follows_focus, true)?;\n        }\n\n        Ok(())\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn add_window_to_container(&mut self, direction: OperationDirection) -> eyre::Result<()> {\n        self.handle_unmanaged_window_behaviour()?;\n\n        tracing::info!(\"adding window to container\");\n\n        let workspace = self.focused_workspace_mut()?;\n        let len = NonZeroUsize::new(workspace.containers_mut().len())\n            .ok_or_eyre(\"there must be at least one container\")?;\n        let current_container_idx = workspace.focused_container_idx();\n\n        let is_valid = direction\n            .destination(\n                workspace.layout.as_boxed_direction().as_ref(),\n                workspace.layout_flip,\n                workspace.focused_container_idx(),\n                len,\n                workspace.layout_options,\n            )\n            .is_some();\n\n        if is_valid {\n            let new_idx = workspace\n                .new_idx_for_direction(direction)\n                .ok_or_eyre(\"this is not a valid direction from the current position\")?;\n\n            let mut changed_focus = false;\n\n            let adjusted_new_index = if new_idx > current_container_idx\n                && !matches!(\n                    workspace.layout,\n                    Layout::Default(DefaultLayout::Grid)\n                        | Layout::Default(DefaultLayout::UltrawideVerticalStack)\n                ) {\n                workspace.focus_container(new_idx);\n                changed_focus = true;\n                new_idx.saturating_sub(1)\n            } else {\n                new_idx\n            };\n\n            let mut target_container_is_stack = false;\n\n            if let Some(container) = workspace.containers().get(adjusted_new_index)\n                && container.windows().len() > 1\n            {\n                target_container_is_stack = true;\n            }\n\n            if let Some(current) = workspace.focused_container() {\n                if current.windows().len() > 1 && !target_container_is_stack {\n                    workspace.focus_container(adjusted_new_index);\n                    changed_focus = true;\n                    workspace.move_window_to_container(current_container_idx)?;\n                } else {\n                    workspace.move_window_to_container(adjusted_new_index)?;\n                }\n            }\n\n            if changed_focus && let Some(container) = workspace.focused_container_mut() {\n                container.load_focused_window();\n                if let Some(window) = container.focused_window() {\n                    window.focus(self.mouse_follows_focus)?;\n                }\n            }\n\n            self.update_focused_workspace(self.mouse_follows_focus, false)?;\n        }\n\n        Ok(())\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn promote_container_to_front(&mut self) -> eyre::Result<()> {\n        self.handle_unmanaged_window_behaviour()?;\n\n        let workspace = self.focused_workspace_mut()?;\n\n        if matches!(workspace.layout, Layout::Default(DefaultLayout::Grid)) {\n            tracing::debug!(\"ignoring promote command for grid layout\");\n            return Ok(());\n        }\n\n        tracing::info!(\"promoting container\");\n\n        workspace.promote_container()?;\n        self.update_focused_workspace(self.mouse_follows_focus, true)\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn promote_container_swap(&mut self) -> eyre::Result<()> {\n        self.handle_unmanaged_window_behaviour()?;\n\n        let workspace = self.focused_workspace_mut()?;\n        let focused_container_idx = workspace.focused_container_idx();\n\n        let primary_idx = match &workspace.layout {\n            Layout::Default(_) => 0,\n            Layout::Custom(layout) => layout.first_container_idx(\n                layout\n                    .primary_idx()\n                    .ok_or_eyre(\"this custom layout does not have a primary column\")?,\n            ),\n        };\n\n        if matches!(workspace.layout, Layout::Default(DefaultLayout::Grid)) {\n            tracing::debug!(\"ignoring promote-swap command for grid layout\");\n            return Ok(());\n        }\n\n        let primary_tile_is_focused = focused_container_idx == primary_idx;\n\n        if primary_tile_is_focused && let Some(swap_idx) = workspace.promotion_swap_container_idx {\n            workspace.swap_containers(focused_container_idx, swap_idx);\n        } else {\n            workspace.promotion_swap_container_idx = Some(focused_container_idx);\n            workspace.swap_containers(focused_container_idx, primary_idx);\n        }\n\n        self.update_focused_workspace(self.mouse_follows_focus, true)\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn promote_focus_to_front(&mut self) -> eyre::Result<()> {\n        self.handle_unmanaged_window_behaviour()?;\n\n        let workspace = self.focused_workspace_mut()?;\n\n        if matches!(workspace.layout, Layout::Default(DefaultLayout::Grid)) {\n            tracing::info!(\"ignoring promote focus command for grid layout\");\n            return Ok(());\n        }\n\n        tracing::info!(\"promoting focus\");\n\n        let target_idx = match &workspace.layout {\n            Layout::Default(_) => 0,\n            Layout::Custom(custom) => custom\n                .first_container_idx(custom.primary_idx().map_or(0, |primary_idx| primary_idx)),\n        };\n\n        workspace.focus_container(target_idx);\n        self.update_focused_workspace(self.mouse_follows_focus, true)\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn remove_window_from_container(&mut self) -> eyre::Result<()> {\n        self.handle_unmanaged_window_behaviour()?;\n\n        tracing::info!(\"removing window\");\n\n        if self.focused_container()?.windows().len() == 1 {\n            bail!(\"a container must have at least one window\");\n        }\n\n        let workspace = self.focused_workspace_mut()?;\n\n        workspace.new_container_for_focused_window()?;\n        self.update_focused_workspace(self.mouse_follows_focus, false)\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn toggle_tiling(&mut self) -> eyre::Result<()> {\n        let workspace = self.focused_workspace_mut()?;\n        workspace.tile = !workspace.tile;\n        self.update_focused_workspace(false, false)\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn toggle_float(&mut self, force_float: bool) -> eyre::Result<()> {\n        let hwnd = WindowsApi::foreground_window()?;\n        let workspace = self.focused_workspace_mut()?;\n        if workspace.monocle_container.is_some() {\n            tracing::warn!(\"ignoring toggle-float command while workspace has a monocle container\");\n            return Ok(());\n        }\n\n        let mut is_floating_window = false;\n\n        for window in workspace.floating_windows() {\n            if window.hwnd == hwnd {\n                is_floating_window = true;\n            }\n        }\n\n        if is_floating_window && !force_float {\n            workspace.layer = WorkspaceLayer::Tiling;\n            self.unfloat_window()?;\n        } else {\n            workspace.layer = WorkspaceLayer::Floating;\n            self.float_window()?;\n        }\n\n        self.update_focused_workspace(is_floating_window, true)\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn toggle_lock(&mut self) -> eyre::Result<()> {\n        let workspace = self.focused_workspace_mut()?;\n        if let Some(container) = workspace.focused_container_mut() {\n            // Toggle the locked flag\n            container.locked = !container.locked;\n        }\n        Ok(())\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn float_window(&mut self) -> eyre::Result<()> {\n        tracing::info!(\"floating window\");\n\n        let work_area = self.focused_monitor_work_area()?;\n\n        let toggle_float_placement = self.window_management_behaviour.toggle_float_placement;\n\n        let workspace = self.focused_workspace_mut()?;\n        workspace.new_floating_window()?;\n\n        let window = workspace\n            .floating_windows_mut()\n            .back_mut()\n            .ok_or_eyre(\"there is no floating window\")?;\n\n        if toggle_float_placement.should_center() {\n            window.center(&work_area, toggle_float_placement.should_resize())?;\n        }\n        window.focus(self.mouse_follows_focus)?;\n\n        Ok(())\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn unfloat_window(&mut self) -> eyre::Result<()> {\n        tracing::info!(\"unfloating window\");\n\n        let workspace = self.focused_workspace_mut()?;\n        workspace.new_container_for_floating_window()\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn toggle_monocle(&mut self) -> eyre::Result<()> {\n        self.handle_unmanaged_window_behaviour()?;\n\n        let workspace = self.focused_workspace()?;\n        match workspace.monocle_container {\n            None => self.monocle_on()?,\n            Some(_) => self.monocle_off()?,\n        }\n\n        self.update_focused_workspace(true, true)?;\n\n        Ok(())\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn monocle_on(&mut self) -> eyre::Result<()> {\n        tracing::info!(\"enabling monocle\");\n\n        let workspace = self.focused_workspace_mut()?;\n        workspace.new_monocle_container()?;\n\n        for container in workspace.containers_mut() {\n            container.hide(None);\n        }\n\n        for window in workspace.floating_windows_mut() {\n            window.hide();\n        }\n\n        Ok(())\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn monocle_off(&mut self) -> eyre::Result<()> {\n        tracing::info!(\"disabling monocle\");\n\n        let workspace = self.focused_workspace_mut()?;\n\n        for container in workspace.containers_mut() {\n            container.restore();\n        }\n\n        for window in workspace.floating_windows_mut() {\n            window.restore();\n        }\n\n        workspace.reintegrate_monocle_container()\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn cycle_monocle(&mut self, direction: CycleDirection) -> eyre::Result<()> {\n        tracing::info!(\"cycling monocle container\");\n\n        if self.focused_workspace()?.containers().is_empty() {\n            return Ok(());\n        }\n\n        self.focused_workspace_mut()?\n            .cycle_monocle_container(direction)?;\n\n        for container in self.focused_workspace_mut()?.containers_mut() {\n            container.hide(None);\n        }\n\n        // borders were getting funny during cycles, can't be bothered to root cause it\n        border_manager::destroy_all_borders()?;\n\n        self.update_focused_workspace(true, true)\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn toggle_maximize(&mut self) -> eyre::Result<()> {\n        self.handle_unmanaged_window_behaviour()?;\n\n        let workspace = self.focused_workspace_mut()?;\n\n        match workspace.maximized_window {\n            None => self.maximize_window()?,\n            Some(_) => self.unmaximize_window()?,\n        }\n\n        self.update_focused_workspace(true, false)\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn maximize_window(&mut self) -> eyre::Result<()> {\n        tracing::info!(\"maximizing windowj\");\n\n        let workspace = self.focused_workspace_mut()?;\n        workspace.new_maximized_window()\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn unmaximize_window(&mut self) -> eyre::Result<()> {\n        tracing::info!(\"unmaximizing window\");\n\n        let workspace = self.focused_workspace_mut()?;\n        workspace.reintegrate_maximized_window()\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn flip_layout(&mut self, layout_flip: Axis) -> eyre::Result<()> {\n        let workspace = self.focused_workspace_mut()?;\n\n        tracing::info!(\"flipping layout\");\n\n        #[allow(clippy::match_same_arms)]\n        match workspace.layout_flip {\n            None => {\n                workspace.layout_flip = Option::from(layout_flip);\n            }\n            Some(current_layout_flip) => {\n                match current_layout_flip {\n                    Axis::Horizontal => match layout_flip {\n                        Axis::Horizontal => workspace.layout_flip = None,\n                        Axis::Vertical => {\n                            workspace.layout_flip = Option::from(Axis::HorizontalAndVertical)\n                        }\n                        Axis::HorizontalAndVertical => {\n                            workspace.layout_flip = Option::from(Axis::HorizontalAndVertical)\n                        }\n                    },\n                    Axis::Vertical => match layout_flip {\n                        Axis::Horizontal => {\n                            workspace.layout_flip = Option::from(Axis::HorizontalAndVertical)\n                        }\n                        Axis::Vertical => workspace.layout_flip = None,\n                        Axis::HorizontalAndVertical => {\n                            workspace.layout_flip = Option::from(Axis::HorizontalAndVertical)\n                        }\n                    },\n                    Axis::HorizontalAndVertical => match layout_flip {\n                        Axis::Horizontal => workspace.layout_flip = Option::from(Axis::Vertical),\n                        Axis::Vertical => workspace.layout_flip = Option::from(Axis::Horizontal),\n                        Axis::HorizontalAndVertical => workspace.layout_flip = None,\n                    },\n                };\n            }\n        }\n\n        self.update_focused_workspace(false, false)\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn change_workspace_layout_default(&mut self, layout: DefaultLayout) -> eyre::Result<()> {\n        tracing::info!(\"changing layout\");\n\n        let monitor_count = self.monitors().len();\n        let workspace = self.focused_workspace_mut()?;\n\n        if monitor_count > 1 && matches!(layout, DefaultLayout::Scrolling) {\n            tracing::warn!(\n                \"scrolling layout is only supported for a single monitor; not changing layout\"\n            );\n            return Ok(());\n        }\n\n        match &workspace.layout {\n            Layout::Default(_) => {}\n            Layout::Custom(layout) => {\n                let primary_idx = layout.first_container_idx(\n                    layout\n                        .primary_idx()\n                        .ok_or_eyre(\"this custom layout does not have a primary column\")?,\n                );\n\n                if !workspace.containers().is_empty() && primary_idx < workspace.containers().len()\n                {\n                    workspace.swap_containers(0, primary_idx);\n                }\n            }\n        }\n\n        workspace.layout = Layout::Default(layout);\n        self.update_focused_workspace(self.mouse_follows_focus, false)\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn cycle_layout(&mut self, direction: CycleDirection) -> eyre::Result<()> {\n        tracing::info!(\"cycling layout\");\n\n        let workspace = self.focused_workspace_mut()?;\n        let current_layout = &workspace.layout;\n\n        match current_layout {\n            Layout::Default(current) => {\n                let new_layout = match direction {\n                    CycleDirection::Previous => current.cycle_previous(),\n                    CycleDirection::Next => current.cycle_next(),\n                };\n\n                tracing::info!(\"next layout: {new_layout}\");\n                workspace.layout = Layout::Default(new_layout);\n            }\n            Layout::Custom(_) => {}\n        }\n\n        self.update_focused_workspace(self.mouse_follows_focus, false)\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn change_workspace_custom_layout<P>(&mut self, path: P) -> eyre::Result<()>\n    where\n        P: AsRef<Path> + std::fmt::Debug,\n    {\n        tracing::info!(\"changing layout\");\n\n        let layout = CustomLayout::from_path(path)?;\n        let workspace = self.focused_workspace_mut()?;\n\n        match workspace.layout {\n            Layout::Default(_) => {\n                let primary_idx = layout.first_container_idx(\n                    layout\n                        .primary_idx()\n                        .ok_or_eyre(\"this custom layout does not have a primary column\")?,\n                );\n\n                if !workspace.containers().is_empty() && primary_idx < workspace.containers().len()\n                {\n                    workspace.swap_containers(0, primary_idx);\n                }\n            }\n            Layout::Custom(_) => {}\n        }\n\n        workspace.layout = Layout::Custom(layout);\n        workspace.layout_flip = None;\n        self.update_focused_workspace(self.mouse_follows_focus, false)\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn adjust_workspace_padding(\n        &mut self,\n        sizing: Sizing,\n        adjustment: i32,\n    ) -> eyre::Result<()> {\n        tracing::info!(\"adjusting workspace padding\");\n\n        let workspace = self.focused_workspace_mut()?;\n\n        let padding = workspace\n            .workspace_padding\n            .ok_or_eyre(\"there is no workspace padding\")?;\n\n        workspace.workspace_padding = Option::from(sizing.adjust_by(padding, adjustment));\n\n        self.update_focused_workspace(false, false)\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn adjust_container_padding(\n        &mut self,\n        sizing: Sizing,\n        adjustment: i32,\n    ) -> eyre::Result<()> {\n        tracing::info!(\"adjusting container padding\");\n\n        let workspace = self.focused_workspace_mut()?;\n\n        let padding = workspace\n            .container_padding\n            .ok_or_eyre(\"there is no container padding\")?;\n\n        workspace.container_padding = Option::from(sizing.adjust_by(padding, adjustment));\n\n        self.update_focused_workspace(false, false)\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn set_workspace_tiling(\n        &mut self,\n        monitor_idx: usize,\n        workspace_idx: usize,\n        tile: bool,\n    ) -> eyre::Result<()> {\n        let monitor = self\n            .monitors_mut()\n            .get_mut(monitor_idx)\n            .ok_or_eyre(\"there is no monitor\")?;\n\n        let workspace = monitor\n            .workspaces_mut()\n            .get_mut(workspace_idx)\n            .ok_or_eyre(\"there is no monitor\")?;\n\n        workspace.tile = tile;\n\n        self.update_focused_workspace(false, false)\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn add_workspace_layout_default_rule(\n        &mut self,\n        monitor_idx: usize,\n        workspace_idx: usize,\n        at_container_count: usize,\n        layout: DefaultLayout,\n    ) -> eyre::Result<()> {\n        tracing::info!(\"setting workspace layout\");\n\n        let focused_monitor_idx = self.focused_monitor_idx();\n\n        let monitor = self\n            .monitors_mut()\n            .get_mut(monitor_idx)\n            .ok_or_eyre(\"there is no monitor\")?;\n\n        let focused_workspace_idx = monitor.focused_workspace_idx();\n\n        let workspace = monitor\n            .workspaces_mut()\n            .get_mut(workspace_idx)\n            .ok_or_eyre(\"there is no monitor\")?;\n\n        let rules: &mut Vec<(usize, Layout)> = &mut workspace.layout_rules;\n        rules.retain(|pair| pair.0 != at_container_count);\n        rules.push((at_container_count, Layout::Default(layout)));\n        rules.sort_by(|a, b| a.0.cmp(&b.0));\n\n        // If this is the focused workspace on a non-focused screen, let's update it\n        if focused_monitor_idx != monitor_idx && focused_workspace_idx == workspace_idx {\n            workspace.update()?;\n            Ok(())\n        } else {\n            Ok(self.update_focused_workspace(false, false)?)\n        }\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn add_workspace_layout_custom_rule<P>(\n        &mut self,\n        monitor_idx: usize,\n        workspace_idx: usize,\n        at_container_count: usize,\n        path: P,\n    ) -> eyre::Result<()>\n    where\n        P: AsRef<Path> + std::fmt::Debug,\n    {\n        tracing::info!(\"setting workspace layout\");\n\n        let focused_monitor_idx = self.focused_monitor_idx();\n\n        let monitor = self\n            .monitors_mut()\n            .get_mut(monitor_idx)\n            .ok_or_eyre(\"there is no monitor\")?;\n\n        let focused_workspace_idx = monitor.focused_workspace_idx();\n\n        let workspace = monitor\n            .workspaces_mut()\n            .get_mut(workspace_idx)\n            .ok_or_eyre(\"there is no monitor\")?;\n\n        let layout = CustomLayout::from_path(path)?;\n\n        let rules: &mut Vec<(usize, Layout)> = &mut workspace.layout_rules;\n        rules.retain(|pair| pair.0 != at_container_count);\n        rules.push((at_container_count, Layout::Custom(layout)));\n        rules.sort_by(|a, b| a.0.cmp(&b.0));\n\n        // If this is the focused workspace on a non-focused screen, let's update it\n        if focused_monitor_idx != monitor_idx && focused_workspace_idx == workspace_idx {\n            workspace.update()?;\n            Ok(())\n        } else {\n            Ok(self.update_focused_workspace(false, false)?)\n        }\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn clear_workspace_layout_rules(\n        &mut self,\n        monitor_idx: usize,\n        workspace_idx: usize,\n    ) -> eyre::Result<()> {\n        tracing::info!(\"setting workspace layout\");\n\n        let focused_monitor_idx = self.focused_monitor_idx();\n\n        let monitor = self\n            .monitors_mut()\n            .get_mut(monitor_idx)\n            .ok_or_eyre(\"there is no monitor\")?;\n\n        let focused_workspace_idx = monitor.focused_workspace_idx();\n\n        let workspace = monitor\n            .workspaces_mut()\n            .get_mut(workspace_idx)\n            .ok_or_eyre(\"there is no monitor\")?;\n\n        let rules: &mut Vec<(usize, Layout)> = &mut workspace.layout_rules;\n        rules.clear();\n\n        // If this is the focused workspace on a non-focused screen, let's update it\n        if focused_monitor_idx != monitor_idx && focused_workspace_idx == workspace_idx {\n            workspace.update()?;\n            Ok(())\n        } else {\n            Ok(self.update_focused_workspace(false, false)?)\n        }\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn set_workspace_layout_default(\n        &mut self,\n        monitor_idx: usize,\n        workspace_idx: usize,\n        layout: DefaultLayout,\n    ) -> eyre::Result<()> {\n        tracing::info!(\"setting workspace layout\");\n\n        let focused_monitor_idx = self.focused_monitor_idx();\n\n        let monitor = self\n            .monitors_mut()\n            .get_mut(monitor_idx)\n            .ok_or_eyre(\"there is no monitor\")?;\n\n        let focused_workspace_idx = monitor.focused_workspace_idx();\n\n        let workspace = monitor\n            .workspaces_mut()\n            .get_mut(workspace_idx)\n            .ok_or_eyre(\"there is no monitor\")?;\n\n        workspace.layout = Layout::Default(layout);\n\n        // If this is the focused workspace on a non-focused screen, let's update it\n        if focused_monitor_idx != monitor_idx && focused_workspace_idx == workspace_idx {\n            workspace.update()?;\n            Ok(())\n        } else {\n            Ok(self.update_focused_workspace(false, false)?)\n        }\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn set_workspace_layout_custom<P>(\n        &mut self,\n        monitor_idx: usize,\n        workspace_idx: usize,\n        path: P,\n    ) -> eyre::Result<()>\n    where\n        P: AsRef<Path> + std::fmt::Debug,\n    {\n        tracing::info!(\"setting workspace layout\");\n        let layout = CustomLayout::from_path(path)?;\n        let focused_monitor_idx = self.focused_monitor_idx();\n\n        let monitor = self\n            .monitors_mut()\n            .get_mut(monitor_idx)\n            .ok_or_eyre(\"there is no monitor\")?;\n\n        let focused_workspace_idx = monitor.focused_workspace_idx();\n\n        let workspace = monitor\n            .workspaces_mut()\n            .get_mut(workspace_idx)\n            .ok_or_eyre(\"there is no monitor\")?;\n\n        workspace.layout = Layout::Custom(layout);\n        workspace.layout_flip = None;\n\n        // If this is the focused workspace on a non-focused screen, let's update it\n        if focused_monitor_idx != monitor_idx && focused_workspace_idx == workspace_idx {\n            workspace.update()?;\n            Ok(())\n        } else {\n            Ok(self.update_focused_workspace(false, false)?)\n        }\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn ensure_workspaces_for_monitor(\n        &mut self,\n        monitor_idx: usize,\n        workspace_count: usize,\n    ) -> eyre::Result<()> {\n        tracing::info!(\"ensuring workspace count\");\n\n        let monitor = self\n            .monitors_mut()\n            .get_mut(monitor_idx)\n            .ok_or_eyre(\"there is no monitor\")?;\n\n        monitor.ensure_workspace_count(workspace_count);\n\n        Ok(())\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn ensure_named_workspaces_for_monitor(\n        &mut self,\n        monitor_idx: usize,\n        names: &Vec<String>,\n    ) -> eyre::Result<()> {\n        tracing::info!(\"ensuring workspace count\");\n\n        let monitor = self\n            .monitors_mut()\n            .get_mut(monitor_idx)\n            .ok_or_eyre(\"there is no monitor\")?;\n\n        monitor.ensure_workspace_count(names.len());\n\n        for (workspace_idx, name) in names.iter().enumerate() {\n            if let Some(workspace) = monitor.workspaces_mut().get_mut(workspace_idx) {\n                workspace.name = Option::from(name.clone());\n            }\n        }\n\n        Ok(())\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn set_workspace_padding(\n        &mut self,\n        monitor_idx: usize,\n        workspace_idx: usize,\n        size: i32,\n    ) -> eyre::Result<()> {\n        tracing::info!(\"setting workspace padding\");\n\n        let monitor = self\n            .monitors_mut()\n            .get_mut(monitor_idx)\n            .ok_or_eyre(\"there is no monitor\")?;\n\n        let workspace = monitor\n            .workspaces_mut()\n            .get_mut(workspace_idx)\n            .ok_or_eyre(\"there is no monitor\")?;\n\n        workspace.workspace_padding = Option::from(size);\n\n        self.update_focused_workspace(false, false)\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn set_workspace_name(\n        &mut self,\n        monitor_idx: usize,\n        workspace_idx: usize,\n        name: String,\n    ) -> eyre::Result<()> {\n        tracing::info!(\"setting workspace name\");\n\n        let monitor = self\n            .monitors_mut()\n            .get_mut(monitor_idx)\n            .ok_or_eyre(\"there is no monitor\")?;\n\n        let workspace = monitor\n            .workspaces_mut()\n            .get_mut(workspace_idx)\n            .ok_or_eyre(\"there is no monitor\")?;\n\n        workspace.name = Option::from(name.clone());\n        monitor.workspace_names.insert(workspace_idx, name);\n\n        Ok(())\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn set_container_padding(\n        &mut self,\n        monitor_idx: usize,\n        workspace_idx: usize,\n        size: i32,\n    ) -> eyre::Result<()> {\n        tracing::info!(\"setting container padding\");\n\n        let monitor = self\n            .monitors_mut()\n            .get_mut(monitor_idx)\n            .ok_or_eyre(\"there is no monitor\")?;\n\n        let workspace = monitor\n            .workspaces_mut()\n            .get_mut(workspace_idx)\n            .ok_or_eyre(\"there is no monitor\")?;\n\n        workspace.container_padding = Option::from(size);\n\n        self.update_focused_workspace(false, false)\n    }\n\n    pub fn focused_monitor_size(&self) -> eyre::Result<Rect> {\n        Ok(self\n            .focused_monitor()\n            .ok_or_eyre(\"there is no monitor\")?\n            .size)\n    }\n\n    pub fn focused_monitor_work_area(&self) -> eyre::Result<Rect> {\n        Ok(self\n            .focused_monitor()\n            .ok_or_eyre(\"there is no monitor\")?\n            .work_area_size)\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn focus_monitor(&mut self, idx: usize) -> eyre::Result<()> {\n        tracing::info!(\"focusing monitor\");\n\n        if self.monitors().get(idx).is_some() {\n            self.monitors.focus(idx);\n        } else {\n            bail!(\"this is not a valid monitor index\");\n        }\n\n        Ok(())\n    }\n\n    pub fn monitor_idx_from_window(&mut self, window: Window) -> Option<usize> {\n        let hmonitor = WindowsApi::monitor_from_window(window.hwnd);\n\n        for (i, monitor) in self.monitors().iter().enumerate() {\n            if monitor.id == hmonitor {\n                return Option::from(i);\n            }\n        }\n\n        // our hmonitor might be stale, so if we didn't return above, try querying via the latest\n        // info taken from win32_display_data and update our hmonitor while we're at it\n        if let Ok(latest) = WindowsApi::monitor(hmonitor) {\n            for (i, monitor) in self.monitors_mut().iter_mut().enumerate() {\n                if monitor.device_id == latest.device_id {\n                    monitor.id = latest.id;\n                    return Option::from(i);\n                }\n            }\n        }\n\n        None\n    }\n\n    pub fn monitor_idx_from_current_pos(&mut self) -> Option<usize> {\n        let hmonitor = WindowsApi::monitor_from_point(WindowsApi::cursor_pos().ok()?);\n\n        for (i, monitor) in self.monitors().iter().enumerate() {\n            if monitor.id == hmonitor {\n                return Option::from(i);\n            }\n        }\n\n        // our hmonitor might be stale, so if we didn't return above, try querying via the latest\n        // info taken from win32_display_data and update our hmonitor while we're at it\n        if let Ok(latest) = WindowsApi::monitor(hmonitor) {\n            for (i, monitor) in self.monitors_mut().iter_mut().enumerate() {\n                if monitor.device_id == latest.device_id {\n                    monitor.id = latest.id;\n                    return Option::from(i);\n                }\n            }\n        }\n\n        None\n    }\n\n    pub fn focused_workspace_idx(&self) -> eyre::Result<usize> {\n        Ok(self\n            .focused_monitor()\n            .ok_or_eyre(\"there is no monitor\")?\n            .focused_workspace_idx())\n    }\n\n    pub fn focused_workspace(&self) -> eyre::Result<&Workspace> {\n        self.focused_monitor()\n            .ok_or_eyre(\"there is no monitor\")?\n            .focused_workspace()\n            .ok_or_eyre(\"there is no workspace\")\n    }\n\n    pub fn focused_workspace_mut(&mut self) -> eyre::Result<&mut Workspace> {\n        self.focused_monitor_mut()\n            .ok_or_eyre(\"there is no monitor\")?\n            .focused_workspace_mut()\n            .ok_or_eyre(\"there is no workspace\")\n    }\n\n    pub fn focused_workspace_idx_for_monitor_idx(&self, idx: usize) -> eyre::Result<usize> {\n        Ok(self\n            .monitors()\n            .get(idx)\n            .ok_or_eyre(\"there is no monitor at this index\")?\n            .focused_workspace_idx())\n    }\n\n    pub fn focused_workspace_for_monitor_idx(&self, idx: usize) -> eyre::Result<&Workspace> {\n        self.monitors()\n            .get(idx)\n            .ok_or_eyre(\"there is no monitor at this index\")?\n            .focused_workspace()\n            .ok_or_eyre(\"there is no workspace\")\n    }\n\n    pub fn focused_workspace_for_monitor_idx_mut(\n        &mut self,\n        idx: usize,\n    ) -> eyre::Result<&mut Workspace> {\n        self.monitors_mut()\n            .get_mut(idx)\n            .ok_or_eyre(\"there is no monitor at this index\")?\n            .focused_workspace_mut()\n            .ok_or_eyre(\"there is no workspace\")\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn focus_workspace(&mut self, idx: usize) -> eyre::Result<()> {\n        tracing::info!(\"focusing workspace\");\n\n        let mouse_follows_focus = self.mouse_follows_focus;\n        let monitor = self\n            .focused_monitor_mut()\n            .ok_or_eyre(\"there is no workspace\")?;\n\n        monitor.focus_workspace(idx)?;\n        monitor.load_focused_workspace(mouse_follows_focus)?;\n\n        self.update_focused_workspace(false, true)\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn monitor_workspace_index_by_name(&mut self, name: &str) -> Option<(usize, usize)> {\n        tracing::info!(\"looking up workspace by name\");\n\n        for (monitor_idx, monitor) in self.monitors().iter().enumerate() {\n            for (workspace_idx, workspace) in monitor.workspaces().iter().enumerate() {\n                if let Some(workspace_name) = &workspace.name\n                    && workspace_name == name\n                {\n                    return Option::from((monitor_idx, workspace_idx));\n                }\n            }\n        }\n\n        None\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn new_workspace(&mut self) -> eyre::Result<()> {\n        tracing::info!(\"adding new workspace\");\n\n        let mouse_follows_focus = self.mouse_follows_focus;\n        let monitor = self\n            .focused_monitor_mut()\n            .ok_or_eyre(\"there is no workspace\")?;\n\n        monitor.focus_workspace(monitor.new_workspace_idx())?;\n        monitor.load_focused_workspace(mouse_follows_focus)?;\n\n        self.update_focused_workspace(self.mouse_follows_focus, false)\n    }\n\n    pub fn focused_container(&self) -> eyre::Result<&Container> {\n        self.focused_workspace()?\n            .focused_container()\n            .ok_or_eyre(\"there is no container\")\n    }\n\n    pub fn focused_container_idx(&self) -> eyre::Result<usize> {\n        Ok(self.focused_workspace()?.focused_container_idx())\n    }\n\n    pub fn focused_container_mut(&mut self) -> eyre::Result<&mut Container> {\n        self.focused_workspace_mut()?\n            .focused_container_mut()\n            .ok_or_eyre(\"there is no container\")\n    }\n\n    pub fn focused_window(&self) -> eyre::Result<&Window> {\n        self.focused_container()?\n            .focused_window()\n            .ok_or_eyre(\"there is no window\")\n    }\n\n    fn focused_window_mut(&mut self) -> eyre::Result<&mut Window> {\n        self.focused_container_mut()?\n            .focused_window_mut()\n            .ok_or_eyre(\"there is no window\")\n    }\n\n    /// Updates the list of `known_hwnds` and their monitor/workspace index pair\n    ///\n    /// [`known_hwnds`]: `Self.known_hwnds`\n    pub fn update_known_hwnds(&mut self) {\n        tracing::trace!(\"updating list of known hwnds\");\n        let mut known_hwnds = HashMap::new();\n        for (m_idx, monitor) in self.monitors().iter().enumerate() {\n            for (w_idx, workspace) in monitor.workspaces().iter().enumerate() {\n                for container in workspace.containers() {\n                    for window in container.windows() {\n                        known_hwnds.insert(window.hwnd, (m_idx, w_idx));\n                    }\n                }\n\n                for window in workspace.floating_windows() {\n                    known_hwnds.insert(window.hwnd, (m_idx, w_idx));\n                }\n\n                if let Some(window) = workspace.maximized_window {\n                    known_hwnds.insert(window.hwnd, (m_idx, w_idx));\n                }\n\n                if let Some(container) = &workspace.monocle_container {\n                    for window in container.windows() {\n                        known_hwnds.insert(window.hwnd, (m_idx, w_idx));\n                    }\n                }\n            }\n        }\n\n        if self.known_hwnds != known_hwnds {\n            // Update reaper cache\n            {\n                let mut reaper_cache = crate::reaper::HWNDS_CACHE.lock();\n                *reaper_cache = known_hwnds.clone();\n            }\n\n            // Save to file\n            let hwnd_json = DATA_DIR.join(\"komorebi.hwnd.json\");\n            match OpenOptions::new()\n                .write(true)\n                .truncate(true)\n                .create(true)\n                .open(hwnd_json)\n            {\n                Ok(file) => {\n                    if let Err(error) =\n                        serde_json::to_writer_pretty(&file, &known_hwnds.keys().collect::<Vec<_>>())\n                    {\n                        tracing::error!(\"Failed to save list of known_hwnds on file: {}\", error);\n                    }\n                }\n                Err(error) => {\n                    tracing::error!(\"Failed to save list of known_hwnds on file: {}\", error);\n                }\n            }\n\n            // Store new hwnds\n            self.known_hwnds = known_hwnds;\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::monitor;\n    use crossbeam_channel::Sender;\n    use crossbeam_channel::bounded;\n    use std::path::PathBuf;\n    use uuid::Uuid;\n\n    struct TestContext {\n        socket_path: Option<PathBuf>,\n    }\n\n    impl Drop for TestContext {\n        fn drop(&mut self) {\n            if let Some(socket_path) = &self.socket_path {\n                // Clean up the socket file\n                std::fs::remove_file(socket_path).unwrap();\n            }\n        }\n    }\n\n    fn setup_window_manager() -> (WindowManager, TestContext) {\n        let (_sender, receiver): (Sender<WindowManagerEvent>, Receiver<WindowManagerEvent>) =\n            bounded(1);\n\n        // Temporary socket path for testing\n        let socket_name = format!(\"komorebi-test-{}.sock\", Uuid::new_v4());\n        let socket_path = PathBuf::from(socket_name);\n\n        // Create a new WindowManager instance\n        let wm = WindowManager::new(receiver, Some(socket_path.clone()));\n\n        // Window Manager should be created successfully\n        assert!(wm.is_ok());\n\n        (\n            wm.unwrap(),\n            TestContext {\n                socket_path: Some(socket_path),\n            },\n        )\n    }\n\n    #[test]\n    fn test_create_window_manager() {\n        let (_wm, _test_context) = setup_window_manager();\n    }\n\n    #[test]\n    fn test_focus_workspace() {\n        let (mut wm, _test_context) = setup_window_manager();\n\n        let m = monitor::new(\n            0,\n            Rect::default(),\n            Rect::default(),\n            \"TestMonitor\".to_string(),\n            \"TestDevice\".to_string(),\n            \"TestDeviceID\".to_string(),\n            Some(\"TestMonitorID\".to_string()),\n        );\n\n        // a new monitor should have a single workspace\n        assert_eq!(m.workspaces().len(), 1);\n\n        // the next index on the monitor should be the not-yet-created second workspace\n        let new_workspace_index = m.new_workspace_idx();\n        assert_eq!(new_workspace_index, 1);\n\n        // add the monitor to the window manager\n        wm.monitors_mut().push_back(m);\n\n        {\n            // focusing a workspace which doesn't yet exist should create it\n            let monitor = wm.focused_monitor_mut().unwrap();\n            monitor.focus_workspace(new_workspace_index).unwrap();\n            assert_eq!(monitor.workspaces().len(), 2);\n        }\n        assert_eq!(wm.focused_workspace_idx().unwrap(), 1);\n\n        {\n            // focusing a workspace many indices ahead should create all workspaces\n            // required along the way\n            let monitor = wm.focused_monitor_mut().unwrap();\n            monitor.focus_workspace(new_workspace_index + 2).unwrap();\n            assert_eq!(monitor.workspaces().len(), 4);\n        }\n        assert_eq!(wm.focused_workspace_idx().unwrap(), 3);\n\n        // we should be able to successfully focus an existing workspace too\n        wm.focus_workspace(0).unwrap();\n        assert_eq!(wm.focused_workspace_idx().unwrap(), 0);\n    }\n\n    #[test]\n    fn test_remove_focused_workspace() {\n        let (mut wm, _context) = setup_window_manager();\n\n        let m = monitor::new(\n            0,\n            Rect::default(),\n            Rect::default(),\n            \"TestMonitor\".to_string(),\n            \"TestDevice\".to_string(),\n            \"TestDeviceID\".to_string(),\n            Some(\"TestMonitorID\".to_string()),\n        );\n\n        // a new monitor should have a single workspace\n        assert_eq!(m.workspaces().len(), 1);\n\n        // the next index on the monitor should be the not-yet-created second workspace\n        let new_workspace_index = m.new_workspace_idx();\n        assert_eq!(new_workspace_index, 1);\n\n        // add the monitor to the window manager\n        wm.monitors_mut().push_back(m);\n\n        {\n            // focus a workspace which doesn't yet exist should create it\n            let monitor = wm.focused_monitor_mut().unwrap();\n            monitor.focus_workspace(new_workspace_index + 1).unwrap();\n\n            // Monitor focused workspace should be 2\n            assert_eq!(monitor.focused_workspace_idx(), 2);\n\n            // Should have 3 Workspaces\n            assert_eq!(monitor.workspaces().len(), 3);\n        }\n\n        // Remove the focused workspace\n        wm.remove_focused_workspace().unwrap();\n\n        {\n            let monitor = wm.focused_monitor_mut().unwrap();\n            monitor.focus_workspace(new_workspace_index).unwrap();\n\n            // Should be focused on workspace 1\n            assert_eq!(monitor.focused_workspace_idx(), 1);\n\n            // Should have 2 Workspaces\n            assert_eq!(monitor.workspaces().len(), 2);\n        }\n    }\n\n    #[test]\n    fn test_set_workspace_name() {\n        let (mut wm, _test_context) = setup_window_manager();\n\n        let m = monitor::new(\n            0,\n            Rect::default(),\n            Rect::default(),\n            \"TestMonitor\".to_string(),\n            \"TestDevice\".to_string(),\n            \"TestDeviceID\".to_string(),\n            Some(\"TestMonitorID\".to_string()),\n        );\n\n        // a new monitor should have a single workspace\n        assert_eq!(m.workspaces().len(), 1);\n\n        // create a new workspace\n        let new_workspace_index = m.new_workspace_idx();\n        assert_eq!(new_workspace_index, 1);\n\n        // add the monitor to the window manager\n        wm.monitors_mut().push_back(m);\n\n        {\n            // focusing a workspace which doesn't yet exist should create it\n            let monitor = wm.focused_monitor_mut().unwrap();\n            monitor.focus_workspace(new_workspace_index).unwrap();\n            assert_eq!(monitor.workspaces().len(), 2);\n        }\n        assert_eq!(wm.focused_workspace_idx().unwrap(), 1);\n\n        // set the name of the first workspace\n        wm.set_workspace_name(0, 0, \"workspace1\".to_string())\n            .unwrap();\n\n        // monitor_workspace_index_by_name should return the index of the workspace with the name \"workspace1\"\n        let workspace_index = wm.monitor_workspace_index_by_name(\"workspace1\").unwrap();\n\n        // workspace index 0 should now have the name \"workspace1\"\n        assert_eq!(workspace_index.1, 0);\n    }\n\n    #[test]\n    fn test_switch_focus_monitors() {\n        let (mut wm, _test_context) = setup_window_manager();\n\n        {\n            // Create a first monitor\n            let m = monitor::new(\n                0,\n                Rect::default(),\n                Rect::default(),\n                \"TestMonitor\".to_string(),\n                \"TestDevice\".to_string(),\n                \"TestDeviceID\".to_string(),\n                Some(\"TestMonitorID\".to_string()),\n            );\n\n            // monitor should have a single workspace\n            assert_eq!(m.workspaces().len(), 1);\n\n            // add the monitor to the window manager\n            wm.monitors_mut().push_back(m);\n        }\n        assert_eq!(wm.monitors().len(), 1);\n\n        {\n            // Create a second monitor\n            let m = monitor::new(\n                1,\n                Rect::default(),\n                Rect::default(),\n                \"TestMonitor2\".to_string(),\n                \"TestDevice2\".to_string(),\n                \"TestDeviceID2\".to_string(),\n                Some(\"TestMonitorID2\".to_string()),\n            );\n\n            // monitor should have a single workspace\n            assert_eq!(m.workspaces().len(), 1);\n\n            // add the monitor to the window manager\n            wm.monitors_mut().push_back(m);\n        }\n        assert_eq!(wm.monitors().len(), 2);\n\n        {\n            // Create a third monitor\n            let m = monitor::new(\n                2,\n                Rect::default(),\n                Rect::default(),\n                \"TestMonitor3\".to_string(),\n                \"TestDevice3\".to_string(),\n                \"TestDeviceID3\".to_string(),\n                Some(\"TestMonitorID3\".to_string()),\n            );\n\n            // monitor should have a single workspace\n            assert_eq!(m.workspaces().len(), 1);\n\n            // add the monitor to the window manager\n            wm.monitors_mut().push_back(m);\n        }\n        assert_eq!(wm.monitors().len(), 3);\n\n        {\n            // Set the first monitor as focused and check if it is focused\n            wm.focus_monitor(0).unwrap();\n            let current_monitor_idx = wm.monitors.focused_idx();\n            assert_eq!(current_monitor_idx, 0);\n        }\n\n        {\n            // Set the second monitor as focused and check if it is focused\n            wm.focus_monitor(1).unwrap();\n            let current_monitor_idx = wm.monitors.focused_idx();\n            assert_eq!(current_monitor_idx, 1);\n        }\n\n        {\n            // Set the third monitor as focused and check if it is focused\n            wm.focus_monitor(2).unwrap();\n            let current_monitor_idx = wm.monitors.focused_idx();\n            assert_eq!(current_monitor_idx, 2);\n        }\n\n        // Switch back to the first monitor\n        wm.focus_monitor(0).unwrap();\n        let current_monitor_idx = wm.monitors.focused_idx();\n        assert_eq!(current_monitor_idx, 0);\n    }\n\n    #[test]\n    fn test_switch_focus_to_nonexistent_monitor() {\n        let (mut wm, _test_context) = setup_window_manager();\n\n        {\n            // Create a first monitor\n            let m = monitor::new(\n                0,\n                Rect::default(),\n                Rect::default(),\n                \"TestMonitor\".to_string(),\n                \"TestDevice\".to_string(),\n                \"TestDeviceID\".to_string(),\n                Some(\"TestMonitorID\".to_string()),\n            );\n\n            // monitor should have a single workspace\n            assert_eq!(m.workspaces().len(), 1);\n\n            // add the monitor to the window manager\n            wm.monitors_mut().push_back(m);\n        }\n\n        // Should have 1 monitor and the monitor index should be 0\n        assert_eq!(wm.monitors().len(), 1);\n        assert_eq!(wm.focused_monitor_idx(), 0);\n\n        // Should receive an error when trying to focus a non-existent monitor\n        let result = wm.focus_monitor(1);\n        assert!(\n            result.is_err(),\n            \"Expected an error when focusing a non-existent monitor\"\n        );\n\n        // Should still be focused on the first monitor\n        assert_eq!(wm.focused_monitor_idx(), 0);\n    }\n\n    #[test]\n    fn test_focused_monitor_size() {\n        let (mut wm, _test_context) = setup_window_manager();\n\n        {\n            // Create a first monitor\n            let m = monitor::new(\n                0,\n                Rect::default(),\n                Rect::default(),\n                \"TestMonitor\".to_string(),\n                \"TestDevice\".to_string(),\n                \"TestDeviceID\".to_string(),\n                Some(\"TestMonitorID\".to_string()),\n            );\n\n            // monitor should have a single workspace\n            assert_eq!(m.workspaces().len(), 1);\n\n            // add the monitor to the window manager\n            wm.monitors_mut().push_back(m);\n        }\n        assert_eq!(wm.monitors().len(), 1);\n\n        {\n            // Set the first monitor as focused and check if it is focused\n            wm.focus_monitor(0).unwrap();\n            let current_monitor_size = wm.focused_monitor_size().unwrap();\n            assert_eq!(current_monitor_size, Rect::default());\n        }\n    }\n\n    #[test]\n    fn test_focus_container_in_cycle_direction() {\n        let (mut wm, _test_context) = setup_window_manager();\n\n        // Create a monitor\n        let mut m = monitor::new(\n            0,\n            Rect::default(),\n            Rect::default(),\n            \"TestMonitor\".to_string(),\n            \"TestDevice\".to_string(),\n            \"TestDeviceID\".to_string(),\n            Some(\"TestMonitorID\".to_string()),\n        );\n\n        let workspace = m.focused_workspace_mut().unwrap();\n        workspace.layer = WorkspaceLayer::Tiling;\n\n        for i in 0..4 {\n            let mut container = Container::default();\n            container.windows_mut().push_back(Window::from(i));\n            workspace.add_container_to_back(container);\n        }\n        assert_eq!(workspace.containers().len(), 4);\n\n        workspace.focus_container(0);\n\n        // add the monitor to the window manager\n        wm.monitors_mut().push_back(m);\n\n        // container focus should be on the second container\n        wm.focus_container_in_cycle_direction(CycleDirection::Next)\n            .ok();\n        assert_eq!(wm.focused_container_idx().unwrap(), 1);\n\n        // container focus should be on the third container\n        wm.focus_container_in_cycle_direction(CycleDirection::Next)\n            .ok();\n        assert_eq!(wm.focused_container_idx().unwrap(), 2);\n\n        // container focus should be on the second container\n        wm.focus_container_in_cycle_direction(CycleDirection::Previous)\n            .ok();\n        assert_eq!(wm.focused_container_idx().unwrap(), 1);\n\n        // container focus should be on the first container\n        wm.focus_container_in_cycle_direction(CycleDirection::Previous)\n            .ok();\n        assert_eq!(wm.focused_container_idx().unwrap(), 0);\n    }\n\n    #[test]\n    fn test_transfer_window() {\n        let (mut wm, _context) = setup_window_manager();\n\n        {\n            // Create a first monitor\n            let mut m = monitor::new(\n                0,\n                Rect::default(),\n                Rect::default(),\n                \"TestMonitor\".to_string(),\n                \"TestDevice\".to_string(),\n                \"TestDeviceID\".to_string(),\n                Some(\"TestMonitorID\".to_string()),\n            );\n\n            // Create a container\n            let workspace = m.focused_workspace_mut().unwrap();\n            let mut container = Container::default();\n\n            // Add a window to the container\n            container.windows_mut().push_back(Window::from(0));\n            workspace.add_container_to_back(container);\n\n            // Should contain 1 container\n            assert_eq!(workspace.containers().len(), 1);\n\n            wm.monitors_mut().push_back(m);\n        }\n\n        {\n            // Create a second monitor\n            let mut m = monitor::new(\n                1,\n                Rect::default(),\n                Rect::default(),\n                \"TestMonitor2\".to_string(),\n                \"TestDevice2\".to_string(),\n                \"TestDeviceID2\".to_string(),\n                Some(\"TestMonitorID2\".to_string()),\n            );\n\n            // Create a container\n            let workspace = m.focused_workspace_mut().unwrap();\n            let mut container = Container::default();\n\n            // Add a window to the container\n            container.windows_mut().push_back(Window::from(1));\n            workspace.add_container_to_back(container);\n\n            // Should contain 1 container\n            assert_eq!(workspace.containers().len(), 1);\n\n            wm.monitors_mut().push_back(m);\n        }\n\n        // Should contain 2 monitors\n        assert_eq!(wm.monitors().len(), 2);\n\n        {\n            // Monitor 0, Workspace 0, Window 0\n            let origin = (0, 0, 0);\n\n            // Monitor 1, Workspace 0, Window 0\n            let target = (1, 0, 0);\n\n            // Transfer the window from monitor 0 to monitor 1\n            wm.transfer_window(origin, target).unwrap();\n\n            // Monitor 1 should contain 0 containers\n            let workspace = wm.focused_workspace_mut().unwrap();\n            assert_eq!(workspace.containers().len(), 0);\n\n            // Monitor 2 should contain 2 containers\n            wm.focus_monitor(1).unwrap();\n            let workspace = wm.focused_workspace_mut().unwrap();\n            assert_eq!(workspace.containers().len(), 2);\n        }\n\n        {\n            // Monitor 1, Workspace 0, Window 0\n            let origin = (1, 0, 0);\n\n            // Monitor 0, Workspace 0, Window 0\n            let target = (0, 0, 0);\n\n            // Transfer the window from monitor 1 back to monitor 0\n            wm.transfer_window(origin, target).unwrap();\n\n            // Monitor 2 should contain 1 containers\n            let workspace = wm.focused_workspace_mut().unwrap();\n            assert_eq!(workspace.containers().len(), 1);\n\n            // Monitor 1 should contain 1 containers\n            wm.focus_monitor(0).unwrap();\n            let workspace = wm.focused_workspace_mut().unwrap();\n            assert_eq!(workspace.containers().len(), 1);\n        }\n    }\n\n    #[test]\n    fn test_transfer_window_to_nonexistent_monitor() {\n        // NOTE: transfer_window is primarily used when a window is being dragged by a mouse. The\n        // transfer_window function does return an error when the target monitor doesn't exist but\n        // there is a bug where the window isn't in the container after the window fails to\n        // transfer. The test will test for the result of the transfer_window function but not if\n        // the window is in the container after the transfer fails.\n\n        let (mut wm, _context) = setup_window_manager();\n\n        {\n            // Create a first monitor\n            let mut m = monitor::new(\n                0,\n                Rect::default(),\n                Rect::default(),\n                \"TestMonitor\".to_string(),\n                \"TestDevice\".to_string(),\n                \"TestDeviceID\".to_string(),\n                Some(\"TestMonitorID\".to_string()),\n            );\n\n            // Create a container\n            let workspace = m.focused_workspace_mut().unwrap();\n            let mut container = Container::default();\n\n            // Add a window to the container\n            container.windows_mut().push_back(Window::from(0));\n            workspace.add_container_to_back(container);\n\n            // Should contain 1 container\n            assert_eq!(workspace.containers().len(), 1);\n\n            wm.monitors_mut().push_back(m);\n        }\n\n        {\n            // Monitor 0, Workspace 0, Window 0\n            let origin = (0, 0, 0);\n\n            // Monitor 1, Workspace 0, Window 0\n            //\n            let target = (1, 0, 0);\n\n            // Attempt to transfer the window from monitor 0 to a non-existent monitor\n            let result = wm.transfer_window(origin, target);\n\n            // Result should be an error since the monitor doesn't exist\n            assert!(\n                result.is_err(),\n                \"Expected an error when transferring to a non-existent monitor\"\n            );\n\n            assert_eq!(wm.focused_container_idx().unwrap(), 0);\n            assert_eq!(wm.focused_workspace_idx().unwrap(), 0);\n        }\n    }\n\n    #[test]\n    fn test_transfer_container() {\n        let (mut wm, _context) = setup_window_manager();\n\n        {\n            // Create a first monitor\n            let mut m = monitor::new(\n                0,\n                Rect::default(),\n                Rect::default(),\n                \"TestMonitor\".to_string(),\n                \"TestDevice\".to_string(),\n                \"TestDeviceID\".to_string(),\n                Some(\"TestMonitorID\".to_string()),\n            );\n\n            // Create a container\n            let workspace = m.focused_workspace_mut().unwrap();\n            let mut container = Container::default();\n\n            // Add a window to the container\n            container.windows_mut().push_back(Window::from(0));\n            workspace.add_container_to_back(container);\n\n            // Should contain 1 container\n            assert_eq!(workspace.containers().len(), 1);\n\n            wm.monitors_mut().push_back(m);\n        }\n\n        {\n            // Create a second monitor\n            let mut m = monitor::new(\n                1,\n                Rect::default(),\n                Rect::default(),\n                \"TestMonitor2\".to_string(),\n                \"TestDevice2\".to_string(),\n                \"TestDeviceID2\".to_string(),\n                Some(\"TestMonitorID2\".to_string()),\n            );\n\n            // Create a container\n            let workspace = m.focused_workspace_mut().unwrap();\n            let mut container = Container::default();\n\n            // Add a window to the container\n            container.windows_mut().push_back(Window::from(1));\n            workspace.add_container_to_back(container);\n\n            // Should contain 1 container\n            assert_eq!(workspace.containers().len(), 1);\n\n            wm.monitors_mut().push_back(m);\n        }\n\n        // Should contain 2 monitors\n        assert_eq!(wm.monitors().len(), 2);\n\n        {\n            // Monitor 0, Workspace 0, Window 0\n            let origin = (0, 0, 0);\n\n            // Monitor 1, Workspace 0, Window 0\n            let target = (1, 0, 0);\n\n            // Transfer the window from monitor 0 to monitor 1\n            wm.transfer_container(origin, target).unwrap();\n\n            // Monitor 1 should contain 0 containers\n            let workspace = wm.focused_workspace_mut().unwrap();\n            assert_eq!(workspace.containers().len(), 0);\n\n            // Monitor 2 should contain 2 containers\n            wm.focus_monitor(1).unwrap();\n            let workspace = wm.focused_workspace_mut().unwrap();\n            assert_eq!(workspace.containers().len(), 2);\n        }\n\n        {\n            // Monitor 1, Workspace 0, Window 0\n            let origin = (1, 0, 0);\n\n            // Monitor 0, Workspace 0, Window 0\n            let target = (0, 0, 0);\n\n            // Transfer the window from monitor 1 back to monitor 0\n            wm.transfer_container(origin, target).unwrap();\n\n            // Monitor 2 should contain 1 containers\n            let workspace = wm.focused_workspace_mut().unwrap();\n            assert_eq!(workspace.containers().len(), 1);\n\n            // Monitor 1 should contain 1 containers\n            wm.focus_monitor(0).unwrap();\n            let workspace = wm.focused_workspace_mut().unwrap();\n            assert_eq!(workspace.containers().len(), 1);\n        }\n    }\n\n    #[test]\n    fn test_remove_window_from_container() {\n        let (mut wm, _context) = setup_window_manager();\n\n        {\n            // Create a first monitor\n            let mut m = monitor::new(\n                0,\n                Rect::default(),\n                Rect::default(),\n                \"TestMonitor1\".to_string(),\n                \"TestDevice1\".to_string(),\n                \"TestDeviceID1\".to_string(),\n                Some(\"TestMonitorID1\".to_string()),\n            );\n\n            // Create a container\n            let mut container = Container::default();\n\n            // Add three windows to the container\n            for i in 0..3 {\n                container.windows_mut().push_back(Window::from(i));\n            }\n            // Should have 3 windows in the container\n            assert_eq!(container.windows().len(), 3);\n\n            // Focus last window\n            container.focus_window(2);\n\n            // Should be focused on the 2nd window\n            assert_eq!(container.focused_window_idx(), 2);\n\n            // Add the container to a workspace\n            let workspace = m.focused_workspace_mut().unwrap();\n            workspace.add_container_to_back(container);\n\n            // Add monitor to the window manager\n            wm.monitors_mut().push_back(m);\n        }\n\n        // Remove the focused window from the container\n        wm.remove_window_from_container().ok();\n\n        {\n            // Should have 2 containers in the workspace\n            let workspace = wm.focused_workspace_mut().unwrap();\n            assert_eq!(workspace.containers().len(), 2);\n\n            // Should contain 1 window in the new container\n            let container = workspace.focused_container_mut().unwrap();\n            assert_eq!(container.windows().len(), 1);\n        }\n\n        {\n            // Switch to the old container\n            let workspace = wm.focused_workspace_mut().unwrap();\n            workspace.focus_container(0);\n\n            // Should contain 2 windows in the old container\n            let container = workspace.focused_container_mut().unwrap();\n            assert_eq!(container.windows().len(), 2);\n        }\n    }\n\n    #[test]\n    fn test_remove_nonexistent_window_from_container() {\n        let (mut wm, _context) = setup_window_manager();\n\n        {\n            // Create a first monitor\n            let mut m = monitor::new(\n                0,\n                Rect::default(),\n                Rect::default(),\n                \"TestMonitor1\".to_string(),\n                \"TestDevice1\".to_string(),\n                \"TestDeviceID1\".to_string(),\n                Some(\"TestMonitorID1\".to_string()),\n            );\n\n            // Create a container\n            let container = Container::default();\n\n            // Should have 3 windows in the container\n            assert_eq!(container.windows().len(), 0);\n\n            // Add the container to a workspace\n            let workspace = m.focused_workspace_mut().unwrap();\n            workspace.add_container_to_back(container);\n\n            // Add monitor to the window manager\n            wm.monitors_mut().push_back(m);\n        }\n\n        // Should receive an error when trying to remove a window from an empty container\n        let result = wm.remove_window_from_container();\n        assert!(\n            result.is_err(),\n            \"Expected an error when trying to remove a window from an empty container\"\n        );\n    }\n\n    #[test]\n    fn cycle_container_window_in_direction() {\n        let (mut wm, _context) = setup_window_manager();\n\n        {\n            let mut m = monitor::new(\n                0,\n                Rect::default(),\n                Rect::default(),\n                \"TestMonitor\".to_string(),\n                \"TestDevice\".to_string(),\n                \"TestDeviceID\".to_string(),\n                Some(\"TestMonitorID\".to_string()),\n            );\n\n            let workspace = m.focused_workspace_mut().unwrap();\n\n            {\n                let mut container = Container::default();\n\n                for i in 0..3 {\n                    container.windows_mut().push_back(Window::from(i));\n                }\n\n                // Should have 3 windows in the container\n                assert_eq!(container.windows().len(), 3);\n\n                // Add container to workspace\n                workspace.add_container_to_back(container);\n            }\n\n            // Add monitor to the window manager\n            wm.monitors_mut().push_back(m);\n        }\n\n        // Cycle to the next window\n        wm.cycle_container_window_in_direction(CycleDirection::Next)\n            .ok();\n\n        {\n            // Should be on Window 1\n            let workspace = wm.focused_workspace_mut().unwrap();\n            let container = workspace.focused_container_mut().unwrap();\n            assert_eq!(container.focused_window_idx(), 1);\n        }\n\n        // Cycle to the next window\n        wm.cycle_container_window_in_direction(CycleDirection::Next)\n            .ok();\n\n        {\n            // Should be on Window 2\n            let workspace = wm.focused_workspace_mut().unwrap();\n            let container = workspace.focused_container_mut().unwrap();\n            assert_eq!(container.focused_window_idx(), 2);\n        }\n\n        // Cycle to the previous window\n        wm.cycle_container_window_in_direction(CycleDirection::Previous)\n            .ok();\n\n        {\n            // Should be on Window 1\n            let workspace = wm.focused_workspace_mut().unwrap();\n            let container = workspace.focused_container_mut().unwrap();\n            assert_eq!(container.focused_window_idx(), 1);\n        }\n    }\n\n    #[test]\n    fn test_cycle_nonexistent_windows() {\n        let (mut wm, _context) = setup_window_manager();\n\n        {\n            // Create a first monitor\n            let mut m = monitor::new(\n                0,\n                Rect::default(),\n                Rect::default(),\n                \"TestMonitor1\".to_string(),\n                \"TestDevice1\".to_string(),\n                \"TestDeviceID1\".to_string(),\n                Some(\"TestMonitorID1\".to_string()),\n            );\n\n            // Create a container\n            let container = Container::default();\n\n            // Should have 3 windows in the container\n            assert_eq!(container.windows().len(), 0);\n\n            // Add the container to a workspace\n            let workspace = m.focused_workspace_mut().unwrap();\n            workspace.add_container_to_back(container);\n\n            // Add monitor to the window manager\n            wm.monitors_mut().push_back(m);\n        }\n\n        // Should return an error when trying to cycle through windows in an empty container\n        let result = wm.cycle_container_window_in_direction(CycleDirection::Next);\n        assert!(\n            result.is_err(),\n            \"Expected an error when cycling through windows in an empty container\"\n        );\n    }\n\n    #[test]\n    fn test_cycle_container_window_index_in_direction() {\n        let (mut wm, _context) = setup_window_manager();\n\n        {\n            let mut m = monitor::new(\n                0,\n                Rect::default(),\n                Rect::default(),\n                \"TestMonitor\".to_string(),\n                \"TestDevice\".to_string(),\n                \"TestDeviceID\".to_string(),\n                Some(\"TestMonitorID\".to_string()),\n            );\n\n            let workspace = m.focused_workspace_mut().unwrap();\n\n            {\n                let mut container = Container::default();\n\n                for i in 0..3 {\n                    container.windows_mut().push_back(Window::from(i));\n                }\n\n                // Should have 3 windows in the container\n                assert_eq!(container.windows().len(), 3);\n\n                // Add container to workspace\n                workspace.add_container_to_back(container);\n            }\n\n            // Add monitor to the window manager\n            wm.monitors_mut().push_back(m);\n        }\n\n        // Cycle to the next window\n        wm.cycle_container_window_index_in_direction(CycleDirection::Next)\n            .ok();\n\n        {\n            // Should be on Window 1\n            let workspace = wm.focused_workspace_mut().unwrap();\n            let container = workspace.focused_container_mut().unwrap();\n            assert_eq!(container.focused_window_idx(), 1);\n        }\n\n        // Cycle to the next window\n        wm.cycle_container_window_index_in_direction(CycleDirection::Next)\n            .ok();\n\n        {\n            // Should be on Window 2\n            let workspace = wm.focused_workspace_mut().unwrap();\n            let container = workspace.focused_container_mut().unwrap();\n            assert_eq!(container.focused_window_idx(), 2);\n        }\n\n        // Cycle to the Previous window\n        wm.cycle_container_window_index_in_direction(CycleDirection::Previous)\n            .ok();\n\n        {\n            // Should be on Window 1\n            let workspace = wm.focused_workspace_mut().unwrap();\n            let container = workspace.focused_container_mut().unwrap();\n            assert_eq!(container.focused_window_idx(), 1);\n        }\n    }\n\n    #[test]\n    fn test_swap_containers() {\n        let (mut wm, _context) = setup_window_manager();\n\n        {\n            // Create a first monitor\n            let mut m = monitor::new(\n                0,\n                Rect::default(),\n                Rect::default(),\n                \"TestMonitor\".to_string(),\n                \"TestDevice\".to_string(),\n                \"TestDeviceID\".to_string(),\n                Some(\"TestMonitorID\".to_string()),\n            );\n\n            // Create a container\n            let mut container = Container::default();\n\n            // Add three windows to the container\n            for i in 0..3 {\n                container.windows_mut().push_back(Window::from(i));\n            }\n\n            // Should have 3 windows in the container\n            assert_eq!(container.windows().len(), 3);\n\n            // Add the container to the workspace\n            let workspace = m.focused_workspace_mut().unwrap();\n            workspace.add_container_to_back(container);\n\n            // Add monitor to the window manager\n            wm.monitors_mut().push_back(m);\n        }\n\n        {\n            // Create a second monitor\n            let mut m = monitor::new(\n                1,\n                Rect::default(),\n                Rect::default(),\n                \"TestMonitor2\".to_string(),\n                \"TestDevice2\".to_string(),\n                \"TestDeviceID2\".to_string(),\n                Some(\"TestMonitorID2\".to_string()),\n            );\n\n            // Create a container\n            let workspace = m.focused_workspace_mut().unwrap();\n            let mut container = Container::default();\n\n            // Add a window to the container\n            container.windows_mut().push_back(Window::from(1));\n            workspace.add_container_to_back(container);\n\n            // Should contain 1 container\n            assert_eq!(workspace.containers().len(), 1);\n\n            wm.monitors_mut().push_back(m);\n        }\n\n        // Should contain 2 monitors\n        assert_eq!(wm.monitors().len(), 2);\n\n        // Monitor 0, Workspace 0, Window 0\n        let origin = (0, 0, 0);\n\n        // Monitor 1, Workspace 0, Window 0\n        let target = (1, 0, 0);\n\n        wm.swap_containers(origin, target).unwrap();\n\n        {\n            // Monitor 0 Workspace 0 container 0 should contain 1 container\n            let workspace = wm.focused_workspace_mut().unwrap();\n            let container = workspace.focused_container_mut().unwrap();\n            assert_eq!(container.windows().len(), 1);\n        }\n\n        wm.focus_monitor(1).unwrap();\n\n        {\n            // Monitor 1 Workspace 0 container 0 should contain 3 containers\n            let workspace = wm.focused_workspace_mut().unwrap();\n            let container = workspace.focused_container_mut().unwrap();\n            assert_eq!(container.windows().len(), 3);\n        }\n    }\n\n    #[test]\n    fn test_swap_container_with_nonexistent_container() {\n        let (mut wm, _context) = setup_window_manager();\n\n        {\n            // Create a first monitor\n            let mut m = monitor::new(\n                0,\n                Rect::default(),\n                Rect::default(),\n                \"TestMonitor\".to_string(),\n                \"TestDevice\".to_string(),\n                \"TestDeviceID\".to_string(),\n                Some(\"TestMonitorID\".to_string()),\n            );\n\n            // Create a container\n            let mut container = Container::default();\n\n            // Add three windows to the container\n            for i in 0..3 {\n                container.windows_mut().push_back(Window::from(i));\n            }\n\n            // Should have 3 windows in the container\n            assert_eq!(container.windows().len(), 3);\n\n            // Add the container to the workspace\n            let workspace = m.focused_workspace_mut().unwrap();\n            workspace.add_container_to_back(container);\n\n            // Add monitor to the window manager\n            wm.monitors_mut().push_back(m);\n        }\n\n        // Monitor 0, Workspace 0, Window 0\n        let origin = (0, 0, 0);\n\n        // Monitor 1, Workspace 0, Window 0\n        let target = (0, 3, 0);\n\n        // Should be focused on the first container\n        assert_eq!(wm.focused_container_idx().unwrap(), 0);\n\n        // Should return an error since there is only one container in the workspace\n        let result = wm.swap_containers(origin, target);\n        assert!(\n            result.is_err(),\n            \"Expected an error when swapping with a non-existent container\"\n        );\n\n        // Should still be focused on the first container\n        assert_eq!(wm.focused_container_idx().unwrap(), 0);\n\n        {\n            // Should still have 1 container in the workspace\n            let workspace = wm.focused_workspace_mut().unwrap();\n            assert_eq!(workspace.containers().len(), 1);\n\n            // Container should still have 3 windows\n            let container = workspace.focused_container_mut().unwrap();\n            assert_eq!(container.windows().len(), 3);\n        }\n    }\n\n    #[test]\n    fn test_swap_monitor_workspaces() {\n        let (mut wm, _context) = setup_window_manager();\n\n        {\n            // Create a first monitor\n            let mut m = monitor::new(\n                0,\n                Rect::default(),\n                Rect::default(),\n                \"TestMonitor\".to_string(),\n                \"TestDevice\".to_string(),\n                \"TestDeviceID\".to_string(),\n                Some(\"TestMonitorID\".to_string()),\n            );\n\n            // Create a container\n            let mut container = Container::default();\n\n            // Add three windows to the container\n            for i in 0..3 {\n                container.windows_mut().push_back(Window::from(i));\n            }\n\n            // Should have 3 windows in the container\n            assert_eq!(container.windows().len(), 3);\n\n            // Add the container to the workspace\n            let workspace = m.focused_workspace_mut().unwrap();\n            workspace.add_container_to_back(container);\n\n            // Add monitor to the window manager\n            wm.monitors_mut().push_back(m);\n        }\n\n        {\n            // Create a second monitor\n            let mut m = monitor::new(\n                1,\n                Rect::default(),\n                Rect::default(),\n                \"TestMonitor2\".to_string(),\n                \"TestDevice2\".to_string(),\n                \"TestDeviceID2\".to_string(),\n                Some(\"TestMonitorID2\".to_string()),\n            );\n\n            // Create a container\n            let workspace = m.focused_workspace_mut().unwrap();\n            let mut container = Container::default();\n\n            // Add a window to the container\n            container.windows_mut().push_back(Window::from(1));\n            workspace.add_container_to_back(container);\n\n            // Should contain 1 container\n            assert_eq!(workspace.containers().len(), 1);\n\n            // Add the monitor to the window manager\n            wm.monitors_mut().push_back(m);\n        }\n\n        // Swap the workspaces between Monitor 0 and Monitor 1\n        wm.swap_monitor_workspaces(0, 1).ok();\n\n        {\n            // The focused workspace container in Monitor 0 should contain 3 containers\n            let workspace = wm.focused_workspace_mut().unwrap();\n            let container = workspace.focused_container_mut().unwrap();\n            assert_eq!(container.windows().len(), 1);\n        }\n\n        // Switch to Monitor 1\n        wm.focus_monitor(1).unwrap();\n        assert_eq!(wm.focused_monitor_idx(), 1);\n\n        {\n            // The focused workspace container in Monitor 1 should contain 3 containers\n            let workspace = wm.focused_workspace_mut().unwrap();\n            let container = workspace.focused_container_mut().unwrap();\n            assert_eq!(container.windows().len(), 3);\n        }\n    }\n\n    #[test]\n    fn test_swap_workspace_with_nonexistent_monitor() {\n        let (mut wm, _context) = setup_window_manager();\n\n        {\n            let mut m = monitor::new(\n                0,\n                Rect::default(),\n                Rect::default(),\n                \"TestMonitor\".to_string(),\n                \"TestDevice\".to_string(),\n                \"TestDeviceID\".to_string(),\n                Some(\"TestMonitorID\".to_string()),\n            );\n\n            // Add another workspace\n            let new_workspace_index = m.new_workspace_idx();\n            m.focus_workspace(new_workspace_index).unwrap();\n\n            // Should have 2 workspaces\n            assert_eq!(m.workspaces().len(), 2);\n\n            // Add monitor to window manager\n            wm.monitors_mut().push_back(m);\n        }\n\n        // Should be an error since Monitor 1 does not exist\n        let result = wm.swap_monitor_workspaces(1, 0);\n        assert!(\n            result.is_err(),\n            \"Expected an error when swapping with a non-existent monitor\"\n        );\n\n        {\n            // Should still have 2 workspaces in Monitor 0\n            let monitor = wm.monitors().front().unwrap();\n            let workspaces = monitor.workspaces();\n            assert_eq!(\n                workspaces.len(),\n                2,\n                \"Expected 2 workspaces after swap attempt\"\n            );\n            assert_eq!(wm.focused_monitor_idx(), 0);\n        }\n    }\n\n    #[test]\n    fn test_move_workspace_to_monitor() {\n        let (mut wm, _context) = setup_window_manager();\n\n        {\n            let mut m = monitor::new(\n                0,\n                Rect::default(),\n                Rect::default(),\n                \"TestMonitor\".to_string(),\n                \"TestDevice\".to_string(),\n                \"TestDeviceID\".to_string(),\n                Some(\"TestMonitorID\".to_string()),\n            );\n\n            // Add another workspace\n            let new_workspace_index = m.new_workspace_idx();\n            m.focus_workspace(new_workspace_index).unwrap();\n\n            // Should have 2 workspaces\n            assert_eq!(m.workspaces().len(), 2);\n\n            // Add monitor to window manager\n            wm.monitors_mut().push_back(m);\n        }\n\n        {\n            let m = monitor::new(\n                1,\n                Rect::default(),\n                Rect::default(),\n                \"TestMonitor2\".to_string(),\n                \"TestDevice2\".to_string(),\n                \"TestDeviceID2\".to_string(),\n                Some(\"TestMonitorID2\".to_string()),\n            );\n\n            // Should contain 1 workspace\n            assert_eq!(m.workspaces().len(), 1);\n\n            // Add monitor to workspace\n            wm.monitors_mut().push_back(m);\n        }\n\n        // Should contain 2 monitors\n        assert_eq!(wm.monitors().len(), 2);\n\n        // Move a workspace from Monitor 0 to Monitor 1\n        wm.move_workspace_to_monitor(1).ok();\n\n        {\n            // Should be focused on Monitor 1\n            assert_eq!(wm.focused_monitor_idx(), 1);\n\n            // Should contain 2 workspaces\n            let monitor = wm.focused_monitor_mut().unwrap();\n            assert_eq!(monitor.workspaces().len(), 2);\n        }\n\n        {\n            // Switch to Monitor 0\n            wm.focus_monitor(0).unwrap();\n\n            // Should contain 1 workspace\n            let monitor = wm.focused_monitor_mut().unwrap();\n            assert_eq!(monitor.workspaces().len(), 1);\n        }\n    }\n\n    #[test]\n    fn test_move_workspace_to_nonexistent_monitor() {\n        let (mut wm, _context) = setup_window_manager();\n\n        {\n            let mut m = monitor::new(\n                0,\n                Rect::default(),\n                Rect::default(),\n                \"TestMonitor\".to_string(),\n                \"TestDevice\".to_string(),\n                \"TestDeviceID\".to_string(),\n                Some(\"TestMonitorID\".to_string()),\n            );\n\n            // Add another workspace\n            let new_workspace_index = m.new_workspace_idx();\n            m.focus_workspace(new_workspace_index).unwrap();\n\n            // Should have 2 workspaces\n            assert_eq!(m.workspaces().len(), 2);\n\n            // Add monitor to window manager\n            wm.monitors_mut().push_back(m);\n        }\n\n        // Attempt to move a workspace to a non-existent monitor\n        let result = wm.move_workspace_to_monitor(1);\n\n        // Should be an error since Monitor 1 does not exist\n        assert!(\n            result.is_err(),\n            \"Expected an error when moving to a non-existent monitor\"\n        );\n    }\n\n    #[test]\n    fn test_toggle_tiling() {\n        let (mut wm, _context) = setup_window_manager();\n\n        {\n            let mut m = monitor::new(\n                0,\n                Rect::default(),\n                Rect::default(),\n                \"TestMonitor\".to_string(),\n                \"TestDevice\".to_string(),\n                \"TestDeviceID\".to_string(),\n                Some(\"TestMonitorID\".to_string()),\n            );\n\n            // Set Workspace Layer to Tiling\n            let workspace = m.focused_workspace_mut().unwrap();\n            workspace.layer = WorkspaceLayer::Tiling;\n\n            // Tiling state should be true\n            assert!(workspace.tile);\n\n            // Add monitor to workspace\n            wm.monitors_mut().push_back(m);\n        }\n\n        {\n            // Tiling state should be false\n            wm.toggle_tiling().unwrap();\n            let workspace = wm.focused_workspace_mut().unwrap();\n            assert!(!workspace.tile);\n        }\n\n        {\n            // Tiling state should be true\n            wm.toggle_tiling().unwrap();\n            let workspace = wm.focused_workspace_mut().unwrap();\n            assert!(workspace.tile);\n        }\n    }\n\n    #[test]\n    fn test_toggle_lock() {\n        let (mut wm, _context) = setup_window_manager();\n\n        {\n            // Add monitor with default workspace to\n            let mut m = monitor::new(\n                0,\n                Rect::default(),\n                Rect::default(),\n                \"TestMonitor\".to_string(),\n                \"TestDevice\".to_string(),\n                \"TestDeviceID\".to_string(),\n                Some(\"TestMonitorID\".to_string()),\n            );\n\n            let workspace = m.focused_workspace_mut().unwrap();\n\n            // Create containers to add to the workspace\n            for _ in 0..3 {\n                let container = Container::default();\n                workspace.add_container_to_back(container);\n            }\n\n            wm.monitors_mut().push_back(m);\n        }\n\n        {\n            // Ensure container 2 is not locked\n            let workspace = wm.focused_workspace_mut().unwrap();\n            assert_eq!(workspace.focused_container_idx(), 2);\n            assert!(!workspace.focused_container().unwrap().locked);\n        }\n\n        // Toggle lock on focused container\n        wm.toggle_lock().unwrap();\n\n        {\n            // Ensure container 2 is locked\n            let workspace = wm.focused_workspace_mut().unwrap();\n            assert!(workspace.focused_container().unwrap().locked);\n        }\n\n        // Toggle lock on focused container\n        wm.toggle_lock().unwrap();\n\n        {\n            // Ensure container 2 is not locked\n            let workspace = wm.focused_workspace_mut().unwrap();\n            assert!(!workspace.focused_container().unwrap().locked);\n        }\n    }\n\n    #[test]\n    fn test_float_window() {\n        let (mut wm, _context) = setup_window_manager();\n\n        {\n            // Create a monitor\n            let mut m = monitor::new(\n                0,\n                Rect::default(),\n                Rect::default(),\n                \"TestMonitor\".to_string(),\n                \"TestDevice\".to_string(),\n                \"TestDeviceID\".to_string(),\n                Some(\"TestMonitorID\".to_string()),\n            );\n\n            // Create a container\n            let mut container = Container::default();\n\n            // Add three windows to the container\n            for i in 0..3 {\n                container.windows_mut().push_back(Window::from(i));\n            }\n\n            // Should have 3 windows in the container\n            assert_eq!(container.windows().len(), 3);\n\n            // Add the container to the workspace\n            let workspace = m.focused_workspace_mut().unwrap();\n            workspace.add_container_to_back(container);\n\n            // Add monitor to the window manager\n            wm.monitors_mut().push_back(m);\n        }\n\n        // Add focused window to floating window list\n        wm.float_window().ok();\n\n        {\n            let workspace = wm.focused_workspace().unwrap();\n            let floating_windows = workspace.floating_windows();\n            let container = workspace.focused_container().unwrap();\n\n            // Hwnd 0 should be added to floating_windows\n            assert_eq!(floating_windows[0].hwnd, 0);\n\n            // Should have a length of 1\n            assert_eq!(floating_windows.len(), 1);\n\n            // Should have 2 windows in the container\n            assert_eq!(container.windows().len(), 2);\n\n            // Should be focused on window 1\n            assert_eq!(container.focused_window(), Some(&Window { hwnd: 1 }));\n        }\n\n        // Add focused window to floating window list\n        wm.float_window().ok();\n\n        {\n            let workspace = wm.focused_workspace().unwrap();\n            let floating_windows = workspace.floating_windows();\n            let container = workspace.focused_container().unwrap();\n\n            // Hwnd 1 should be added to floating_windows\n            assert_eq!(floating_windows[1].hwnd, 1);\n\n            // Should have a length of 2\n            assert_eq!(floating_windows.len(), 2);\n\n            // Should have 1 window in the container\n            assert_eq!(container.windows().len(), 1);\n\n            // Should be focused on window 2\n            assert_eq!(container.focused_window(), Some(&Window { hwnd: 2 }));\n        }\n    }\n\n    #[test]\n    fn test_float_nonexistent_window() {\n        let (mut wm, _context) = setup_window_manager();\n\n        {\n            let mut m = monitor::new(\n                0,\n                Rect::default(),\n                Rect::default(),\n                \"TestMonitor\".to_string(),\n                \"TestDevice\".to_string(),\n                \"TestDeviceID\".to_string(),\n                Some(\"TestMonitorID\".to_string()),\n            );\n\n            // Add another workspace\n            let new_workspace_index = m.new_workspace_idx();\n            m.focus_workspace(new_workspace_index).unwrap();\n\n            // Should have 2 workspaces\n            assert_eq!(m.workspaces().len(), 2);\n\n            // Add monitor to window manager\n            wm.monitors_mut().push_back(m);\n        }\n\n        // Should return an error when trying to float a non-existent window\n        let result = wm.float_window();\n        assert!(\n            result.is_err(),\n            \"Expected an error when trying to float a non-existent window\"\n        );\n    }\n\n    #[test]\n    fn test_maximize_and_unmaximize_window() {\n        let (mut wm, _context) = setup_window_manager();\n\n        {\n            // Create a monitor\n            let mut m = monitor::new(\n                0,\n                Rect::default(),\n                Rect::default(),\n                \"TestMonitor\".to_string(),\n                \"TestDevice\".to_string(),\n                \"TestDeviceID\".to_string(),\n                Some(\"TestMonitorID\".to_string()),\n            );\n\n            // Create a container\n            let mut container = Container::default();\n\n            // Add three windows to the container\n            for i in 0..3 {\n                container.windows_mut().push_back(Window::from(i));\n            }\n\n            // Should have 3 windows in the container\n            assert_eq!(container.windows().len(), 3);\n\n            // Add the container to the workspace\n            let workspace = m.focused_workspace_mut().unwrap();\n            workspace.add_container_to_back(container);\n\n            // Add monitor to the window manager\n            wm.monitors_mut().push_back(m);\n        }\n\n        {\n            // No windows should be maximized\n            let workspace = wm.focused_workspace().unwrap();\n            let maximized_window = workspace.maximized_window;\n            assert_eq!(maximized_window, None);\n        }\n\n        // Maximize the focused window\n        wm.maximize_window().ok();\n\n        {\n            // Window 0 should be maximized\n            let workspace = wm.focused_workspace().unwrap();\n            let maximized_window = workspace.maximized_window;\n            assert_eq!(maximized_window, Some(Window::from(0)));\n        }\n\n        wm.unmaximize_window().ok();\n\n        {\n            // No windows should be maximized\n            let workspace = wm.focused_workspace().unwrap();\n            let maximized_window = workspace.maximized_window;\n            assert_eq!(maximized_window, None);\n        }\n\n        // Focus container at index 1\n        wm.focused_workspace_mut().unwrap().focus_container(1);\n\n        {\n            // Focus the window at index 1\n            let container = wm.focused_container_mut().unwrap();\n            container.focus_window(1);\n        }\n\n        // Maximize the focused window\n        wm.maximize_window().ok();\n\n        {\n            // Window 2 should be maximized\n            let workspace = wm.focused_workspace().unwrap();\n            let maximized_window = workspace.maximized_window;\n            assert_eq!(maximized_window, Some(Window::from(2)));\n        }\n\n        wm.unmaximize_window().ok();\n\n        {\n            // No windows should be maximized\n            let workspace = wm.focused_workspace().unwrap();\n            let maximized_window = workspace.maximized_window;\n            assert_eq!(maximized_window, None);\n        }\n    }\n\n    #[test]\n    fn test_toggle_maximize() {\n        let (mut wm, _context) = setup_window_manager();\n\n        {\n            // Create a monitor\n            let mut m = monitor::new(\n                0,\n                Rect::default(),\n                Rect::default(),\n                \"TestMonitor\".to_string(),\n                \"TestDevice\".to_string(),\n                \"TestDeviceID\".to_string(),\n                Some(\"TestMonitorID\".to_string()),\n            );\n\n            // Create a container\n            let mut container = Container::default();\n\n            // Add three windows to the container\n            for i in 0..3 {\n                container.windows_mut().push_back(Window::from(i));\n            }\n\n            // Should have 3 windows in the container\n            assert_eq!(container.windows().len(), 3);\n\n            // Add the container to the workspace\n            let workspace = m.focused_workspace_mut().unwrap();\n            workspace.add_container_to_back(container);\n\n            // Add monitor to the window manager\n            wm.monitors_mut().push_back(m);\n        }\n\n        // Toggle maximize on\n        wm.toggle_maximize().ok();\n\n        {\n            // Window 0 should be maximized\n            let workspace = wm.focused_workspace().unwrap();\n            let maximized_window = workspace.maximized_window;\n            assert_eq!(maximized_window, Some(Window::from(0)));\n        }\n\n        // Toggle maximize off\n        wm.toggle_maximize().ok();\n\n        {\n            // No windows should be maximized\n            let workspace = wm.focused_workspace().unwrap();\n            let maximized_window = workspace.maximized_window;\n            assert_eq!(maximized_window, None);\n        }\n    }\n\n    #[test]\n    fn test_toggle_maximize_nonexistent_window() {\n        let (mut wm, _context) = setup_window_manager();\n\n        {\n            // Create a monitor\n            let mut m = monitor::new(\n                0,\n                Rect::default(),\n                Rect::default(),\n                \"TestMonitor\".to_string(),\n                \"TestDevice\".to_string(),\n                \"TestDeviceID\".to_string(),\n                Some(\"TestMonitorID\".to_string()),\n            );\n\n            // Create a container\n            let container = Container::default();\n\n            // Add the container to the workspace\n            let workspace = m.focused_workspace_mut().unwrap();\n            workspace.add_container_to_back(container);\n\n            // Add monitor to the window manager\n            wm.monitors_mut().push_back(m);\n        }\n\n        // Should return an error when trying to toggle maximize on a non-existent window\n        let result = wm.toggle_maximize();\n        assert!(\n            result.is_err(),\n            \"Expected an error when trying to toggle maximize on a non-existent window\"\n        );\n    }\n\n    #[test]\n    fn test_monocle_on_and_monocle_off() {\n        let (mut wm, _context) = setup_window_manager();\n\n        {\n            // Create a monitor\n            let mut m = monitor::new(\n                0,\n                Rect::default(),\n                Rect::default(),\n                \"TestMonitor\".to_string(),\n                \"TestDevice\".to_string(),\n                \"TestDeviceID\".to_string(),\n                Some(\"TestMonitorID\".to_string()),\n            );\n\n            // Create a container\n            let mut container = Container::default();\n\n            // Add a window to the container\n            container.windows_mut().push_back(Window::from(1));\n\n            // Should have 1 window in the container\n            assert_eq!(container.windows().len(), 1);\n\n            // Add the container to the workspace\n            let workspace = m.focused_workspace_mut().unwrap();\n            workspace.add_container_to_back(container);\n\n            // Add monitor to the window manager\n            wm.monitors_mut().push_back(m);\n        }\n\n        // Move container to monocle container\n        wm.monocle_on().ok();\n\n        {\n            // Container should be a monocle container\n            let monocle_container = wm\n                .focused_workspace()\n                .unwrap()\n                .monocle_container\n                .as_ref()\n                .unwrap();\n            assert_eq!(monocle_container.windows().len(), 1);\n            assert_eq!(monocle_container.windows()[0].hwnd, 1);\n        }\n\n        {\n            // Should not have any containers\n            let container = wm.focused_workspace().unwrap();\n            assert_eq!(container.containers().len(), 0);\n        }\n\n        // Move monocle container to regular container\n        wm.monocle_off().ok();\n\n        {\n            // Should have 1 container in the workspace\n            let container = wm.focused_workspace().unwrap();\n            assert_eq!(container.containers().len(), 1);\n            assert_eq!(container.containers()[0].windows()[0].hwnd, 1);\n        }\n\n        {\n            // No windows should be in the monocle container\n            let monocle_container = &wm.focused_workspace().unwrap().monocle_container;\n            assert_eq!(*monocle_container, None);\n        }\n    }\n\n    #[test]\n    fn test_monocle_on_and_off_nonexistent_container() {\n        let (mut wm, _context) = setup_window_manager();\n\n        {\n            // Create a monitor\n            let m = monitor::new(\n                0,\n                Rect::default(),\n                Rect::default(),\n                \"TestMonitor\".to_string(),\n                \"TestDevice\".to_string(),\n                \"TestDeviceID\".to_string(),\n                Some(\"TestMonitorID\".to_string()),\n            );\n\n            // Add monitor to the window manager\n            wm.monitors_mut().push_back(m);\n        }\n\n        // Should return an error when trying to move a non-existent container to monocle\n        let result = wm.monocle_on();\n        assert!(\n            result.is_err(),\n            \"Expected an error when trying to move a non-existent container to monocle\"\n        );\n\n        // Should return an error when trying to restore a non-existent container from monocle\n        let result = wm.monocle_off();\n        assert!(\n            result.is_err(),\n            \"Expected an error when trying to restore a non-existent container from monocle\"\n        );\n    }\n\n    #[test]\n    fn test_toggle_monocle() {\n        let (mut wm, _context) = setup_window_manager();\n\n        {\n            // Create a monitor\n            let mut m = monitor::new(\n                0,\n                Rect::default(),\n                Rect::default(),\n                \"TestMonitor\".to_string(),\n                \"TestDevice\".to_string(),\n                \"TestDeviceID\".to_string(),\n                Some(\"TestMonitorID\".to_string()),\n            );\n\n            // Create a container\n            let mut container = Container::default();\n\n            // Add a window to the container\n            container.windows_mut().push_back(Window::from(1));\n\n            // Should have 1 window in the container\n            assert_eq!(container.windows().len(), 1);\n\n            // Add the container to the workspace\n            let workspace = m.focused_workspace_mut().unwrap();\n            workspace.add_container_to_back(container);\n\n            // Add monitor to the window manager\n            wm.monitors_mut().push_back(m);\n        }\n\n        // Toggle monocle on\n        wm.toggle_monocle().ok();\n\n        {\n            // Container should be a monocle container\n            let monocle_container = wm\n                .focused_workspace()\n                .unwrap()\n                .monocle_container\n                .as_ref()\n                .unwrap();\n            assert_eq!(monocle_container.windows().len(), 1);\n            assert_eq!(monocle_container.windows()[0].hwnd, 1);\n        }\n\n        {\n            // Should not have any containers\n            let container = wm.focused_workspace().unwrap();\n            assert_eq!(container.containers().len(), 0);\n        }\n\n        // Toggle monocle off\n        wm.toggle_monocle().ok();\n\n        {\n            // Should have 1 container in the workspace\n            let container = wm.focused_workspace().unwrap();\n            assert_eq!(container.containers().len(), 1);\n            assert_eq!(container.containers()[0].windows()[0].hwnd, 1);\n        }\n\n        {\n            // No windows should be in the monocle container\n            let monocle_container = &wm.focused_workspace().unwrap().monocle_container;\n            assert_eq!(*monocle_container, None);\n        }\n    }\n\n    #[test]\n    fn test_toggle_monocle_nonexistent_container() {\n        let (mut wm, _context) = setup_window_manager();\n\n        {\n            // Create a monitor\n            let m = monitor::new(\n                0,\n                Rect::default(),\n                Rect::default(),\n                \"TestMonitor\".to_string(),\n                \"TestDevice\".to_string(),\n                \"TestDeviceID\".to_string(),\n                Some(\"TestMonitorID\".to_string()),\n            );\n\n            // Add monitor to the window manager\n            wm.monitors_mut().push_back(m);\n        }\n\n        // Should return an error when trying to toggle monocle on a non-existent container\n        let result = wm.toggle_monocle();\n        assert!(\n            result.is_err(),\n            \"Expected an error when trying to toggle monocle on a non-existent container\"\n        );\n    }\n\n    #[test]\n    fn test_ensure_named_workspace_for_monitor() {\n        let (mut wm, _context) = setup_window_manager();\n\n        {\n            // Create a monitor\n            let m = monitor::new(\n                0,\n                Rect::default(),\n                Rect::default(),\n                \"TestMonitor\".to_string(),\n                \"TestDevice\".to_string(),\n                \"TestDeviceID\".to_string(),\n                Some(\"TestMonitorID\".to_string()),\n            );\n\n            // Add the monitor to the window manager\n            wm.monitors_mut().push_back(m);\n        }\n\n        {\n            // Create a monitor\n            let m = monitor::new(\n                1,\n                Rect::default(),\n                Rect::default(),\n                \"TestMonitor1\".to_string(),\n                \"TestDevice1\".to_string(),\n                \"TestDeviceID1\".to_string(),\n                Some(\"TestMonitorID1\".to_string()),\n            );\n\n            // Add the monitor to the window manager\n            wm.monitors_mut().push_back(m);\n        }\n\n        // Workspace names list\n        let mut workspace_names = vec![\"Workspace\".to_string(), \"Workspace1\".to_string()];\n\n        // Ensure workspaces for monitor 1\n        wm.ensure_named_workspaces_for_monitor(1, &workspace_names)\n            .ok();\n\n        {\n            // Monitor 1 should have 2 workspaces with names \"Workspace\" and \"Workspace1\"\n            let monitor = wm.monitors().get(1).unwrap();\n            let workspaces = monitor.workspaces();\n            assert_eq!(workspaces.len(), workspace_names.len());\n            for (i, workspace) in workspaces.iter().enumerate() {\n                assert_eq!(workspace.name, Some(workspace_names[i].clone()));\n            }\n        }\n\n        // Add more workspaces to list\n        workspace_names.push(\"Workspace2\".to_string());\n        workspace_names.push(\"Workspace3\".to_string());\n\n        // Ensure workspaces for monitor 0\n        wm.ensure_named_workspaces_for_monitor(0, &workspace_names)\n            .ok();\n\n        {\n            // Monitor 0 should have 4 workspaces with names \"Workspace\", \"Workspace1\",\n            // \"Workspace2\" and \"Workspace3\"\n            let monitor = wm.monitors().front().unwrap();\n            let workspaces = monitor.workspaces();\n            assert_eq!(workspaces.len(), workspace_names.len());\n            for (i, workspace) in workspaces.iter().enumerate() {\n                assert_eq!(workspace.name, Some(workspace_names[i].clone()));\n            }\n        }\n    }\n\n    #[test]\n    fn test_add_window_handle_to_move_based_on_workspace_rule() {\n        let (wm, _context) = setup_window_manager();\n\n        // Mock Data representing a window and its workspace/movement details\n        let window_title = String::from(\"TestWindow\");\n        let hwnd = 12345;\n        let origin_monitor_idx = 0;\n        let origin_workspace_idx = 0;\n        let target_monitor_idx = 2;\n        let target_workspace_idx = 3;\n        let floating = false;\n\n        // Empty vector to hold workspace rule enforcement operations\n        let mut to_move: Vec<EnforceWorkspaceRuleOp> = Vec::new();\n\n        // Call the function to add a window movement operation based on workspace rules\n        wm.add_window_handle_to_move_based_on_workspace_rule(\n            &window_title,\n            hwnd,\n            origin_monitor_idx,\n            origin_workspace_idx,\n            target_monitor_idx,\n            target_workspace_idx,\n            floating,\n            &mut to_move,\n        );\n\n        // Verify that the vector contains the expected operation with the correct values\n        assert_eq!(to_move.len(), 1);\n        let op = &to_move[0];\n        assert_eq!(op.hwnd, hwnd); // 12345\n        assert_eq!(op.origin_monitor_idx, origin_monitor_idx); // 0\n        assert_eq!(op.origin_workspace_idx, origin_workspace_idx); // 0\n        assert_eq!(op.target_monitor_idx, target_monitor_idx); // 2\n        assert_eq!(op.target_workspace_idx, target_workspace_idx); // 3\n        assert_eq!(op.floating, floating); // false\n    }\n}\n"
  },
  {
    "path": "komorebi/src/window_manager_event.rs",
    "content": "use std::fmt::Display;\nuse std::fmt::Formatter;\n\nuse serde::Deserialize;\nuse serde::Serialize;\n\nuse crate::OBJECT_NAME_CHANGE_ON_LAUNCH;\nuse crate::OBJECT_NAME_CHANGE_TITLE_IGNORE_LIST;\nuse crate::REGEX_IDENTIFIERS;\nuse crate::window::Window;\nuse crate::window::should_act;\nuse crate::winevent::WinEvent;\n\n#[derive(Debug, Copy, Clone, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n#[serde(tag = \"type\", content = \"content\")]\npub enum WindowManagerEvent {\n    Destroy(WinEvent, Window),\n    FocusChange(WinEvent, Window),\n    Hide(WinEvent, Window),\n    Cloak(WinEvent, Window),\n    Minimize(WinEvent, Window),\n    Show(WinEvent, Window),\n    Uncloak(WinEvent, Window),\n    MoveResizeStart(WinEvent, Window),\n    MoveResizeEnd(WinEvent, Window),\n    MouseCapture(WinEvent, Window),\n    Manage(Window),\n    Unmanage(Window),\n    Raise(Window),\n    TitleUpdate(WinEvent, Window),\n}\n\nimpl Display for WindowManagerEvent {\n    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {\n        match self {\n            Self::Manage(window) => {\n                write!(f, \"Manage (Window: {window})\")\n            }\n            Self::Unmanage(window) => {\n                write!(f, \"Unmanage (Window: {window})\")\n            }\n            Self::Destroy(winevent, window) => {\n                write!(f, \"Destroy (WinEvent: {winevent}, Window: {window})\")\n            }\n            Self::FocusChange(winevent, window) => {\n                write!(f, \"FocusChange (WinEvent: {winevent}, Window: {window})\",)\n            }\n            Self::Hide(winevent, window) => {\n                write!(f, \"Hide (WinEvent: {winevent}, Window: {window})\")\n            }\n            Self::Cloak(winevent, window) => {\n                write!(f, \"Cloak (WinEvent: {winevent}, Window: {window})\")\n            }\n            Self::Minimize(winevent, window) => {\n                write!(f, \"Minimize (WinEvent: {winevent}, Window: {window})\")\n            }\n            Self::Show(winevent, window) => {\n                write!(f, \"Show (WinEvent: {winevent}, Window: {window})\")\n            }\n            Self::Uncloak(winevent, window) => {\n                write!(f, \"Uncloak (WinEvent: {winevent}, Window: {window})\")\n            }\n            Self::MoveResizeStart(winevent, window) => {\n                write!(\n                    f,\n                    \"MoveResizeStart (WinEvent: {winevent}, Window: {window})\",\n                )\n            }\n            Self::MoveResizeEnd(winevent, window) => {\n                write!(f, \"MoveResizeEnd (WinEvent: {winevent}, Window: {window})\",)\n            }\n            Self::MouseCapture(winevent, window) => {\n                write!(f, \"MouseCapture (WinEvent: {winevent}, Window: {window})\",)\n            }\n            Self::Raise(window) => {\n                write!(f, \"Raise (Window: {window})\")\n            }\n            Self::TitleUpdate(winevent, window) => {\n                write!(f, \"TitleUpdate (WinEvent: {winevent}, Window: {window})\")\n            }\n        }\n    }\n}\n\nimpl WindowManagerEvent {\n    pub const fn window(self) -> Window {\n        match self {\n            Self::Destroy(_, window)\n            | Self::FocusChange(_, window)\n            | Self::Hide(_, window)\n            | Self::Cloak(_, window)\n            | Self::Minimize(_, window)\n            | Self::Show(_, window)\n            | Self::Uncloak(_, window)\n            | Self::MoveResizeStart(_, window)\n            | Self::MoveResizeEnd(_, window)\n            | Self::MouseCapture(_, window)\n            | Self::Raise(window)\n            | Self::Manage(window)\n            | Self::Unmanage(window)\n            | Self::TitleUpdate(_, window) => window,\n        }\n    }\n\n    pub const fn hwnd(self) -> isize {\n        self.window().hwnd\n    }\n\n    pub const fn title(self) -> &'static str {\n        match self {\n            WindowManagerEvent::Destroy(_, _) => \"Destroy\",\n            WindowManagerEvent::FocusChange(_, _) => \"FocusChange\",\n            WindowManagerEvent::Hide(_, _) => \"Hide\",\n            WindowManagerEvent::Cloak(_, _) => \"Cloak\",\n            WindowManagerEvent::Minimize(_, _) => \"Minimize\",\n            WindowManagerEvent::Show(_, _) => \"Show\",\n            WindowManagerEvent::Uncloak(_, _) => \"Uncloak\",\n            WindowManagerEvent::MoveResizeStart(_, _) => \"MoveResizeStart\",\n            WindowManagerEvent::MoveResizeEnd(_, _) => \"MoveResizeEnd\",\n            WindowManagerEvent::MouseCapture(_, _) => \"MouseCapture\",\n            WindowManagerEvent::Manage(_) => \"Manage\",\n            WindowManagerEvent::Unmanage(_) => \"Unmanage\",\n            WindowManagerEvent::Raise(_) => \"Raise\",\n            WindowManagerEvent::TitleUpdate(_, _) => \"TitleUpdate\",\n        }\n    }\n\n    pub fn winevent(self) -> Option<String> {\n        match self {\n            WindowManagerEvent::Destroy(event, _)\n            | WindowManagerEvent::FocusChange(event, _)\n            | WindowManagerEvent::Hide(event, _)\n            | WindowManagerEvent::Cloak(event, _)\n            | WindowManagerEvent::Minimize(event, _)\n            | WindowManagerEvent::Show(event, _)\n            | WindowManagerEvent::Uncloak(event, _)\n            | WindowManagerEvent::MoveResizeStart(event, _)\n            | WindowManagerEvent::MoveResizeEnd(event, _)\n            | WindowManagerEvent::MouseCapture(event, _)\n            | WindowManagerEvent::TitleUpdate(event, _) => Some(event.to_string()),\n            WindowManagerEvent::Manage(_)\n            | WindowManagerEvent::Unmanage(_)\n            | WindowManagerEvent::Raise(_) => None,\n        }\n    }\n\n    pub fn from_win_event(winevent: WinEvent, window: Window) -> Option<Self> {\n        match winevent {\n            WinEvent::ObjectDestroy => Option::from(Self::Destroy(winevent, window)),\n\n            WinEvent::ObjectHide => Option::from(Self::Hide(winevent, window)),\n            WinEvent::ObjectCloaked => Option::from(Self::Cloak(winevent, window)),\n\n            WinEvent::SystemMinimizeStart => Option::from(Self::Minimize(winevent, window)),\n\n            WinEvent::ObjectShow | WinEvent::SystemMinimizeEnd => {\n                Option::from(Self::Show(winevent, window))\n            }\n\n            WinEvent::ObjectUncloaked => Option::from(Self::Uncloak(winevent, window)),\n\n            WinEvent::ObjectFocus | WinEvent::SystemForeground => {\n                Option::from(Self::FocusChange(winevent, window))\n            }\n            WinEvent::SystemMoveSizeStart => Option::from(Self::MoveResizeStart(winevent, window)),\n            WinEvent::SystemMoveSizeEnd => Option::from(Self::MoveResizeEnd(winevent, window)),\n            WinEvent::SystemCaptureStart | WinEvent::SystemCaptureEnd => {\n                Option::from(Self::MouseCapture(winevent, window))\n            }\n            WinEvent::ObjectNameChange => {\n                // Some apps like Firefox don't send ObjectCreate or ObjectShow on launch\n                // This spams the message queue, but I don't know what else to do. On launch\n                // it only sends the following WinEvents :/\n                //\n                // [yatta\\src\\windows_event.rs:110] event = 32780 ObjectNameChange\n                // [yatta\\src\\windows_event.rs:110] event = 32779 ObjectLocationChange\n\n                let object_name_change_on_launch = OBJECT_NAME_CHANGE_ON_LAUNCH.lock();\n                let object_name_change_title_ignore_list =\n                    OBJECT_NAME_CHANGE_TITLE_IGNORE_LIST.lock();\n                let regex_identifiers = REGEX_IDENTIFIERS.lock();\n\n                let title = &window.title().ok()?;\n                let exe_name = &window.exe().ok()?;\n                let class = &window.class().ok()?;\n                let path = &window.path().ok()?;\n\n                let mut should_trigger_show = should_act(\n                    title,\n                    exe_name,\n                    class,\n                    path,\n                    &object_name_change_on_launch,\n                    &regex_identifiers,\n                )\n                .is_some();\n\n                if should_trigger_show {\n                    for r in &*object_name_change_title_ignore_list {\n                        if r.is_match(title) {\n                            should_trigger_show = false;\n                        }\n                    }\n                }\n\n                // should not trigger show on minimized windows, for example when firefox sends\n                // this message due to youtube autoplay changing the window title\n                // https://github.com/LGUG2Z/komorebi/issues/941\n                if should_trigger_show && !window.is_miminized() {\n                    Option::from(Self::Show(winevent, window))\n                } else {\n                    Option::from(Self::TitleUpdate(winevent, window))\n                }\n            }\n            _ => None,\n        }\n    }\n}\n"
  },
  {
    "path": "komorebi/src/windows_api.rs",
    "content": "use color_eyre::eyre;\nuse color_eyre::eyre::Error;\nuse color_eyre::eyre::OptionExt;\nuse color_eyre::eyre::bail;\nuse core::ffi::c_void;\nuse std::collections::HashMap;\nuse std::collections::VecDeque;\nuse std::convert::TryFrom;\nuse std::mem::size_of;\nuse std::path::Path;\nuse windows::Win32::Foundation::COLORREF;\nuse windows::Win32::Foundation::CloseHandle;\nuse windows::Win32::Foundation::GetLastError;\nuse windows::Win32::Foundation::HANDLE;\nuse windows::Win32::Foundation::HINSTANCE;\nuse windows::Win32::Foundation::HMODULE;\nuse windows::Win32::Foundation::HWND;\nuse windows::Win32::Foundation::LPARAM;\nuse windows::Win32::Foundation::POINT;\nuse windows::Win32::Foundation::RECT;\nuse windows::Win32::Foundation::SetLastError;\nuse windows::Win32::Foundation::WIN32_ERROR;\nuse windows::Win32::Foundation::WPARAM;\nuse windows::Win32::Graphics::Dwm::DWM_CLOAKED_APP;\nuse windows::Win32::Graphics::Dwm::DWM_CLOAKED_INHERITED;\nuse windows::Win32::Graphics::Dwm::DWM_CLOAKED_SHELL;\nuse windows::Win32::Graphics::Dwm::DWMWA_BORDER_COLOR;\nuse windows::Win32::Graphics::Dwm::DWMWA_CLOAKED;\nuse windows::Win32::Graphics::Dwm::DWMWA_COLOR_NONE;\nuse windows::Win32::Graphics::Dwm::DWMWA_EXTENDED_FRAME_BOUNDS;\nuse windows::Win32::Graphics::Dwm::DWMWA_WINDOW_CORNER_PREFERENCE;\nuse windows::Win32::Graphics::Dwm::DWMWCP_ROUND;\nuse windows::Win32::Graphics::Dwm::DWMWINDOWATTRIBUTE;\nuse windows::Win32::Graphics::Dwm::DwmGetWindowAttribute;\nuse windows::Win32::Graphics::Dwm::DwmSetWindowAttribute;\nuse windows::Win32::Graphics::Gdi::CreateSolidBrush;\nuse windows::Win32::Graphics::Gdi::EnumDisplayMonitors;\nuse windows::Win32::Graphics::Gdi::GetMonitorInfoW;\nuse windows::Win32::Graphics::Gdi::HBRUSH;\nuse windows::Win32::Graphics::Gdi::HDC;\nuse windows::Win32::Graphics::Gdi::HMONITOR;\nuse windows::Win32::Graphics::Gdi::InvalidateRect;\nuse windows::Win32::Graphics::Gdi::MONITOR_DEFAULTTONEAREST;\nuse windows::Win32::Graphics::Gdi::MONITORENUMPROC;\nuse windows::Win32::Graphics::Gdi::MONITORINFOEXW;\nuse windows::Win32::Graphics::Gdi::MonitorFromPoint;\nuse windows::Win32::Graphics::Gdi::MonitorFromWindow;\nuse windows::Win32::Graphics::Gdi::Rectangle;\nuse windows::Win32::Graphics::Gdi::RoundRect;\nuse windows::Win32::System::Com::CLSCTX_ALL;\nuse windows::Win32::System::Com::CoCreateInstance;\nuse windows::Win32::System::LibraryLoader::GetModuleHandleW;\nuse windows::Win32::System::Power::HPOWERNOTIFY;\nuse windows::Win32::System::Power::RegisterPowerSettingNotification;\nuse windows::Win32::System::RemoteDesktop::ProcessIdToSessionId;\nuse windows::Win32::System::RemoteDesktop::WTSRegisterSessionNotification;\nuse windows::Win32::System::Threading::GetCurrentProcessId;\nuse windows::Win32::System::Threading::OpenProcess;\nuse windows::Win32::System::Threading::PROCESS_ACCESS_RIGHTS;\nuse windows::Win32::System::Threading::PROCESS_NAME_WIN32;\nuse windows::Win32::System::Threading::PROCESS_QUERY_INFORMATION;\nuse windows::Win32::System::Threading::QueryFullProcessImageNameW;\nuse windows::Win32::UI::HiDpi::DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2;\nuse windows::Win32::UI::HiDpi::GetDpiForMonitor;\nuse windows::Win32::UI::HiDpi::MDT_EFFECTIVE_DPI;\nuse windows::Win32::UI::HiDpi::SetProcessDpiAwarenessContext;\nuse windows::Win32::UI::Input::KeyboardAndMouse::GetKeyState;\nuse windows::Win32::UI::Input::KeyboardAndMouse::INPUT;\nuse windows::Win32::UI::Input::KeyboardAndMouse::INPUT_0;\nuse windows::Win32::UI::Input::KeyboardAndMouse::INPUT_MOUSE;\nuse windows::Win32::UI::Input::KeyboardAndMouse::MOUSEEVENTF_LEFTDOWN;\nuse windows::Win32::UI::Input::KeyboardAndMouse::MOUSEEVENTF_LEFTUP;\nuse windows::Win32::UI::Input::KeyboardAndMouse::MOUSEINPUT;\nuse windows::Win32::UI::Input::KeyboardAndMouse::SendInput;\nuse windows::Win32::UI::Input::KeyboardAndMouse::VK_LBUTTON;\nuse windows::Win32::UI::Input::KeyboardAndMouse::VK_MENU;\nuse windows::Win32::UI::Shell::DWPOS_FILL;\nuse windows::Win32::UI::Shell::DesktopWallpaper;\nuse windows::Win32::UI::Shell::IDesktopWallpaper;\nuse windows::Win32::UI::WindowsAndMessaging::AllowSetForegroundWindow;\nuse windows::Win32::UI::WindowsAndMessaging::BringWindowToTop;\nuse windows::Win32::UI::WindowsAndMessaging::CW_USEDEFAULT;\nuse windows::Win32::UI::WindowsAndMessaging::CreateWindowExW;\nuse windows::Win32::UI::WindowsAndMessaging::DEV_BROADCAST_DEVICEINTERFACE_W;\nuse windows::Win32::UI::WindowsAndMessaging::EnumWindows;\nuse windows::Win32::UI::WindowsAndMessaging::GW_HWNDNEXT;\nuse windows::Win32::UI::WindowsAndMessaging::GWL_EXSTYLE;\nuse windows::Win32::UI::WindowsAndMessaging::GWL_STYLE;\nuse windows::Win32::UI::WindowsAndMessaging::GetCursorPos;\nuse windows::Win32::UI::WindowsAndMessaging::GetDesktopWindow;\nuse windows::Win32::UI::WindowsAndMessaging::GetForegroundWindow;\nuse windows::Win32::UI::WindowsAndMessaging::GetLayeredWindowAttributes;\nuse windows::Win32::UI::WindowsAndMessaging::GetTopWindow;\nuse windows::Win32::UI::WindowsAndMessaging::GetWindow;\nuse windows::Win32::UI::WindowsAndMessaging::GetWindowLongPtrW;\nuse windows::Win32::UI::WindowsAndMessaging::GetWindowRect;\nuse windows::Win32::UI::WindowsAndMessaging::GetWindowTextW;\nuse windows::Win32::UI::WindowsAndMessaging::GetWindowThreadProcessId;\nuse windows::Win32::UI::WindowsAndMessaging::HDEVNOTIFY;\nuse windows::Win32::UI::WindowsAndMessaging::HWND_BOTTOM;\nuse windows::Win32::UI::WindowsAndMessaging::HWND_TOP;\nuse windows::Win32::UI::WindowsAndMessaging::IsIconic;\nuse windows::Win32::UI::WindowsAndMessaging::IsWindow;\nuse windows::Win32::UI::WindowsAndMessaging::IsWindowVisible;\nuse windows::Win32::UI::WindowsAndMessaging::IsZoomed;\nuse windows::Win32::UI::WindowsAndMessaging::LWA_ALPHA;\nuse windows::Win32::UI::WindowsAndMessaging::MoveWindow;\nuse windows::Win32::UI::WindowsAndMessaging::PostMessageW;\nuse windows::Win32::UI::WindowsAndMessaging::REGISTER_NOTIFICATION_FLAGS;\nuse windows::Win32::UI::WindowsAndMessaging::RealGetWindowClassW;\nuse windows::Win32::UI::WindowsAndMessaging::RegisterClassW;\nuse windows::Win32::UI::WindowsAndMessaging::RegisterDeviceNotificationW;\nuse windows::Win32::UI::WindowsAndMessaging::SET_WINDOW_POS_FLAGS;\nuse windows::Win32::UI::WindowsAndMessaging::SHOW_WINDOW_CMD;\nuse windows::Win32::UI::WindowsAndMessaging::SPI_GETACTIVEWINDOWTRACKING;\nuse windows::Win32::UI::WindowsAndMessaging::SPI_GETFOREGROUNDLOCKTIMEOUT;\nuse windows::Win32::UI::WindowsAndMessaging::SPI_SETACTIVEWINDOWTRACKING;\nuse windows::Win32::UI::WindowsAndMessaging::SPI_SETFOREGROUNDLOCKTIMEOUT;\nuse windows::Win32::UI::WindowsAndMessaging::SPIF_SENDCHANGE;\nuse windows::Win32::UI::WindowsAndMessaging::SW_HIDE;\nuse windows::Win32::UI::WindowsAndMessaging::SW_MAXIMIZE;\nuse windows::Win32::UI::WindowsAndMessaging::SW_MINIMIZE;\nuse windows::Win32::UI::WindowsAndMessaging::SW_NORMAL;\nuse windows::Win32::UI::WindowsAndMessaging::SW_SHOWNOACTIVATE;\nuse windows::Win32::UI::WindowsAndMessaging::SWP_ASYNCWINDOWPOS;\nuse windows::Win32::UI::WindowsAndMessaging::SWP_NOMOVE;\nuse windows::Win32::UI::WindowsAndMessaging::SWP_NOSIZE;\nuse windows::Win32::UI::WindowsAndMessaging::SWP_SHOWWINDOW;\nuse windows::Win32::UI::WindowsAndMessaging::SYSTEM_PARAMETERS_INFO_ACTION;\nuse windows::Win32::UI::WindowsAndMessaging::SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS;\nuse windows::Win32::UI::WindowsAndMessaging::SetCursorPos;\nuse windows::Win32::UI::WindowsAndMessaging::SetForegroundWindow;\nuse windows::Win32::UI::WindowsAndMessaging::SetLayeredWindowAttributes;\nuse windows::Win32::UI::WindowsAndMessaging::SetWindowLongPtrW;\nuse windows::Win32::UI::WindowsAndMessaging::SetWindowPos;\nuse windows::Win32::UI::WindowsAndMessaging::ShowWindow;\nuse windows::Win32::UI::WindowsAndMessaging::ShowWindowAsync;\nuse windows::Win32::UI::WindowsAndMessaging::SystemParametersInfoW;\nuse windows::Win32::UI::WindowsAndMessaging::WINDOW_LONG_PTR_INDEX;\nuse windows::Win32::UI::WindowsAndMessaging::WM_CLOSE;\nuse windows::Win32::UI::WindowsAndMessaging::WNDCLASSW;\nuse windows::Win32::UI::WindowsAndMessaging::WNDENUMPROC;\nuse windows::Win32::UI::WindowsAndMessaging::WS_DISABLED;\nuse windows::Win32::UI::WindowsAndMessaging::WS_EX_NOACTIVATE;\nuse windows::Win32::UI::WindowsAndMessaging::WS_EX_TOOLWINDOW;\nuse windows::Win32::UI::WindowsAndMessaging::WS_EX_TOPMOST;\nuse windows::Win32::UI::WindowsAndMessaging::WS_POPUP;\nuse windows::Win32::UI::WindowsAndMessaging::WS_SYSMENU;\nuse windows::Win32::UI::WindowsAndMessaging::WindowFromPoint;\nuse windows::core::PCWSTR;\nuse windows::core::PWSTR;\nuse windows::core::Result as WindowsCrateResult;\nuse windows_core::BOOL;\nuse windows_core::HSTRING;\n\nuse crate::core::Rect;\n\nuse crate::DISPLAY_INDEX_PREFERENCES;\nuse crate::DUPLICATE_MONITOR_SERIAL_IDS;\nuse crate::MONITOR_INDEX_PREFERENCES;\nuse crate::WINDOW_HANDLING_BEHAVIOUR;\nuse crate::Window;\nuse crate::WindowHandlingBehaviour;\nuse crate::WindowManager;\nuse crate::container::Container;\nuse crate::monitor;\nuse crate::monitor::Monitor;\nuse crate::ring::Ring;\nuse crate::set_window_position::SetWindowPosition;\nuse crate::windows_callbacks;\n\nmacro_rules! as_ptr {\n    ($value:expr) => {\n        $value as *mut core::ffi::c_void\n    };\n}\n\nuse crate::border_manager::Border;\npub(crate) use as_ptr;\n\npub enum WindowsResult<T, E> {\n    Err(E),\n    Ok(T),\n}\n\nmacro_rules! impl_from_integer_for_windows_result {\n    ( $( $integer_type:ty ),+ ) => {\n        $(\n            impl From<$integer_type> for WindowsResult<$integer_type, Error> {\n                fn from(return_value: $integer_type) -> Self {\n                    match return_value {\n                        0 => Self::Err(std::io::Error::last_os_error().into()),\n                        _ => Self::Ok(return_value),\n                    }\n                }\n            }\n        )+\n    };\n}\n\nimpl_from_integer_for_windows_result!(usize, isize, u16, u32, i32);\n\nimpl<T, E> From<WindowsResult<T, E>> for Result<T, E> {\n    fn from(result: WindowsResult<T, E>) -> Self {\n        match result {\n            WindowsResult::Err(error) => Err(error),\n            WindowsResult::Ok(ok) => Ok(ok),\n        }\n    }\n}\n\npub trait ProcessWindowsCrateResult<T> {\n    fn process(self) -> eyre::Result<T>;\n}\n\nmacro_rules! impl_process_windows_crate_integer_wrapper_result {\n    ( $($input:ty => $deref:ty),+ $(,)? ) => (\n        paste::paste! {\n            $(\n                impl ProcessWindowsCrateResult<$deref> for $input {\n                    fn process(self) -> eyre::Result<$deref> {\n                        if self == $input(std::ptr::null_mut()) {\n                            Err(std::io::Error::last_os_error().into())\n                        } else {\n                            Ok(self.0 as $deref)\n                        }\n                    }\n                }\n            )+\n        }\n    );\n}\n\nimpl_process_windows_crate_integer_wrapper_result!(\n    HWND => isize,\n);\n\nimpl<T> ProcessWindowsCrateResult<T> for WindowsCrateResult<T> {\n    fn process(self) -> eyre::Result<T> {\n        match self {\n            Ok(value) => Ok(value),\n            Err(error) => Err(error.into()),\n        }\n    }\n}\n\npub struct WindowsApi;\n\nimpl WindowsApi {\n    pub fn enum_display_monitors(\n        callback: MONITORENUMPROC,\n        callback_data_address: isize,\n    ) -> eyre::Result<()> {\n        unsafe { EnumDisplayMonitors(None, None, callback, LPARAM(callback_data_address)) }\n            .ok()\n            .process()\n    }\n\n    pub fn valid_hmonitors() -> eyre::Result<Vec<(String, isize)>> {\n        Ok(win32_display_data::connected_displays_all()\n            .flatten()\n            .map(|d| {\n                let name = d.device_name.trim_start_matches(r\"\\\\.\\\").to_string();\n                let name = name.split('\\\\').collect::<Vec<_>>()[0].to_string();\n\n                (name, d.hmonitor)\n            })\n            .collect::<Vec<_>>())\n    }\n\n    pub fn load_monitor_information(wm: &mut WindowManager) -> eyre::Result<()> {\n        let monitors = &mut wm.monitors;\n        let monitor_usr_idx_map = &mut wm.monitor_usr_idx_map;\n\n        let all_displays = win32_display_data::connected_displays_all()\n            .flatten()\n            .collect::<Vec<_>>();\n\n        let mut serial_id_map = HashMap::new();\n\n        for d in &all_displays {\n            if let Some(id) = &d.serial_number_id {\n                *serial_id_map.entry(id.clone()).or_insert(0) += 1;\n            }\n        }\n\n        for d in &all_displays {\n            if let Some(id) = &d.serial_number_id\n                && serial_id_map.get(id).copied().unwrap_or_default() > 1\n            {\n                let mut dupes = DUPLICATE_MONITOR_SERIAL_IDS.write();\n                if !dupes.contains(id) {\n                    (*dupes).push(id.clone());\n                }\n            }\n        }\n\n        'read: for mut display in all_displays {\n            let path = display.device_path.clone();\n\n            let (device, device_id) = if path.is_empty() {\n                (String::from(\"UNKNOWN\"), String::from(\"UNKNOWN\"))\n            } else {\n                let mut split: Vec<_> = path.split('#').collect();\n                split.remove(0);\n                split.remove(split.len() - 1);\n                let device = split[0].to_string();\n                let device_id = split.join(\"-\");\n                (device, device_id)\n            };\n\n            let name = display.device_name.trim_start_matches(r\"\\\\.\\\").to_string();\n            let name = name.split('\\\\').collect::<Vec<_>>()[0].to_string();\n\n            for monitor in monitors.elements() {\n                if device_id.eq(&monitor.device_id) {\n                    continue 'read;\n                }\n            }\n\n            if let Some(id) = &display.serial_number_id {\n                let dupes = DUPLICATE_MONITOR_SERIAL_IDS.read();\n                if dupes.contains(id) {\n                    display.serial_number_id = None;\n                }\n            }\n\n            let m = monitor::new(\n                display.hmonitor,\n                display.size.into(),\n                display.work_area_size.into(),\n                name,\n                device,\n                device_id,\n                display.serial_number_id,\n            );\n\n            let mut index_preference = None;\n            let monitor_index_preferences = MONITOR_INDEX_PREFERENCES.lock();\n            for (index, monitor_size) in &*monitor_index_preferences {\n                if m.size == *monitor_size {\n                    index_preference = Option::from(index);\n                }\n            }\n\n            let display_index_preferences = DISPLAY_INDEX_PREFERENCES.read();\n            for (index, id) in &*display_index_preferences {\n                if m.serial_number_id.as_ref().is_some_and(|sn| sn == id) || id.eq(&m.device_id) {\n                    index_preference = Option::from(index);\n                }\n            }\n\n            if let Some(preference) = index_preference {\n                while *preference >= monitors.elements().len() {\n                    monitors.elements_mut().push_back(Monitor::placeholder());\n                }\n\n                let current_name = monitors\n                    .elements_mut()\n                    .get(*preference)\n                    .map_or(\"\", |m| &m.name);\n                if current_name == \"PLACEHOLDER\" {\n                    let _ = monitors.elements_mut().remove(*preference);\n                    monitors.elements_mut().insert(*preference, m);\n                } else {\n                    monitors.elements_mut().insert(*preference, m);\n                }\n            } else {\n                monitors.elements_mut().push_back(m);\n            }\n        }\n\n        monitors.elements_mut().retain(|m| m.name.ne(\"PLACEHOLDER\"));\n\n        // Rebuild monitor index map\n        *monitor_usr_idx_map = HashMap::new();\n        let mut added_monitor_idxs = Vec::new();\n        for (index, id) in &*DISPLAY_INDEX_PREFERENCES.read() {\n            if let Some(m_idx) = monitors.elements().iter().position(|m| {\n                m.serial_number_id.as_ref().is_some_and(|sn| sn == id) || m.device_id.eq(id)\n            }) {\n                monitor_usr_idx_map.insert(*index, m_idx);\n                added_monitor_idxs.push(m_idx);\n            }\n        }\n\n        let max_usr_idx = monitors\n            .elements()\n            .len()\n            .max(monitor_usr_idx_map.keys().max().map_or(0, |v| *v));\n\n        let mut available_usr_idxs = (0..max_usr_idx)\n            .filter(|i| !monitor_usr_idx_map.contains_key(i))\n            .collect::<Vec<_>>();\n\n        let not_added_monitor_idxs = (0..monitors.elements().len())\n            .filter(|i| !added_monitor_idxs.contains(i))\n            .collect::<Vec<_>>();\n\n        for i in not_added_monitor_idxs {\n            if let Some(next_usr_idx) = available_usr_idxs.first() {\n                monitor_usr_idx_map.insert(*next_usr_idx, i);\n                available_usr_idxs.remove(0);\n            } else if let Some(idx) = monitor_usr_idx_map.keys().max() {\n                monitor_usr_idx_map.insert(*idx, i);\n            }\n        }\n\n        Ok(())\n    }\n\n    pub fn enum_windows(callback: WNDENUMPROC, callback_data_address: isize) -> eyre::Result<()> {\n        unsafe { EnumWindows(callback, LPARAM(callback_data_address)) }.process()\n    }\n\n    pub fn load_workspace_information(monitors: &mut Ring<Monitor>) -> eyre::Result<()> {\n        for monitor in monitors.elements_mut() {\n            let monitor_name = monitor.name.clone();\n            if let Some(workspace) = monitor.workspaces_mut().front_mut() {\n                // EnumWindows will enumerate through windows on all monitors\n                Self::enum_windows(\n                    Some(windows_callbacks::enum_window),\n                    workspace.containers_mut() as *mut VecDeque<Container> as isize,\n                )?;\n\n                // Ensure that the resize_dimensions Vec length matches the number of containers for\n                // the potential later calls to workspace.remove_window later in this fn\n                let len = workspace.containers().len();\n                workspace.resize_dimensions.resize(len, None);\n\n                // We have to prune each monitor's primary workspace of undesired windows here\n                let mut windows_on_other_monitors = vec![];\n\n                for container in workspace.containers_mut() {\n                    for window in container.windows() {\n                        if Self::monitor_name_from_window(window.hwnd)? != monitor_name {\n                            windows_on_other_monitors.push(window.hwnd);\n                        }\n                    }\n                }\n\n                for hwnd in windows_on_other_monitors {\n                    workspace.remove_window(hwnd)?;\n                }\n            }\n        }\n\n        Ok(())\n    }\n\n    pub fn allow_set_foreground_window(process_id: u32) -> eyre::Result<()> {\n        unsafe { AllowSetForegroundWindow(process_id) }.process()\n    }\n\n    pub fn monitor_from_window(hwnd: isize) -> isize {\n        // MONITOR_DEFAULTTONEAREST ensures that the return value will never be NULL\n        // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-monitorfromwindow\n        unsafe { MonitorFromWindow(HWND(as_ptr!(hwnd)), MONITOR_DEFAULTTONEAREST) }.0 as isize\n    }\n\n    pub fn monitor_name_from_window(hwnd: isize) -> eyre::Result<String> {\n        // MONITOR_DEFAULTTONEAREST ensures that the return value will never be NULL\n        // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-monitorfromwindow\n        Ok(Self::monitor(\n            unsafe { MonitorFromWindow(HWND(as_ptr!(hwnd)), MONITOR_DEFAULTTONEAREST) }.0 as isize,\n        )?\n        .name\n        .to_string())\n    }\n\n    pub fn monitor_from_point(point: POINT) -> isize {\n        // MONITOR_DEFAULTTONEAREST ensures that the return value will never be NULL\n        // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-monitorfromwindow\n        unsafe { MonitorFromPoint(point, MONITOR_DEFAULTTONEAREST) }.0 as isize\n    }\n\n    /// position window resizes the target window to the given layout, adjusting\n    /// the layout to account for any window shadow borders (the window painted\n    /// region will match layout on completion).\n    pub fn position_window(\n        hwnd: isize,\n        layout: &Rect,\n        top: bool,\n        with_async_window_pos: bool,\n    ) -> eyre::Result<()> {\n        let hwnd = HWND(as_ptr!(hwnd));\n\n        let mut flags = SetWindowPosition::NO_ACTIVATE\n            | SetWindowPosition::NO_SEND_CHANGING\n            | SetWindowPosition::NO_COPY_BITS\n            | SetWindowPosition::FRAME_CHANGED;\n\n        // If the request is to place the window on top, then HWND_TOP will take\n        // effect, otherwise pass NO_Z_ORDER that will cause set_window_pos to\n        // ignore the z-order paramter.\n\n        // By default SetWindowPos waits for target window's WindowProc thread\n        // to process the message, so we have to use ASYNC_WINDOW_POS to avoid\n        // blocking our thread in case the target window is not responding.\n        if with_async_window_pos\n            && matches!(\n                WINDOW_HANDLING_BEHAVIOUR.load(),\n                WindowHandlingBehaviour::Async\n            )\n        {\n            flags |= SetWindowPosition::ASYNC_WINDOW_POS;\n        }\n\n        if !top {\n            flags |= SetWindowPosition::NO_Z_ORDER;\n        }\n\n        let shadow_rect = Self::shadow_rect(hwnd).unwrap_or_default();\n        let rect = Rect {\n            left: layout.left + shadow_rect.left,\n            top: layout.top + shadow_rect.top,\n            right: layout.right + shadow_rect.right,\n            bottom: layout.bottom + shadow_rect.bottom,\n        };\n\n        // Note: earlier code had set HWND_TOPMOST here, but we should not do\n        // that. HWND_TOPMOST is a sticky z-order change, rather than a regular\n        // z-order reordering. Programs will use TOPMOST themselves to do things\n        // such as making sure that their tool windows or dialog pop-ups are\n        // above their main window. If any such windows are unmanaged, they must\n        // still remian topmost, so we set HWND_TOP here, which will cause the\n        // managed window to come to the front, but if the managed window has a\n        // child that is TOPMOST it will still be rendered above, in the proper\n        // order expected by the application. It's also important to understand\n        // that TOPMOST is somewhat viral, in that when you set a window to\n        // TOPMOST all of its owned windows are also made TOPMOST.\n        // See https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowpos#remarks\n        Self::set_window_pos(hwnd, &rect, HWND_TOP, flags.bits())\n    }\n\n    pub fn bring_window_to_top(hwnd: isize) -> eyre::Result<()> {\n        unsafe { BringWindowToTop(HWND(as_ptr!(hwnd))) }.process()\n    }\n\n    /// Raise the window to the top of the Z order, but do not activate or focus\n    /// it. Use raise_and_focus_window to activate and focus a window.\n    pub fn raise_window(hwnd: isize) -> eyre::Result<()> {\n        let mut flags = SetWindowPosition::NO_MOVE\n            | SetWindowPosition::NO_SIZE\n            | SetWindowPosition::NO_ACTIVATE\n            | SetWindowPosition::SHOW_WINDOW;\n\n        if matches!(\n            WINDOW_HANDLING_BEHAVIOUR.load(),\n            WindowHandlingBehaviour::Async\n        ) {\n            flags |= SetWindowPosition::ASYNC_WINDOW_POS;\n        }\n\n        let position = HWND_TOP;\n        Self::set_window_pos(\n            HWND(as_ptr!(hwnd)),\n            &Rect::default(),\n            position,\n            flags.bits(),\n        )\n    }\n\n    /// Lower the window to the bottom of the Z order, but do not activate or focus\n    /// it.\n    pub fn lower_window(hwnd: isize) -> eyre::Result<()> {\n        let mut flags = SetWindowPosition::NO_MOVE\n            | SetWindowPosition::NO_SIZE\n            | SetWindowPosition::NO_ACTIVATE\n            | SetWindowPosition::SHOW_WINDOW;\n\n        if matches!(\n            WINDOW_HANDLING_BEHAVIOUR.load(),\n            WindowHandlingBehaviour::Async\n        ) {\n            flags |= SetWindowPosition::ASYNC_WINDOW_POS;\n        }\n\n        let position = HWND_BOTTOM;\n        Self::set_window_pos(\n            HWND(as_ptr!(hwnd)),\n            &Rect::default(),\n            position,\n            flags.bits(),\n        )\n    }\n\n    pub fn set_border_pos(hwnd: isize, layout: &Rect, position: isize) -> eyre::Result<()> {\n        let mut flags = SetWindowPosition::NO_SEND_CHANGING\n            | SetWindowPosition::NO_ACTIVATE\n            | SetWindowPosition::NO_REDRAW\n            | SetWindowPosition::SHOW_WINDOW;\n\n        if matches!(\n            WINDOW_HANDLING_BEHAVIOUR.load(),\n            WindowHandlingBehaviour::Async\n        ) {\n            flags |= SetWindowPosition::ASYNC_WINDOW_POS;\n        }\n\n        Self::set_window_pos(\n            HWND(as_ptr!(hwnd)),\n            layout,\n            HWND(as_ptr!(position)),\n            flags.bits(),\n        )\n    }\n\n    /// set_window_pos calls SetWindowPos without any accounting for Window decorations.\n    fn set_window_pos(hwnd: HWND, layout: &Rect, position: HWND, flags: u32) -> eyre::Result<()> {\n        unsafe {\n            SetWindowPos(\n                hwnd,\n                Option::from(position),\n                layout.left,\n                layout.top,\n                layout.right,\n                layout.bottom,\n                SET_WINDOW_POS_FLAGS(flags),\n            )\n        }\n        .process()\n    }\n\n    /// move_windows calls MoveWindow, but cannot be called with async window pos, so it might hang\n    pub fn move_window(hwnd: isize, layout: &Rect, repaint: bool) -> eyre::Result<()> {\n        let hwnd = HWND(as_ptr!(hwnd));\n\n        let shadow_rect = Self::shadow_rect(hwnd).unwrap_or_default();\n        let rect = Rect {\n            left: layout.left + shadow_rect.left,\n            top: layout.top + shadow_rect.top,\n            right: layout.right + shadow_rect.right,\n            bottom: layout.bottom + shadow_rect.bottom,\n        };\n        unsafe { MoveWindow(hwnd, rect.left, rect.top, rect.right, rect.bottom, repaint) }.process()\n    }\n\n    pub fn show_window(hwnd: isize, command: SHOW_WINDOW_CMD) {\n        // BOOL is returned but does not signify whether or not the operation was succesful\n        // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-showwindow\n        // TODO: error handling\n        if matches!(\n            WINDOW_HANDLING_BEHAVIOUR.load(),\n            WindowHandlingBehaviour::Async\n        ) {\n            unsafe {\n                let _ = ShowWindowAsync(HWND(as_ptr!(hwnd)), command);\n            };\n        } else {\n            unsafe {\n                let _ = ShowWindow(HWND(as_ptr!(hwnd)), command);\n            };\n        }\n    }\n\n    pub fn minimize_window(hwnd: isize) {\n        Self::show_window(hwnd, SW_MINIMIZE);\n    }\n\n    fn post_message(hwnd: HWND, message: u32, wparam: WPARAM, lparam: LPARAM) -> eyre::Result<()> {\n        unsafe { PostMessageW(Option::from(hwnd), message, wparam, lparam) }.process()\n    }\n\n    pub fn close_window(hwnd: isize) -> eyre::Result<()> {\n        if Self::post_message(HWND(as_ptr!(hwnd)), WM_CLOSE, WPARAM(0), LPARAM(0)).is_err() {\n            bail!(\"could not close window\");\n        }\n\n        Ok(())\n    }\n\n    pub fn hide_window(hwnd: isize) {\n        Self::show_window(hwnd, SW_HIDE);\n    }\n\n    pub fn restore_window(hwnd: isize) {\n        Self::show_window(hwnd, SW_SHOWNOACTIVATE);\n    }\n\n    pub fn unmaximize_window(hwnd: isize) {\n        Self::show_window(hwnd, SW_NORMAL);\n    }\n\n    pub fn maximize_window(hwnd: isize) {\n        Self::show_window(hwnd, SW_MAXIMIZE);\n    }\n\n    pub fn foreground_window() -> eyre::Result<isize> {\n        unsafe { GetForegroundWindow() }.process()\n    }\n\n    pub fn raise_and_focus_window(hwnd: isize) -> eyre::Result<()> {\n        let event = [INPUT {\n            r#type: INPUT_MOUSE,\n            ..Default::default()\n        }];\n\n        unsafe {\n            // Send an input event to our own process first so that we pass the\n            // foreground lock check\n            SendInput(&event, size_of::<INPUT>() as i32);\n            // Error ignored, as the operation is not always necessary.\n            let _ = SetWindowPos(\n                HWND(as_ptr!(hwnd)),\n                Option::from(HWND_TOP),\n                0,\n                0,\n                0,\n                0,\n                SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW | SWP_ASYNCWINDOWPOS,\n            )\n            .process();\n            SetForegroundWindow(HWND(as_ptr!(hwnd)))\n        }\n        .ok()\n        .process()\n    }\n\n    #[allow(dead_code)]\n    pub fn top_window() -> eyre::Result<isize> {\n        unsafe { GetTopWindow(None)? }.process()\n    }\n\n    pub fn desktop_window() -> eyre::Result<isize> {\n        unsafe { GetDesktopWindow() }.process()\n    }\n\n    #[allow(dead_code)]\n    pub fn next_window(hwnd: isize) -> eyre::Result<isize> {\n        unsafe { GetWindow(HWND(as_ptr!(hwnd)), GW_HWNDNEXT)? }.process()\n    }\n\n    pub fn alt_tab_windows() -> eyre::Result<Vec<Window>> {\n        let mut hwnds = vec![];\n        Self::enum_windows(\n            Some(windows_callbacks::alt_tab_windows),\n            &mut hwnds as *mut Vec<Window> as isize,\n        )?;\n\n        Ok(hwnds)\n    }\n\n    #[allow(dead_code)]\n    pub fn top_visible_window() -> eyre::Result<isize> {\n        let hwnd = Self::top_window()?;\n        let mut next_hwnd = hwnd;\n\n        while next_hwnd != 0 {\n            if Self::is_window_visible(next_hwnd) {\n                return Ok(next_hwnd);\n            }\n\n            next_hwnd = Self::next_window(next_hwnd)?;\n        }\n\n        bail!(\"could not find next window\")\n    }\n\n    pub fn window_rect(hwnd: isize) -> eyre::Result<Rect> {\n        let mut rect = unsafe { std::mem::zeroed() };\n\n        if Self::dwm_get_window_attribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &mut rect).is_ok() {\n            // TODO(raggi): once we declare DPI awareness, we will need to scale the rect.\n            // let window_scale = unsafe { GetDpiForWindow(hwnd) };\n            // let system_scale = unsafe { GetDpiForSystem() };\n            // Ok(Rect::from(rect).scale(system_scale.try_into()?, window_scale.try_into()?))\n            Ok(Rect::from(rect))\n        } else {\n            unsafe { GetWindowRect(HWND(as_ptr!(hwnd)), &mut rect) }.process()?;\n            Ok(Rect::from(rect))\n        }\n    }\n\n    /// shadow_rect computes the offset of the shadow position of the window to\n    /// the window painted region. The four values in the returned Rect can be\n    /// added to a position rect to compute a size for set_window_pos that will\n    /// fill the target area, ignoring shadows.\n    fn shadow_rect(hwnd: HWND) -> eyre::Result<Rect> {\n        let window_rect = Self::window_rect(hwnd.0 as isize)?;\n\n        let mut srect = Default::default();\n        unsafe { GetWindowRect(hwnd, &mut srect) }.process()?;\n        let shadow_rect = Rect::from(srect);\n\n        Ok(Rect {\n            left: shadow_rect.left - window_rect.left,\n            top: shadow_rect.top - window_rect.top,\n            right: shadow_rect.right - window_rect.right,\n            bottom: shadow_rect.bottom - window_rect.bottom,\n        })\n    }\n\n    pub fn round_rect(hdc: HDC, rect: &Rect, border_radius: i32) {\n        unsafe {\n            // TODO: error handling\n            let _ = RoundRect(\n                hdc,\n                rect.left,\n                rect.top,\n                rect.right,\n                rect.bottom,\n                border_radius,\n                border_radius,\n            );\n        }\n    }\n    pub fn rectangle(hdc: HDC, rect: &Rect) {\n        unsafe {\n            // TODO: error handling\n            let _ = Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom);\n        }\n    }\n    fn set_cursor_pos(x: i32, y: i32) -> eyre::Result<()> {\n        unsafe { SetCursorPos(x, y) }.process()\n    }\n\n    pub fn cursor_pos() -> eyre::Result<POINT> {\n        let mut cursor_pos = POINT::default();\n        unsafe { GetCursorPos(&mut cursor_pos) }.process()?;\n\n        Ok(cursor_pos)\n    }\n\n    pub fn window_from_point(point: POINT) -> eyre::Result<isize> {\n        unsafe { WindowFromPoint(point) }.process()\n    }\n\n    pub fn window_at_cursor_pos() -> eyre::Result<isize> {\n        Self::window_from_point(Self::cursor_pos()?)\n    }\n\n    pub fn center_cursor_in_rect(rect: &Rect) -> eyre::Result<()> {\n        Self::set_cursor_pos(rect.left + (rect.right / 2), rect.top + (rect.bottom / 2))\n    }\n\n    pub fn window_thread_process_id(hwnd: isize) -> (u32, u32) {\n        let mut process_id: u32 = 0;\n\n        // Behaviour is undefined if an invalid HWND is given\n        // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowthreadprocessid\n        let thread_id = unsafe {\n            GetWindowThreadProcessId(\n                HWND(as_ptr!(hwnd)),\n                Option::from(std::ptr::addr_of_mut!(process_id)),\n            )\n        };\n\n        (process_id, thread_id)\n    }\n\n    pub fn current_process_id() -> u32 {\n        unsafe { GetCurrentProcessId() }\n    }\n\n    pub fn process_id_to_session_id() -> eyre::Result<u32> {\n        let process_id = Self::current_process_id();\n        let mut session_id = 0;\n\n        unsafe {\n            if ProcessIdToSessionId(process_id, &mut session_id).is_err() {\n                bail!(\"could not determine current session id\")\n            }\n        }\n\n        Ok(session_id)\n    }\n\n    #[cfg(target_pointer_width = \"64\")]\n    fn set_window_long_ptr_w(\n        hwnd: HWND,\n        index: WINDOW_LONG_PTR_INDEX,\n        new_value: isize,\n    ) -> eyre::Result<()> {\n        Result::from(WindowsResult::from(unsafe {\n            SetWindowLongPtrW(hwnd, index, new_value)\n        }))\n        .map(|_| {})\n    }\n\n    #[cfg(target_pointer_width = \"32\")]\n    fn set_window_long_ptr_w(\n        hwnd: HWND,\n        index: WINDOW_LONG_PTR_INDEX,\n        new_value: i32,\n    ) -> eyre::Result<()> {\n        Result::from(WindowsResult::from(unsafe {\n            SetWindowLongPtrW(hwnd, index, new_value)\n        }))\n        .map(|_| {})\n    }\n\n    #[cfg(target_pointer_width = \"64\")]\n    pub fn gwl_style(hwnd: isize) -> eyre::Result<isize> {\n        Self::window_long_ptr_w(HWND(as_ptr!(hwnd)), GWL_STYLE)\n    }\n\n    #[cfg(target_pointer_width = \"32\")]\n    pub fn gwl_style(hwnd: isize) -> eyre::Result<i32> {\n        Self::window_long_ptr_w(HWND(as_ptr!(hwnd)), GWL_STYLE)\n    }\n\n    #[cfg(target_pointer_width = \"64\")]\n    pub fn gwl_ex_style(hwnd: isize) -> eyre::Result<isize> {\n        Self::window_long_ptr_w(HWND(as_ptr!(hwnd)), GWL_EXSTYLE)\n    }\n\n    #[cfg(target_pointer_width = \"32\")]\n    pub fn gwl_ex_style(hwnd: isize) -> eyre::Result<i32> {\n        Self::window_long_ptr_w(HWND(as_ptr!(hwnd)), GWL_EXSTYLE)\n    }\n\n    #[cfg(target_pointer_width = \"64\")]\n    fn window_long_ptr_w(hwnd: HWND, index: WINDOW_LONG_PTR_INDEX) -> eyre::Result<isize> {\n        // Can return 0, which does not always mean that an error has occurred\n        // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowlongptrw\n        unsafe {\n            SetLastError(WIN32_ERROR(0));\n            let result = GetWindowLongPtrW(hwnd, index);\n\n            if result != 0 {\n                Ok(result)\n            } else {\n                let last_error = GetLastError();\n                if last_error == WIN32_ERROR(0) {\n                    Ok(0)\n                } else {\n                    Err(std::io::Error::from_raw_os_error(last_error.0 as i32).into())\n                }\n            }\n        }\n    }\n\n    #[cfg(target_pointer_width = \"32\")]\n    fn window_long_ptr_w(hwnd: HWND, index: WINDOW_LONG_PTR_INDEX) -> eyre::Result<i32> {\n        // Can return 0, which does not always mean that an error has occurred\n        // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowlongptrw\n        unsafe {\n            SetLastError(WIN32_ERROR(0));\n            let result = GetWindowLongPtrW(hwnd, index);\n\n            if result != 0 {\n                Ok(result)\n            } else {\n                let last_error = GetLastError();\n                if last_error == WIN32_ERROR(0) {\n                    Ok(0)\n                } else {\n                    Err(std::io::Error::from_raw_os_error(last_error.0 as i32).into())\n                }\n            }\n        }\n    }\n\n    #[cfg(target_pointer_width = \"64\")]\n    pub fn update_style(hwnd: isize, new_value: isize) -> eyre::Result<()> {\n        Self::set_window_long_ptr_w(HWND(as_ptr!(hwnd)), GWL_STYLE, new_value)\n    }\n\n    #[cfg(target_pointer_width = \"32\")]\n    pub fn update_style(hwnd: isize, new_value: i32) -> eyre::Result<()> {\n        Self::set_window_long_ptr_w(HWND(as_ptr!(hwnd)), GWL_STYLE, new_value)\n    }\n\n    #[cfg(target_pointer_width = \"64\")]\n    pub fn update_ex_style(hwnd: isize, new_value: isize) -> eyre::Result<()> {\n        Self::set_window_long_ptr_w(HWND(as_ptr!(hwnd)), GWL_EXSTYLE, new_value)\n    }\n\n    #[cfg(target_pointer_width = \"32\")]\n    pub fn update_ex_style(hwnd: isize, new_value: i32) -> eyre::Result<()> {\n        Self::set_window_long_ptr_w(HWND(as_ptr!(hwnd)), GWL_EXSTYLE, new_value)\n    }\n\n    pub fn window_text_w(hwnd: isize) -> eyre::Result<String> {\n        let mut text: [u16; 512] = [0; 512];\n        match WindowsResult::from(unsafe { GetWindowTextW(HWND(as_ptr!(hwnd)), &mut text) }) {\n            WindowsResult::Ok(len) => {\n                let length = usize::try_from(len)?;\n                Ok(String::from_utf16(&text[..length])?)\n            }\n            WindowsResult::Err(error) => Err(error),\n        }\n    }\n\n    fn open_process(\n        access_rights: PROCESS_ACCESS_RIGHTS,\n        inherit_handle: bool,\n        process_id: u32,\n    ) -> eyre::Result<HANDLE> {\n        unsafe { OpenProcess(access_rights, inherit_handle, process_id) }.process()\n    }\n\n    pub fn close_process(handle: HANDLE) -> eyre::Result<()> {\n        unsafe { CloseHandle(handle) }.process()\n    }\n\n    pub fn process_handle(process_id: u32) -> eyre::Result<HANDLE> {\n        Self::open_process(PROCESS_QUERY_INFORMATION, false, process_id)\n    }\n\n    pub fn exe_path(handle: HANDLE) -> eyre::Result<String> {\n        let mut len = 260_u32;\n        let mut path: Vec<u16> = vec![0; len as usize];\n        let text_ptr = path.as_mut_ptr();\n\n        unsafe {\n            QueryFullProcessImageNameW(handle, PROCESS_NAME_WIN32, PWSTR(text_ptr), &mut len)\n        }\n        .process()?;\n\n        Ok(String::from_utf16(&path[..len as usize])?)\n    }\n\n    pub fn exe(handle: HANDLE) -> eyre::Result<String> {\n        Ok(Self::exe_path(handle)?\n            .split('\\\\')\n            .next_back()\n            .ok_or_eyre(\"there is no last element\")?\n            .to_string())\n    }\n\n    pub fn real_window_class_w(hwnd: isize) -> eyre::Result<String> {\n        const BUF_SIZE: usize = 512;\n        let mut class: [u16; BUF_SIZE] = [0; BUF_SIZE];\n\n        let len = Result::from(WindowsResult::from(unsafe {\n            RealGetWindowClassW(HWND(as_ptr!(hwnd)), &mut class)\n        }))?;\n\n        Ok(String::from_utf16(&class[0..len as usize])?)\n    }\n\n    pub fn dwm_get_window_attribute<T>(\n        hwnd: isize,\n        attribute: DWMWINDOWATTRIBUTE,\n        value: &mut T,\n    ) -> eyre::Result<()> {\n        unsafe {\n            DwmGetWindowAttribute(\n                HWND(as_ptr!(hwnd)),\n                attribute,\n                (value as *mut T).cast(),\n                u32::try_from(std::mem::size_of::<T>())?,\n            )?;\n        }\n\n        Ok(())\n    }\n\n    pub fn is_window_cloaked(hwnd: isize) -> eyre::Result<bool> {\n        let mut cloaked: u32 = 0;\n        Self::dwm_get_window_attribute(hwnd, DWMWA_CLOAKED, &mut cloaked)?;\n\n        Ok(matches!(\n            cloaked,\n            DWM_CLOAKED_APP | DWM_CLOAKED_SHELL | DWM_CLOAKED_INHERITED\n        ))\n    }\n\n    pub fn is_window(hwnd: isize) -> bool {\n        unsafe { IsWindow(Option::from(HWND(as_ptr!(hwnd)))) }.into()\n    }\n\n    pub fn is_window_visible(hwnd: isize) -> bool {\n        unsafe { IsWindowVisible(HWND(as_ptr!(hwnd))) }.into()\n    }\n\n    pub fn is_iconic(hwnd: isize) -> bool {\n        unsafe { IsIconic(HWND(as_ptr!(hwnd))) }.into()\n    }\n\n    pub fn is_zoomed(hwnd: isize) -> bool {\n        unsafe { IsZoomed(HWND(as_ptr!(hwnd))) }.into()\n    }\n\n    pub fn monitor_info_w(hmonitor: HMONITOR) -> eyre::Result<MONITORINFOEXW> {\n        let mut ex_info = MONITORINFOEXW::default();\n        ex_info.monitorInfo.cbSize = u32::try_from(std::mem::size_of::<MONITORINFOEXW>())?;\n        unsafe { GetMonitorInfoW(hmonitor, &mut ex_info.monitorInfo) }\n            .ok()\n            .process()?;\n\n        Ok(ex_info)\n    }\n\n    pub fn monitor_device_path(hmonitor: isize) -> Option<String> {\n        for display in win32_display_data::connected_displays_all().flatten() {\n            if display.hmonitor == hmonitor {\n                return Some(display.device_path.clone());\n            }\n        }\n\n        None\n    }\n\n    pub fn monitor(hmonitor: isize) -> eyre::Result<Monitor> {\n        for mut display in win32_display_data::connected_displays_all().flatten() {\n            if display.hmonitor == hmonitor {\n                let path = display.device_path;\n\n                let (device, device_id) = if path.is_empty() {\n                    (String::from(\"UNKNOWN\"), String::from(\"UNKNOWN\"))\n                } else {\n                    let mut split: Vec<_> = path.split('#').collect();\n                    split.remove(0);\n                    split.remove(split.len() - 1);\n                    let device = split[0].to_string();\n                    let device_id = split.join(\"-\");\n                    (device, device_id)\n                };\n\n                let name = display.device_name.trim_start_matches(r\"\\\\.\\\").to_string();\n                let name = name.split('\\\\').collect::<Vec<_>>()[0].to_string();\n\n                if let Some(id) = &display.serial_number_id {\n                    let dupes = DUPLICATE_MONITOR_SERIAL_IDS.read();\n                    if dupes.contains(id) {\n                        display.serial_number_id = None;\n                    }\n                }\n\n                let monitor = monitor::new(\n                    hmonitor,\n                    display.size.into(),\n                    display.work_area_size.into(),\n                    name,\n                    device,\n                    device_id,\n                    display.serial_number_id,\n                );\n\n                return Ok(monitor);\n            }\n        }\n\n        bail!(\"could not find device_id for hmonitor: {hmonitor}\");\n    }\n\n    pub fn set_process_dpi_awareness_context() -> eyre::Result<()> {\n        unsafe { SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) }\n            .process()\n    }\n\n    #[allow(dead_code)]\n    pub fn system_parameters_info_w(\n        action: SYSTEM_PARAMETERS_INFO_ACTION,\n        ui_param: u32,\n        pv_param: *mut c_void,\n        update_flags: SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS,\n    ) -> eyre::Result<()> {\n        unsafe { SystemParametersInfoW(action, ui_param, Option::from(pv_param), update_flags) }\n            .process()\n    }\n\n    #[tracing::instrument]\n    pub fn foreground_lock_timeout() -> eyre::Result<()> {\n        let mut value: u32 = 0;\n\n        Self::system_parameters_info_w(\n            SPI_GETFOREGROUNDLOCKTIMEOUT,\n            0,\n            std::ptr::addr_of_mut!(value).cast(),\n            SPIF_SENDCHANGE,\n        )?;\n\n        tracing::info!(\"current value of ForegroundLockTimeout is {value}\");\n\n        if value != 0 {\n            tracing::info!(\n                \"updating value of ForegroundLockTimeout to {value} in order to enable keyboard-driven focus updating\"\n            );\n\n            Self::system_parameters_info_w(\n                SPI_SETFOREGROUNDLOCKTIMEOUT,\n                0,\n                std::ptr::null_mut::<c_void>(),\n                SPIF_SENDCHANGE,\n            )?;\n\n            Self::system_parameters_info_w(\n                SPI_GETFOREGROUNDLOCKTIMEOUT,\n                0,\n                std::ptr::addr_of_mut!(value).cast(),\n                SPIF_SENDCHANGE,\n            )?;\n\n            tracing::info!(\"updated value of ForegroundLockTimeout is now {value}\");\n        }\n\n        Ok(())\n    }\n\n    #[allow(dead_code)]\n    pub fn focus_follows_mouse() -> eyre::Result<bool> {\n        let mut is_enabled: BOOL = unsafe { std::mem::zeroed() };\n\n        Self::system_parameters_info_w(\n            SPI_GETACTIVEWINDOWTRACKING,\n            0,\n            std::ptr::addr_of_mut!(is_enabled).cast(),\n            SPIF_SENDCHANGE,\n        )?;\n\n        Ok(is_enabled.into())\n    }\n\n    #[allow(dead_code)]\n    pub fn enable_focus_follows_mouse() -> eyre::Result<()> {\n        #[allow(clippy::manual_dangling_ptr)]\n        Self::system_parameters_info_w(\n            SPI_SETACTIVEWINDOWTRACKING,\n            0,\n            1 as *mut c_void,\n            SPIF_SENDCHANGE,\n        )\n    }\n\n    #[allow(dead_code)]\n    pub fn disable_focus_follows_mouse() -> eyre::Result<()> {\n        Self::system_parameters_info_w(\n            SPI_SETACTIVEWINDOWTRACKING,\n            0,\n            std::ptr::null_mut::<c_void>(),\n            SPIF_SENDCHANGE,\n        )\n    }\n\n    pub fn module_handle_w() -> eyre::Result<HMODULE> {\n        unsafe { GetModuleHandleW(None) }.process()\n    }\n\n    pub fn create_solid_brush(colour: u32) -> HBRUSH {\n        unsafe { CreateSolidBrush(COLORREF(colour)) }\n    }\n\n    pub fn register_class_w(window_class: &WNDCLASSW) -> eyre::Result<u16> {\n        Result::from(WindowsResult::from(unsafe { RegisterClassW(window_class) }))\n    }\n\n    pub fn dpi_for_monitor(hmonitor: isize) -> eyre::Result<f32> {\n        let mut dpi_x = u32::default();\n        let mut dpi_y = u32::default();\n\n        unsafe {\n            GetDpiForMonitor(\n                HMONITOR(as_ptr!(hmonitor)),\n                MDT_EFFECTIVE_DPI,\n                std::ptr::addr_of_mut!(dpi_x),\n                std::ptr::addr_of_mut!(dpi_y),\n            )\n        }\n        .process()?;\n\n        #[allow(clippy::cast_precision_loss)]\n        Ok(dpi_y as f32 / 96.0)\n    }\n\n    pub fn monitors_have_same_dpi(hmonitor_a: isize, hmonitor_b: isize) -> eyre::Result<bool> {\n        let dpi_a = Self::dpi_for_monitor(hmonitor_a)?;\n        let dpi_b = Self::dpi_for_monitor(hmonitor_b)?;\n\n        Ok((dpi_a - dpi_b).abs() < f32::EPSILON)\n    }\n\n    pub fn round_corners(hwnd: isize) -> eyre::Result<()> {\n        let round = DWMWCP_ROUND;\n\n        unsafe {\n            DwmSetWindowAttribute(\n                HWND(as_ptr!(hwnd)),\n                DWMWA_WINDOW_CORNER_PREFERENCE,\n                std::ptr::addr_of!(round).cast(),\n                4,\n            )\n        }\n        .process()\n    }\n\n    pub fn set_window_accent(hwnd: isize, color: Option<u32>) -> eyre::Result<()> {\n        let col_ref = COLORREF(color.unwrap_or(DWMWA_COLOR_NONE));\n        unsafe {\n            DwmSetWindowAttribute(\n                HWND(as_ptr!(hwnd)),\n                DWMWA_BORDER_COLOR,\n                std::ptr::addr_of!(col_ref).cast(),\n                4,\n            )\n        }\n        .process()\n    }\n\n    pub fn create_border_window(\n        name: PCWSTR,\n        instance: isize,\n        border: *mut Border,\n    ) -> eyre::Result<isize> {\n        unsafe {\n            CreateWindowExW(\n                WS_EX_TOOLWINDOW | WS_EX_TOPMOST | WS_EX_NOACTIVATE,\n                name,\n                name,\n                WS_POPUP | WS_SYSMENU,\n                CW_USEDEFAULT,\n                CW_USEDEFAULT,\n                CW_USEDEFAULT,\n                CW_USEDEFAULT,\n                None,\n                None,\n                Option::from(HINSTANCE(as_ptr!(instance))),\n                Some(border as _),\n            )?\n        }\n        .process()\n    }\n\n    pub fn set_transparent(hwnd: isize, alpha: u8) -> eyre::Result<()> {\n        unsafe {\n            #[allow(clippy::cast_sign_loss)]\n            SetLayeredWindowAttributes(\n                HWND(as_ptr!(hwnd)),\n                COLORREF(-1i32 as u32),\n                alpha,\n                LWA_ALPHA,\n            )?;\n        }\n\n        Ok(())\n    }\n\n    pub fn get_transparent(hwnd: isize) -> eyre::Result<u8> {\n        unsafe {\n            let mut alpha: u8 = u8::default();\n            let mut color_ref = COLORREF(-1i32 as u32);\n            let mut flags = LWA_ALPHA;\n            GetLayeredWindowAttributes(\n                HWND(as_ptr!(hwnd)),\n                Some(std::ptr::addr_of_mut!(color_ref)),\n                Some(std::ptr::addr_of_mut!(alpha)),\n                Some(std::ptr::addr_of_mut!(flags)),\n            )?;\n            Ok(alpha)\n        }\n    }\n\n    pub fn create_hidden_window(name: PCWSTR, instance: isize) -> eyre::Result<isize> {\n        unsafe {\n            CreateWindowExW(\n                WS_EX_NOACTIVATE,\n                name,\n                name,\n                WS_DISABLED,\n                CW_USEDEFAULT,\n                CW_USEDEFAULT,\n                CW_USEDEFAULT,\n                CW_USEDEFAULT,\n                None,\n                None,\n                Option::from(HINSTANCE(as_ptr!(instance))),\n                None,\n            )?\n        }\n        .process()\n    }\n\n    pub fn register_power_setting_notification(\n        hwnd: isize,\n        guid: &windows_core::GUID,\n        flags: REGISTER_NOTIFICATION_FLAGS,\n    ) -> WindowsCrateResult<HPOWERNOTIFY> {\n        unsafe { RegisterPowerSettingNotification(HANDLE::from(HWND(as_ptr!(hwnd))), guid, flags) }\n    }\n\n    pub fn register_device_notification(\n        hwnd: isize,\n        mut filter: DEV_BROADCAST_DEVICEINTERFACE_W,\n        flags: REGISTER_NOTIFICATION_FLAGS,\n    ) -> WindowsCrateResult<HDEVNOTIFY> {\n        unsafe {\n            let state_ptr: *const c_void = &mut filter as *mut _ as *const c_void;\n            RegisterDeviceNotificationW(HANDLE::from(HWND(as_ptr!(hwnd))), state_ptr, flags)\n        }\n    }\n\n    pub fn invalidate_rect(hwnd: isize, rect: Option<&Rect>, erase: bool) -> bool {\n        let rect = rect.map(|rect| &rect.rect() as *const RECT);\n        unsafe { InvalidateRect(Option::from(HWND(as_ptr!(hwnd))), rect, erase) }.as_bool()\n    }\n\n    pub fn alt_is_pressed() -> bool {\n        let state = unsafe { GetKeyState(i32::from(VK_MENU.0)) };\n        #[allow(clippy::cast_sign_loss)]\n        let actual = (state as u16) & 0x8000;\n        actual != 0\n    }\n\n    pub fn lbutton_is_pressed() -> bool {\n        let state = unsafe { GetKeyState(i32::from(VK_LBUTTON.0)) };\n        #[allow(clippy::cast_sign_loss)]\n        let actual = (state as u16) & 0x8000;\n        actual != 0\n    }\n\n    pub fn left_click() -> u32 {\n        let inputs = [\n            INPUT {\n                r#type: INPUT_MOUSE,\n                Anonymous: INPUT_0 {\n                    mi: MOUSEINPUT {\n                        dx: 0,\n                        dy: 0,\n                        mouseData: 0,\n                        dwFlags: MOUSEEVENTF_LEFTDOWN,\n                        time: 0,\n                        dwExtraInfo: 0,\n                    },\n                },\n            },\n            INPUT {\n                r#type: INPUT_MOUSE,\n                Anonymous: INPUT_0 {\n                    mi: MOUSEINPUT {\n                        dx: 0,\n                        dy: 0,\n                        mouseData: 0,\n                        dwFlags: MOUSEEVENTF_LEFTUP,\n                        time: 0,\n                        dwExtraInfo: 0,\n                    },\n                },\n            },\n        ];\n\n        #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]\n        unsafe {\n            SendInput(&inputs, std::mem::size_of::<INPUT>() as i32)\n        }\n    }\n\n    pub fn wts_register_session_notification(hwnd: isize) -> eyre::Result<()> {\n        unsafe { WTSRegisterSessionNotification(HWND(as_ptr!(hwnd)), 1) }.process()\n    }\n\n    pub fn set_wallpaper(path: &Path, hmonitor: isize) -> eyre::Result<()> {\n        let path = path.canonicalize()?;\n\n        let wallpaper: IDesktopWallpaper =\n            unsafe { CoCreateInstance(&DesktopWallpaper, None, CLSCTX_ALL)? };\n\n        let wallpaper_path = HSTRING::from(path.to_str().unwrap_or_default());\n        unsafe {\n            wallpaper.SetPosition(DWPOS_FILL)?;\n        }\n\n        let monitor_id = if let Some(path) = Self::monitor_device_path(hmonitor) {\n            PCWSTR::from_raw(HSTRING::from(path).as_ptr())\n        } else {\n            PCWSTR::null()\n        };\n\n        // Set the wallpaper\n        unsafe {\n            wallpaper.SetWallpaper(monitor_id, PCWSTR::from_raw(wallpaper_path.as_ptr()))?;\n        }\n        Ok(())\n    }\n\n    pub fn get_wallpaper(hmonitor: isize) -> eyre::Result<String> {\n        let wallpaper: IDesktopWallpaper =\n            unsafe { CoCreateInstance(&DesktopWallpaper, None, CLSCTX_ALL)? };\n\n        let monitor_id = if let Some(path) = Self::monitor_device_path(hmonitor) {\n            PCWSTR::from_raw(HSTRING::from(path).as_ptr())\n        } else {\n            PCWSTR::null()\n        };\n\n        // Set the wallpaper\n        unsafe {\n            wallpaper\n                .GetWallpaper(monitor_id)\n                .and_then(|pwstr| pwstr.to_string().map_err(|e| e.into()))\n        }\n        .process()\n    }\n}\n"
  },
  {
    "path": "komorebi/src/windows_callbacks.rs",
    "content": "use std::collections::VecDeque;\n\nuse crate::border_manager;\nuse crate::container::Container;\nuse crate::window::RuleDebug;\nuse crate::window::Window;\nuse crate::window_manager_event::WindowManagerEvent;\nuse crate::windows_api::WindowsApi;\nuse crate::winevent::WinEvent;\nuse crate::winevent_listener;\nuse windows::Win32::Foundation::HWND;\nuse windows::Win32::Foundation::LPARAM;\nuse windows::Win32::Foundation::WPARAM;\nuse windows::Win32::UI::Accessibility::HWINEVENTHOOK;\nuse windows::Win32::UI::WindowsAndMessaging::GWL_EXSTYLE;\nuse windows::Win32::UI::WindowsAndMessaging::GWL_STYLE;\nuse windows::Win32::UI::WindowsAndMessaging::GetWindowLongW;\nuse windows::Win32::UI::WindowsAndMessaging::OBJID_WINDOW;\nuse windows::Win32::UI::WindowsAndMessaging::SendNotifyMessageW;\nuse windows::Win32::UI::WindowsAndMessaging::WS_CHILD;\nuse windows::Win32::UI::WindowsAndMessaging::WS_EX_NOACTIVATE;\nuse windows::Win32::UI::WindowsAndMessaging::WS_EX_TOOLWINDOW;\nuse windows_core::BOOL;\n\npub extern \"system\" fn enum_window(hwnd: HWND, lparam: LPARAM) -> BOOL {\n    let containers = unsafe { &mut *(lparam.0 as *mut VecDeque<Container>) };\n\n    let is_visible = WindowsApi::is_window_visible(hwnd.0 as isize);\n    let is_window = WindowsApi::is_window(hwnd.0 as isize);\n    let is_minimized = WindowsApi::is_iconic(hwnd.0 as isize);\n    let is_maximized = WindowsApi::is_zoomed(hwnd.0 as isize);\n\n    if is_visible && is_window && !is_minimized {\n        let window = Window::from(hwnd);\n\n        if let Ok(should_manage) = window.should_manage(None, &mut RuleDebug::default())\n            && should_manage\n        {\n            if is_maximized {\n                WindowsApi::restore_window(window.hwnd);\n            }\n\n            let mut container = Container::default();\n            container.windows_mut().push_back(window);\n            containers.push_back(container);\n        }\n    }\n\n    true.into()\n}\n\npub extern \"system\" fn alt_tab_windows(hwnd: HWND, lparam: LPARAM) -> BOOL {\n    let windows = unsafe { &mut *(lparam.0 as *mut Vec<Window>) };\n\n    let is_visible = WindowsApi::is_window_visible(hwnd.0 as isize);\n    let is_window = WindowsApi::is_window(hwnd.0 as isize);\n    let is_minimized = WindowsApi::is_iconic(hwnd.0 as isize);\n\n    if is_visible && is_window && !is_minimized {\n        let window = Window::from(hwnd);\n\n        if let Ok(should_manage) = window.should_manage(None, &mut RuleDebug::default())\n            && should_manage\n        {\n            windows.push(window);\n        }\n    }\n\n    true.into()\n}\n\nfn has_filtered_style(hwnd: HWND) -> bool {\n    let style = unsafe { GetWindowLongW(hwnd, GWL_STYLE) as u32 };\n    let ex_style = unsafe { GetWindowLongW(hwnd, GWL_EXSTYLE) as u32 };\n\n    style & WS_CHILD.0 != 0\n        || ex_style & WS_EX_TOOLWINDOW.0 != 0\n        || ex_style & WS_EX_NOACTIVATE.0 != 0\n}\n\npub extern \"system\" fn win_event_hook(\n    _h_win_event_hook: HWINEVENTHOOK,\n    event: u32,\n    hwnd: HWND,\n    id_object: i32,\n    _id_child: i32,\n    _id_event_thread: u32,\n    _dwms_event_time: u32,\n) {\n    if id_object != OBJID_WINDOW.0 {\n        return;\n    }\n\n    let window = Window::from(hwnd);\n\n    let winevent = match WinEvent::try_from(event) {\n        Ok(event) => event,\n        Err(_) => return,\n    };\n\n    // this forwards the message to the window's border when it moves or is destroyed\n    // see border_manager/border.rs\n    if matches!(\n        winevent,\n        WinEvent::ObjectLocationChange | WinEvent::ObjectDestroy\n    ) && !has_filtered_style(hwnd)\n    {\n        let border_info = border_manager::window_border(hwnd.0 as isize);\n\n        if let Some(border_info) = border_info {\n            unsafe {\n                let _ = SendNotifyMessageW(\n                    border_info.hwnd(),\n                    event,\n                    WPARAM(0),\n                    LPARAM(hwnd.0 as isize),\n                );\n            }\n        }\n    }\n\n    // sometimes the border focus state and colors don't get updated because this event comes too\n    // slow for the value of GetForegroundWindow to be up to date by the time it is inspected in\n    // the border manager to determine if a window show have its border show as \"focused\"\n    //\n    // so here we can just fire another event at the border manager when the system has finally\n    // registered the new foreground window and this time the correct border colors will be applied\n    if matches!(winevent, WinEvent::SystemForeground) && !has_filtered_style(hwnd) {\n        border_manager::send_notification(Some(hwnd.0 as isize));\n    }\n\n    let event_type = match WindowManagerEvent::from_win_event(winevent, window) {\n        None => {\n            tracing::trace!(\n                \"Unhandled WinEvent: {winevent} (hwnd: {}, exe: {}, title: {}, class: {})\",\n                window.hwnd,\n                window.exe().unwrap_or_default(),\n                window.title().unwrap_or_default(),\n                window.class().unwrap_or_default()\n            );\n\n            return;\n        }\n        Some(event) => event,\n    };\n\n    winevent_listener::event_tx()\n        .send(event_type)\n        .expect(\"could not send message on winevent_listener::event_tx\");\n}\n"
  },
  {
    "path": "komorebi/src/winevent.rs",
    "content": "#![allow(clippy::use_self)]\n\nuse serde::Deserialize;\nuse serde::Serialize;\nuse strum::Display;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_AIA_END;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_AIA_START;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_CARET;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_END;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_END_APPLICATION;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_LAYOUT;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_START_APPLICATION;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_UPDATE_REGION;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_UPDATE_SCROLL;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_CONSOLE_UPDATE_SIMPLE;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_ACCELERATORCHANGE;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_CLOAKED;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_CONTENTSCROLLED;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_CREATE;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DEFACTIONCHANGE;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DESCRIPTIONCHANGE;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DESTROY;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DRAGCANCEL;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DRAGCOMPLETE;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DRAGDROPPED;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DRAGENTER;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DRAGLEAVE;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DRAGSTART;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_END;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_FOCUS;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_HELPCHANGE;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_HIDE;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_HOSTEDOBJECTSINVALIDATED;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_IME_CHANGE;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_IME_HIDE;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_IME_SHOW;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_INVOKED;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_LIVEREGIONCHANGED;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_LOCATIONCHANGE;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_NAMECHANGE;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_PARENTCHANGE;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_REORDER;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_SELECTION;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_SELECTIONADD;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_SELECTIONREMOVE;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_SELECTIONWITHIN;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_SHOW;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_STATECHANGE;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_TEXTEDIT_CONVERSIONTARGETCHANGED;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_TEXTSELECTIONCHANGED;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_UNCLOAKED;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_VALUECHANGE;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_OEM_DEFINED_END;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_OEM_DEFINED_START;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_ALERT;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_ARRANGMENTPREVIEW;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_CAPTUREEND;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_CAPTURESTART;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_CONTEXTHELPEND;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_CONTEXTHELPSTART;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_DESKTOPSWITCH;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_DIALOGEND;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_DIALOGSTART;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_DRAGDROPEND;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_DRAGDROPSTART;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_END;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_FOREGROUND;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_IME_KEY_NOTIFICATION;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MENUEND;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MENUPOPUPEND;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MENUPOPUPSTART;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MENUSTART;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MINIMIZEEND;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MINIMIZESTART;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MOVESIZEEND;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_MOVESIZESTART;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SCROLLINGEND;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SCROLLINGSTART;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SOUND;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SWITCHEND;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SWITCHER_APPDROPPED;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SWITCHER_APPGRABBED;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SWITCHER_APPOVERTARGET;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SWITCHER_CANCELLED;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_SYSTEM_SWITCHSTART;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_UIA_EVENTID_END;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_UIA_EVENTID_START;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_UIA_PROPID_END;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_UIA_PROPID_START;\n\n#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize, Display)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n#[repr(u32)]\n#[allow(dead_code)]\npub enum WinEvent {\n    AiaEnd = EVENT_AIA_END,\n    AiaStart = EVENT_AIA_START,\n    ConsoleCaret = EVENT_CONSOLE_CARET,\n    ConsoleEnd = EVENT_CONSOLE_END,\n    ConsoleEndApplication = EVENT_CONSOLE_END_APPLICATION,\n    ConsoleLayout = EVENT_CONSOLE_LAYOUT,\n    ConsoleStartApplication = EVENT_CONSOLE_START_APPLICATION,\n    ConsoleUpdateRegion = EVENT_CONSOLE_UPDATE_REGION,\n    ConsoleUpdateScroll = EVENT_CONSOLE_UPDATE_SCROLL,\n    ConsoleUpdateSimple = EVENT_CONSOLE_UPDATE_SIMPLE,\n    ObjectAcceleratorChange = EVENT_OBJECT_ACCELERATORCHANGE,\n    ObjectCloaked = EVENT_OBJECT_CLOAKED,\n    ObjectContentScrolled = EVENT_OBJECT_CONTENTSCROLLED,\n    ObjectCreate = EVENT_OBJECT_CREATE,\n    ObjectDefActionChange = EVENT_OBJECT_DEFACTIONCHANGE,\n    ObjectDescriptionChange = EVENT_OBJECT_DESCRIPTIONCHANGE,\n    ObjectDestroy = EVENT_OBJECT_DESTROY,\n    ObjectDragCancel = EVENT_OBJECT_DRAGCANCEL,\n    ObjectDragComplete = EVENT_OBJECT_DRAGCOMPLETE,\n    ObjectDragDropped = EVENT_OBJECT_DRAGDROPPED,\n    ObjectDragEnter = EVENT_OBJECT_DRAGENTER,\n    ObjectDragLeave = EVENT_OBJECT_DRAGLEAVE,\n    ObjectDragStart = EVENT_OBJECT_DRAGSTART,\n    ObjectEnd = EVENT_OBJECT_END,\n    ObjectFocus = EVENT_OBJECT_FOCUS,\n    ObjectHelpChange = EVENT_OBJECT_HELPCHANGE,\n    ObjectHide = EVENT_OBJECT_HIDE,\n    ObjectHostedObjectsInvalidated = EVENT_OBJECT_HOSTEDOBJECTSINVALIDATED,\n    ObjectImeChange = EVENT_OBJECT_IME_CHANGE,\n    ObjectImeHide = EVENT_OBJECT_IME_HIDE,\n    ObjectImeShow = EVENT_OBJECT_IME_SHOW,\n    ObjectInvoked = EVENT_OBJECT_INVOKED,\n    ObjectLiveRegionChanged = EVENT_OBJECT_LIVEREGIONCHANGED,\n    ObjectLocationChange = EVENT_OBJECT_LOCATIONCHANGE,\n    ObjectNameChange = EVENT_OBJECT_NAMECHANGE,\n    ObjectParentChange = EVENT_OBJECT_PARENTCHANGE,\n    ObjectReorder = EVENT_OBJECT_REORDER,\n    ObjectSelection = EVENT_OBJECT_SELECTION,\n    ObjectSelectionAdd = EVENT_OBJECT_SELECTIONADD,\n    ObjectSelectionRemove = EVENT_OBJECT_SELECTIONREMOVE,\n    ObjectSelectionWithin = EVENT_OBJECT_SELECTIONWITHIN,\n    ObjectShow = EVENT_OBJECT_SHOW,\n    ObjectStateChange = EVENT_OBJECT_STATECHANGE,\n    ObjectTextEditConversionTargetChanged = EVENT_OBJECT_TEXTEDIT_CONVERSIONTARGETCHANGED,\n    ObjectTextSelectionChanged = EVENT_OBJECT_TEXTSELECTIONCHANGED,\n    ObjectUncloaked = EVENT_OBJECT_UNCLOAKED,\n    ObjectValueChange = EVENT_OBJECT_VALUECHANGE,\n    OemDefinedEnd = EVENT_OEM_DEFINED_END,\n    OemDefinedStart = EVENT_OEM_DEFINED_START,\n    SystemAlert = EVENT_SYSTEM_ALERT,\n    SystemArrangementPreview = EVENT_SYSTEM_ARRANGMENTPREVIEW,\n    SystemCaptureEnd = EVENT_SYSTEM_CAPTUREEND,\n    SystemCaptureStart = EVENT_SYSTEM_CAPTURESTART,\n    SystemContextHelpEnd = EVENT_SYSTEM_CONTEXTHELPEND,\n    SystemContextHelpStart = EVENT_SYSTEM_CONTEXTHELPSTART,\n    SystemDesktopSwitch = EVENT_SYSTEM_DESKTOPSWITCH,\n    SystemDialogEnd = EVENT_SYSTEM_DIALOGEND,\n    SystemDialogStart = EVENT_SYSTEM_DIALOGSTART,\n    SystemDragDropEnd = EVENT_SYSTEM_DRAGDROPEND,\n    SystemDragDropStart = EVENT_SYSTEM_DRAGDROPSTART,\n    SystemEnd = EVENT_SYSTEM_END,\n    SystemForeground = EVENT_SYSTEM_FOREGROUND,\n    SystemImeKeyNotification = EVENT_SYSTEM_IME_KEY_NOTIFICATION,\n    SystemMenuEnd = EVENT_SYSTEM_MENUEND,\n    SystemMenuPopupEnd = EVENT_SYSTEM_MENUPOPUPEND,\n    SystemMenuPopupStart = EVENT_SYSTEM_MENUPOPUPSTART,\n    SystemMenuStart = EVENT_SYSTEM_MENUSTART,\n    SystemMinimizeEnd = EVENT_SYSTEM_MINIMIZEEND,\n    SystemMinimizeStart = EVENT_SYSTEM_MINIMIZESTART,\n    SystemMoveSizeEnd = EVENT_SYSTEM_MOVESIZEEND,\n    SystemMoveSizeStart = EVENT_SYSTEM_MOVESIZESTART,\n    SystemScrollingEnd = EVENT_SYSTEM_SCROLLINGEND,\n    SystemScrollingStart = EVENT_SYSTEM_SCROLLINGSTART,\n    SystemSound = EVENT_SYSTEM_SOUND,\n    SystemSwitchEnd = EVENT_SYSTEM_SWITCHEND,\n    SystemSwitchStart = EVENT_SYSTEM_SWITCHSTART,\n    SystemSwitcherAppDropped = EVENT_SYSTEM_SWITCHER_APPDROPPED,\n    SystemSwitcherAppGrabbed = EVENT_SYSTEM_SWITCHER_APPGRABBED,\n    SystemSwitcherAppOverTarget = EVENT_SYSTEM_SWITCHER_APPOVERTARGET,\n    SystemSwitcherCancelled = EVENT_SYSTEM_SWITCHER_CANCELLED,\n    UiaEventIdSEnd = EVENT_UIA_EVENTID_END,\n    UiaEventIdStart = EVENT_UIA_EVENTID_START,\n    UiaPropIdSEnd = EVENT_UIA_PROPID_END,\n    UiaPropIdStart = EVENT_UIA_PROPID_START,\n}\n\nimpl TryFrom<u32> for WinEvent {\n    type Error = ();\n\n    fn try_from(value: u32) -> Result<Self, Self::Error> {\n        match value {\n            EVENT_AIA_END => Ok(Self::AiaEnd),\n            EVENT_AIA_START => Ok(Self::AiaStart),\n            EVENT_CONSOLE_CARET => Ok(Self::ConsoleCaret),\n            EVENT_CONSOLE_END => Ok(Self::ConsoleEnd),\n            EVENT_CONSOLE_END_APPLICATION => Ok(Self::ConsoleEndApplication),\n            EVENT_CONSOLE_LAYOUT => Ok(Self::ConsoleLayout),\n            EVENT_CONSOLE_START_APPLICATION => Ok(Self::ConsoleStartApplication),\n            EVENT_CONSOLE_UPDATE_REGION => Ok(Self::ConsoleUpdateRegion),\n            EVENT_CONSOLE_UPDATE_SCROLL => Ok(Self::ConsoleUpdateScroll),\n            EVENT_CONSOLE_UPDATE_SIMPLE => Ok(Self::ConsoleUpdateSimple),\n            EVENT_OBJECT_ACCELERATORCHANGE => Ok(Self::ObjectAcceleratorChange),\n            EVENT_OBJECT_CLOAKED => Ok(Self::ObjectCloaked),\n            EVENT_OBJECT_CONTENTSCROLLED => Ok(Self::ObjectContentScrolled),\n            EVENT_OBJECT_CREATE => Ok(Self::ObjectCreate),\n            EVENT_OBJECT_DEFACTIONCHANGE => Ok(Self::ObjectDefActionChange),\n            EVENT_OBJECT_DESCRIPTIONCHANGE => Ok(Self::ObjectDescriptionChange),\n            EVENT_OBJECT_DESTROY => Ok(Self::ObjectDestroy),\n            EVENT_OBJECT_DRAGCANCEL => Ok(Self::ObjectDragCancel),\n            EVENT_OBJECT_DRAGCOMPLETE => Ok(Self::ObjectDragComplete),\n            EVENT_OBJECT_DRAGDROPPED => Ok(Self::ObjectDragDropped),\n            EVENT_OBJECT_DRAGENTER => Ok(Self::ObjectDragEnter),\n            EVENT_OBJECT_DRAGLEAVE => Ok(Self::ObjectDragLeave),\n            EVENT_OBJECT_DRAGSTART => Ok(Self::ObjectDragStart),\n            EVENT_OBJECT_END => Ok(Self::ObjectEnd),\n            EVENT_OBJECT_FOCUS => Ok(Self::ObjectFocus),\n            EVENT_OBJECT_HELPCHANGE => Ok(Self::ObjectHelpChange),\n            EVENT_OBJECT_HIDE => Ok(Self::ObjectHide),\n            EVENT_OBJECT_HOSTEDOBJECTSINVALIDATED => Ok(Self::ObjectHostedObjectsInvalidated),\n            EVENT_OBJECT_IME_CHANGE => Ok(Self::ObjectImeChange),\n            EVENT_OBJECT_IME_HIDE => Ok(Self::ObjectImeHide),\n            EVENT_OBJECT_IME_SHOW => Ok(Self::ObjectImeShow),\n            EVENT_OBJECT_INVOKED => Ok(Self::ObjectInvoked),\n            EVENT_OBJECT_LIVEREGIONCHANGED => Ok(Self::ObjectLiveRegionChanged),\n            EVENT_OBJECT_LOCATIONCHANGE => Ok(Self::ObjectLocationChange),\n            EVENT_OBJECT_NAMECHANGE => Ok(Self::ObjectNameChange),\n            EVENT_OBJECT_PARENTCHANGE => Ok(Self::ObjectParentChange),\n            EVENT_OBJECT_REORDER => Ok(Self::ObjectReorder),\n            EVENT_OBJECT_SELECTION => Ok(Self::ObjectSelection),\n            EVENT_OBJECT_SELECTIONADD => Ok(Self::ObjectSelectionAdd),\n            EVENT_OBJECT_SELECTIONREMOVE => Ok(Self::ObjectSelectionRemove),\n            EVENT_OBJECT_SELECTIONWITHIN => Ok(Self::ObjectSelectionWithin),\n            EVENT_OBJECT_SHOW => Ok(Self::ObjectShow),\n            EVENT_OBJECT_STATECHANGE => Ok(Self::ObjectStateChange),\n            EVENT_OBJECT_TEXTEDIT_CONVERSIONTARGETCHANGED => {\n                Ok(Self::ObjectTextEditConversionTargetChanged)\n            }\n            EVENT_OBJECT_TEXTSELECTIONCHANGED => Ok(Self::ObjectTextSelectionChanged),\n            EVENT_OBJECT_UNCLOAKED => Ok(Self::ObjectUncloaked),\n            EVENT_OBJECT_VALUECHANGE => Ok(Self::ObjectValueChange),\n            EVENT_OEM_DEFINED_END => Ok(Self::OemDefinedEnd),\n            EVENT_OEM_DEFINED_START => Ok(Self::OemDefinedStart),\n            EVENT_SYSTEM_ALERT => Ok(Self::SystemAlert),\n            EVENT_SYSTEM_ARRANGMENTPREVIEW => Ok(Self::SystemArrangementPreview),\n            EVENT_SYSTEM_CAPTUREEND => Ok(Self::SystemCaptureEnd),\n            EVENT_SYSTEM_CAPTURESTART => Ok(Self::SystemCaptureStart),\n            EVENT_SYSTEM_CONTEXTHELPEND => Ok(Self::SystemContextHelpEnd),\n            EVENT_SYSTEM_CONTEXTHELPSTART => Ok(Self::SystemContextHelpStart),\n            EVENT_SYSTEM_DESKTOPSWITCH => Ok(Self::SystemDesktopSwitch),\n            EVENT_SYSTEM_DIALOGEND => Ok(Self::SystemDialogEnd),\n            EVENT_SYSTEM_DIALOGSTART => Ok(Self::SystemDialogStart),\n            EVENT_SYSTEM_DRAGDROPEND => Ok(Self::SystemDragDropEnd),\n            EVENT_SYSTEM_DRAGDROPSTART => Ok(Self::SystemDragDropStart),\n            EVENT_SYSTEM_END => Ok(Self::SystemEnd),\n            EVENT_SYSTEM_FOREGROUND => Ok(Self::SystemForeground),\n            EVENT_SYSTEM_IME_KEY_NOTIFICATION => Ok(Self::SystemImeKeyNotification),\n            EVENT_SYSTEM_MENUEND => Ok(Self::SystemMenuEnd),\n            EVENT_SYSTEM_MENUPOPUPEND => Ok(Self::SystemMenuPopupEnd),\n            EVENT_SYSTEM_MENUPOPUPSTART => Ok(Self::SystemMenuPopupStart),\n            EVENT_SYSTEM_MENUSTART => Ok(Self::SystemMenuStart),\n            EVENT_SYSTEM_MINIMIZEEND => Ok(Self::SystemMinimizeEnd),\n            EVENT_SYSTEM_MINIMIZESTART => Ok(Self::SystemMinimizeStart),\n            EVENT_SYSTEM_MOVESIZEEND => Ok(Self::SystemMoveSizeEnd),\n            EVENT_SYSTEM_MOVESIZESTART => Ok(Self::SystemMoveSizeStart),\n            EVENT_SYSTEM_SCROLLINGEND => Ok(Self::SystemScrollingEnd),\n            EVENT_SYSTEM_SCROLLINGSTART => Ok(Self::SystemScrollingStart),\n            EVENT_SYSTEM_SOUND => Ok(Self::SystemSound),\n            EVENT_SYSTEM_SWITCHEND => Ok(Self::SystemSwitchEnd),\n            EVENT_SYSTEM_SWITCHER_APPDROPPED => Ok(Self::SystemSwitcherAppDropped),\n            EVENT_SYSTEM_SWITCHER_APPGRABBED => Ok(Self::SystemSwitcherAppGrabbed),\n            EVENT_SYSTEM_SWITCHER_APPOVERTARGET => Ok(Self::SystemSwitcherAppOverTarget),\n            EVENT_SYSTEM_SWITCHER_CANCELLED => Ok(Self::SystemSwitcherCancelled),\n            EVENT_SYSTEM_SWITCHSTART => Ok(Self::SystemSwitchStart),\n            EVENT_UIA_EVENTID_END => Ok(Self::UiaEventIdSEnd),\n            EVENT_UIA_EVENTID_START => Ok(Self::UiaEventIdStart),\n            EVENT_UIA_PROPID_END => Ok(Self::UiaPropIdSEnd),\n            EVENT_UIA_PROPID_START => Ok(Self::UiaPropIdStart),\n\n            _ => Err(()),\n        }\n    }\n}\n"
  },
  {
    "path": "komorebi/src/winevent_listener.rs",
    "content": "use std::sync::OnceLock;\nuse std::time::Duration;\n\nuse crossbeam_channel::Receiver;\nuse crossbeam_channel::Sender;\nuse windows::Win32::UI::Accessibility::SetWinEventHook;\nuse windows::Win32::UI::WindowsAndMessaging::DispatchMessageW;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_MAX;\nuse windows::Win32::UI::WindowsAndMessaging::EVENT_MIN;\nuse windows::Win32::UI::WindowsAndMessaging::GetMessageW;\nuse windows::Win32::UI::WindowsAndMessaging::MSG;\nuse windows::Win32::UI::WindowsAndMessaging::TranslateMessage;\nuse windows::Win32::UI::WindowsAndMessaging::WINEVENT_OUTOFCONTEXT;\nuse windows::Win32::UI::WindowsAndMessaging::WINEVENT_SKIPOWNPROCESS;\n\nuse crate::window_manager_event::WindowManagerEvent;\nuse crate::windows_callbacks;\n\nstatic CHANNEL: OnceLock<(Sender<WindowManagerEvent>, Receiver<WindowManagerEvent>)> =\n    OnceLock::new();\n\nstatic EVENT_PUMP: OnceLock<std::thread::JoinHandle<()>> = OnceLock::new();\n\npub fn start() {\n    EVENT_PUMP.get_or_init(|| {\n        std::thread::spawn(move || {\n            unsafe {\n                SetWinEventHook(\n                    EVENT_MIN,\n                    EVENT_MAX,\n                    None,\n                    Some(windows_callbacks::win_event_hook),\n                    0,\n                    0,\n                    WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS,\n                )\n            };\n\n            let mut msg: MSG = MSG::default();\n\n            loop {\n                unsafe {\n                    if !GetMessageW(&mut msg, None, 0, 0).as_bool() {\n                        tracing::debug!(\"windows event processing thread shutdown\");\n                        break;\n                    };\n                    // TODO: error handling\n                    let _ = TranslateMessage(&msg);\n                    DispatchMessageW(&msg);\n                }\n\n                std::thread::sleep(Duration::from_millis(10))\n            }\n        })\n    });\n}\n\nfn channel() -> &'static (Sender<WindowManagerEvent>, Receiver<WindowManagerEvent>) {\n    CHANNEL.get_or_init(|| crossbeam_channel::bounded(20))\n}\n\npub fn event_tx() -> Sender<WindowManagerEvent> {\n    channel().0.clone()\n}\n\npub fn event_rx() -> Receiver<WindowManagerEvent> {\n    channel().1.clone()\n}\n"
  },
  {
    "path": "komorebi/src/workspace.rs",
    "content": "use std::collections::VecDeque;\nuse std::ffi::OsStr;\nuse std::fmt::Display;\nuse std::fmt::Formatter;\nuse std::io::Write;\nuse std::num::NonZeroUsize;\nuse std::sync::atomic::Ordering;\n\nuse crate::DATA_DIR;\nuse crate::DEFAULT_CONTAINER_PADDING;\nuse crate::DEFAULT_WORKSPACE_PADDING;\nuse crate::FloatingLayerBehaviour;\nuse crate::INITIAL_CONFIGURATION_LOADED;\nuse crate::KomorebiTheme;\nuse crate::NO_TITLEBAR;\nuse crate::REGEX_IDENTIFIERS;\nuse crate::REMOVE_TITLEBARS;\nuse crate::SocketMessage;\nuse crate::Wallpaper;\nuse crate::WindowContainerBehaviour;\nuse crate::border_manager;\nuse crate::container::Container;\nuse crate::core::Axis;\nuse crate::core::CustomLayout;\nuse crate::core::CycleDirection;\nuse crate::core::DefaultLayout;\nuse crate::core::Layout;\nuse crate::core::LayoutOptions;\nuse crate::core::OperationDirection;\nuse crate::core::Rect;\nuse crate::lockable_sequence::LockableSequence;\nuse crate::ring::Ring;\nuse crate::should_act;\nuse crate::stackbar_manager;\nuse crate::stackbar_manager::STACKBAR_TAB_HEIGHT;\nuse crate::static_config::WorkspaceConfig;\nuse crate::window::Window;\nuse crate::window::WindowDetails;\nuse crate::windows_api::WindowsApi;\nuse color_eyre::eyre;\nuse color_eyre::eyre::OptionExt;\nuse komorebi_themes::Base16ColourPalette;\nuse komorebi_themes::KomorebiThemeCustom as Custom;\nuse serde::Deserialize;\nuse serde::Serialize;\nuse uds_windows::UnixStream;\n\n#[allow(clippy::struct_field_names)]\n#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\npub struct Workspace {\n    pub name: Option<String>,\n    pub containers: Ring<Container>,\n    pub monocle_container: Option<Container>,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub monocle_container_restore_idx: Option<usize>,\n    pub maximized_window: Option<Window>,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub maximized_window_restore_idx: Option<usize>,\n    pub floating_windows: Ring<Window>,\n    pub layout: Layout,\n    pub layout_options: Option<LayoutOptions>,\n    pub layout_rules: Vec<(usize, Layout)>,\n    pub layout_flip: Option<Axis>,\n    pub workspace_padding: Option<i32>,\n    pub container_padding: Option<i32>,\n    pub latest_layout: Vec<Rect>,\n    pub resize_dimensions: Vec<Option<Rect>>,\n    pub tile: bool,\n    pub work_area_offset: Option<Rect>,\n    pub apply_window_based_work_area_offset: bool,\n    pub window_container_behaviour: Option<WindowContainerBehaviour>,\n    pub window_container_behaviour_rules: Option<Vec<(usize, WindowContainerBehaviour)>>,\n    pub float_override: Option<bool>,\n    #[serde(skip)]\n    pub globals: WorkspaceGlobals,\n    pub layer: WorkspaceLayer,\n    pub floating_layer_behaviour: Option<FloatingLayerBehaviour>,\n    pub wallpaper: Option<Wallpaper>,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub workspace_config: Option<WorkspaceConfig>,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub preselected_container_idx: Option<usize>,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub promotion_swap_container_idx: Option<usize>,\n}\n\n#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\npub enum WorkspaceLayer {\n    #[default]\n    Tiling,\n    Floating,\n}\n\nimpl Display for WorkspaceLayer {\n    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {\n        match self {\n            WorkspaceLayer::Tiling => write!(f, \"Tiling\"),\n            WorkspaceLayer::Floating => write!(f, \"Floating\"),\n        }\n    }\n}\n\nimpl_ring_elements!(Workspace, Container);\nimpl_ring_elements!(Workspace, Window, \"floating_window\");\n\nimpl Default for Workspace {\n    fn default() -> Self {\n        Self {\n            name: None,\n            containers: Ring::default(),\n            monocle_container: None,\n            maximized_window: None,\n            maximized_window_restore_idx: None,\n            monocle_container_restore_idx: None,\n            floating_windows: Ring::default(),\n            layout: Layout::Default(DefaultLayout::BSP),\n            layout_options: None,\n            layout_rules: vec![],\n            layout_flip: None,\n            workspace_padding: Option::from(DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst)),\n            container_padding: Option::from(DEFAULT_CONTAINER_PADDING.load(Ordering::SeqCst)),\n            latest_layout: vec![],\n            resize_dimensions: vec![],\n            tile: true,\n            work_area_offset: None,\n            apply_window_based_work_area_offset: true,\n            window_container_behaviour: None,\n            window_container_behaviour_rules: None,\n            float_override: None,\n            layer: Default::default(),\n            floating_layer_behaviour: Default::default(),\n            globals: Default::default(),\n            workspace_config: None,\n            wallpaper: None,\n            preselected_container_idx: None,\n            promotion_swap_container_idx: None,\n        }\n    }\n}\n\n#[derive(Debug)]\npub enum WorkspaceWindowLocation {\n    Monocle(usize), // window_idx\n    Maximized,\n    Container(usize, usize), // container_idx, window_idx\n    Floating(usize),         // idx in floating_windows\n}\n\n#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Settings setup either by the parent monitor or by the `WindowManager`\npub struct WorkspaceGlobals {\n    pub container_padding: Option<i32>,\n    pub workspace_padding: Option<i32>,\n    pub border_width: i32,\n    pub border_offset: i32,\n    pub work_area: Rect,\n    pub work_area_offset: Option<Rect>,\n    pub window_based_work_area_offset: Option<Rect>,\n    pub window_based_work_area_offset_limit: isize,\n    pub floating_layer_behaviour: Option<FloatingLayerBehaviour>,\n}\n\nimpl Workspace {\n    pub fn load_static_config(&mut self, config: &WorkspaceConfig) -> eyre::Result<()> {\n        self.name = Option::from(config.name.clone());\n\n        self.container_padding = config.container_padding;\n\n        self.workspace_padding = config.workspace_padding;\n\n        if let Some(layout) = &config.layout {\n            self.layout = Layout::Default(*layout);\n        }\n\n        #[allow(deprecated)]\n        if let Some(pathbuf) = &config.custom_layout {\n            let layout = CustomLayout::from_path(pathbuf)?;\n            self.layout = Layout::Custom(layout);\n        }\n\n        #[allow(deprecated)]\n        {\n            self.tile = !(config.custom_layout.is_none()\n                && config.layout.is_none()\n                && config.tile.is_none()\n                || config.tile.is_some_and(|tile| !tile));\n        }\n\n        let mut all_layout_rules = vec![];\n        if let Some(layout_rules) = &config.layout_rules {\n            for (count, rule) in layout_rules {\n                all_layout_rules.push((*count, Layout::Default(*rule)));\n            }\n\n            all_layout_rules.sort_by_key(|(i, _)| *i);\n            self.tile = true;\n        }\n\n        self.layout_rules = all_layout_rules.clone();\n\n        #[allow(deprecated)]\n        if let Some(layout_rules) = &config.custom_layout_rules {\n            for (count, pathbuf) in layout_rules {\n                let rule = CustomLayout::from_path(pathbuf)?;\n                all_layout_rules.push((*count, Layout::Custom(rule)));\n            }\n\n            all_layout_rules.sort_by_key(|(i, _)| *i);\n            self.tile = true;\n            self.layout_rules = all_layout_rules;\n        }\n\n        self.work_area_offset = config.work_area_offset;\n\n        self.apply_window_based_work_area_offset =\n            config.apply_window_based_work_area_offset.unwrap_or(true);\n\n        self.window_container_behaviour = config.window_container_behaviour;\n\n        if let Some(window_container_behaviour_rules) = &config.window_container_behaviour_rules {\n            if window_container_behaviour_rules.is_empty() {\n                self.window_container_behaviour_rules = None;\n            } else {\n                let mut all_rules = vec![];\n                for (count, behaviour) in window_container_behaviour_rules {\n                    all_rules.push((*count, *behaviour));\n                }\n\n                all_rules.sort_by_key(|(i, _)| *i);\n                self.window_container_behaviour_rules = Some(all_rules);\n            }\n        } else {\n            self.window_container_behaviour_rules = None;\n        }\n\n        self.float_override = config.float_override;\n        self.layout_flip = config.layout_flip;\n        self.floating_layer_behaviour = config.floating_layer_behaviour;\n        self.wallpaper = config.wallpaper.clone();\n        self.layout_options = config.layout_options;\n\n        tracing::debug!(\n            \"Workspace '{}' loaded layout_options: {:?}\",\n            self.name.as_deref().unwrap_or(\"unnamed\"),\n            self.layout_options\n        );\n\n        self.workspace_config = Some(config.clone());\n\n        Ok(())\n    }\n\n    pub fn hide(&mut self, omit: Option<isize>) {\n        for window in self.floating_windows_mut().iter_mut().rev() {\n            let mut should_hide = omit.is_none();\n\n            if !should_hide\n                && let Some(omit) = omit\n                && omit != window.hwnd\n            {\n                should_hide = true\n            }\n\n            if should_hide {\n                window.hide();\n            }\n        }\n\n        for container in self.containers_mut() {\n            container.hide(omit)\n        }\n\n        if let Some(window) = self.maximized_window {\n            window.hide();\n        }\n\n        if let Some(container) = &self.monocle_container {\n            container.hide(omit)\n        }\n    }\n\n    pub fn apply_wallpaper(\n        &self,\n        hmonitor: isize,\n        monitor_wp: &Option<Wallpaper>,\n    ) -> eyre::Result<()> {\n        if let Some(wallpaper) = self.wallpaper.as_ref().or(monitor_wp.as_ref()) {\n            if let Err(error) = WindowsApi::set_wallpaper(&wallpaper.path, hmonitor) {\n                tracing::error!(\"failed to set wallpaper: {error}\");\n            }\n\n            if wallpaper.generate_theme.unwrap_or(true) {\n                let variant = wallpaper\n                    .theme_options\n                    .as_ref()\n                    .and_then(|t| t.theme_variant)\n                    .unwrap_or_default();\n\n                let cached_palette = DATA_DIR.join(format!(\n                    \"{}.base16.{variant}.json\",\n                    wallpaper\n                        .path\n                        .file_name()\n                        .unwrap_or(OsStr::new(\"tmp\"))\n                        .to_string_lossy()\n                ));\n\n                let mut base16_palette = None;\n\n                if cached_palette.is_file() {\n                    tracing::info!(\n                        \"colour palette for wallpaper {} found in cache\",\n                        cached_palette.display()\n                    );\n\n                    // this code is VERY slow on debug builds - should only be a one-time issue when loading\n                    // an uncached wallpaper\n                    if let Ok(palette) = serde_json::from_str::<Base16ColourPalette>(\n                        &std::fs::read_to_string(&cached_palette)?,\n                    ) {\n                        base16_palette = Some(palette);\n                    }\n                };\n\n                if base16_palette.is_none() {\n                    base16_palette =\n                        komorebi_themes::generate_base16_palette(&wallpaper.path, variant).ok();\n\n                    std::fs::write(\n                        &cached_palette,\n                        serde_json::to_string_pretty(&base16_palette)?,\n                    )?;\n\n                    tracing::info!(\n                        \"colour palette for wallpaper {} cached\",\n                        cached_palette.display()\n                    );\n                }\n\n                if let Some(palette) = base16_palette {\n                    let komorebi_theme = KomorebiTheme::Custom(Custom {\n                        colours: Box::new(palette),\n                        single_border: wallpaper\n                            .theme_options\n                            .as_ref()\n                            .and_then(|o| o.single_border),\n                        stack_border: wallpaper\n                            .theme_options\n                            .as_ref()\n                            .and_then(|o| o.stack_border),\n                        monocle_border: wallpaper\n                            .theme_options\n                            .as_ref()\n                            .and_then(|o| o.monocle_border),\n                        floating_border: wallpaper\n                            .theme_options\n                            .as_ref()\n                            .and_then(|o| o.floating_border),\n                        unfocused_border: wallpaper\n                            .theme_options\n                            .as_ref()\n                            .and_then(|o| o.unfocused_border),\n                        unfocused_locked_border: wallpaper\n                            .theme_options\n                            .as_ref()\n                            .and_then(|o| o.unfocused_locked_border),\n                        stackbar_focused_text: wallpaper\n                            .theme_options\n                            .as_ref()\n                            .and_then(|o| o.stackbar_focused_text),\n                        stackbar_unfocused_text: wallpaper\n                            .theme_options\n                            .as_ref()\n                            .and_then(|o| o.stackbar_unfocused_text),\n                        stackbar_background: wallpaper\n                            .theme_options\n                            .as_ref()\n                            .and_then(|o| o.stackbar_background),\n                        bar_accent: wallpaper.theme_options.as_ref().and_then(|o| o.bar_accent),\n                    });\n\n                    let bytes = SocketMessage::Theme(Box::new(komorebi_theme)).as_bytes()?;\n\n                    let socket = DATA_DIR.join(\"komorebi.sock\");\n                    match UnixStream::connect(socket) {\n                        Ok(mut stream) => {\n                            if let Err(error) = stream.write_all(&bytes) {\n                                tracing::error!(\"failed to send theme update message: {error}\")\n                            }\n                        }\n                        Err(error) => {\n                            tracing::error!(\"{error}\")\n                        }\n                    }\n                }\n            }\n        }\n\n        Ok(())\n    }\n\n    pub fn restore(\n        &mut self,\n        mouse_follows_focus: bool,\n        hmonitor: isize,\n        monitor_wp: &Option<Wallpaper>,\n    ) -> eyre::Result<()> {\n        if let Some(container) = &self.monocle_container\n            && let Some(window) = container.focused_window()\n        {\n            container.restore();\n            window.focus(mouse_follows_focus)?;\n            return self.apply_wallpaper(hmonitor, monitor_wp);\n        }\n\n        let idx = self.focused_container_idx();\n        let mut to_focus = None;\n\n        for (i, container) in self.containers_mut().iter_mut().enumerate() {\n            if let Some(window) = container.focused_window_mut()\n                && idx == i\n            {\n                to_focus = Option::from(*window);\n            }\n\n            container.restore();\n        }\n\n        if let Some(container) = self.focused_container_mut() {\n            container.focus_window(container.focused_window_idx());\n        }\n\n        for window in self.floating_windows() {\n            window.restore();\n        }\n\n        // Do this here to make sure that an error doesn't stop the restoration of other windows\n        // Maximised windows and floating windows should always be drawn at the top of the Z order\n        // when switching to a workspace\n        if let Some(window) = to_focus {\n            if self.maximized_window.is_none() && matches!(self.layer, WorkspaceLayer::Tiling) {\n                window.focus(mouse_follows_focus)?;\n            } else if let Some(maximized_window) = self.maximized_window {\n                maximized_window.restore();\n                maximized_window.focus(mouse_follows_focus)?;\n            } else if let Some(floating_window) = self.focused_floating_window() {\n                floating_window.focus(mouse_follows_focus)?;\n            }\n        } else if let Some(maximized_window) = self.maximized_window {\n            maximized_window.restore();\n            maximized_window.focus(mouse_follows_focus)?;\n        } else if let Some(floating_window) = self.focused_floating_window() {\n            floating_window.focus(mouse_follows_focus)?;\n        }\n\n        self.apply_wallpaper(hmonitor, monitor_wp)\n    }\n\n    pub fn update(&mut self) -> eyre::Result<()> {\n        if !INITIAL_CONFIGURATION_LOADED.load(Ordering::SeqCst) {\n            return Ok(());\n        }\n\n        // make sure we are never holding on to empty containers\n        self.containers_mut()\n            .retain(|c| c.is_preselect() || !c.windows().is_empty());\n\n        let container_padding = self\n            .container_padding\n            .or(self.globals.container_padding)\n            .unwrap_or_default();\n        let workspace_padding = self\n            .workspace_padding\n            .or(self.globals.workspace_padding)\n            .unwrap_or_default();\n        let border_width = self.globals.border_width;\n        let border_offset = self.globals.border_offset;\n        let work_area = self.globals.work_area;\n        let work_area_offset = self.work_area_offset.or(self.globals.work_area_offset);\n        let window_based_work_area_offset = self.globals.window_based_work_area_offset;\n        let window_based_work_area_offset_limit = self.globals.window_based_work_area_offset_limit;\n\n        let mut adjusted_work_area = work_area_offset.map_or_else(\n            || work_area,\n            |offset| {\n                let mut with_offset = work_area;\n                with_offset.left += offset.left;\n                with_offset.top += offset.top;\n                with_offset.right -= offset.right;\n                with_offset.bottom -= offset.bottom;\n\n                with_offset\n            },\n        );\n\n        if (self.containers().len() <= window_based_work_area_offset_limit as usize\n            || self.monocle_container.is_some() && window_based_work_area_offset_limit > 0)\n            && self.apply_window_based_work_area_offset\n        {\n            adjusted_work_area = window_based_work_area_offset.map_or_else(\n                || adjusted_work_area,\n                |offset| {\n                    let mut with_offset = adjusted_work_area;\n                    with_offset.left += offset.left;\n                    with_offset.top += offset.top;\n                    with_offset.right -= offset.right;\n                    with_offset.bottom -= offset.bottom;\n\n                    with_offset\n                },\n            );\n        }\n\n        adjusted_work_area.add_padding(workspace_padding);\n\n        self.enforce_resize_constraints();\n\n        if !self.layout_rules.is_empty() {\n            let mut updated_layout = None;\n\n            for (threshold, layout) in &self.layout_rules {\n                if self.containers().len() >= *threshold {\n                    updated_layout = Option::from(layout.clone());\n                }\n            }\n\n            if let Some(updated_layout) = updated_layout {\n                self.layout = updated_layout;\n            }\n        }\n\n        if let Some(window_container_behaviour_rules) = &self.window_container_behaviour_rules {\n            let mut updated_behaviour = None;\n            for (threshold, behaviour) in window_container_behaviour_rules {\n                if self.containers().len() >= *threshold {\n                    updated_behaviour = Option::from(*behaviour);\n                }\n            }\n\n            self.window_container_behaviour = updated_behaviour;\n        }\n\n        let managed_maximized_window = self.maximized_window.is_some();\n\n        if self.tile {\n            if let Some(container) = &mut self.monocle_container {\n                if let Some(window) = container.focused_window_mut() {\n                    adjusted_work_area.add_padding(container_padding);\n                    adjusted_work_area.add_padding(border_offset);\n                    adjusted_work_area.add_padding(border_width);\n                    window.set_position(&adjusted_work_area, true)?;\n                };\n            } else if let Some(window) = &mut self.maximized_window {\n                window.maximize();\n            } else if !self.containers().is_empty() {\n                tracing::debug!(\n                    \"Workspace '{}' update() - self.layout_options before calculate: {:?}\",\n                    self.name.as_deref().unwrap_or(\"unnamed\"),\n                    self.layout_options\n                );\n                let mut layouts = self.layout.as_boxed_arrangement().calculate(\n                    &adjusted_work_area,\n                    NonZeroUsize::new(self.containers().len()).ok_or_eyre(\n                        \"there must be at least one container to calculate a workspace layout\",\n                    )?,\n                    Some(container_padding),\n                    self.layout_flip,\n                    &self.resize_dimensions,\n                    self.focused_container_idx(),\n                    self.layout_options,\n                    &self.latest_layout,\n                );\n\n                let should_remove_titlebars = REMOVE_TITLEBARS.load(Ordering::SeqCst);\n                let no_titlebar = NO_TITLEBAR.lock().clone();\n                let regex_identifiers = REGEX_IDENTIFIERS.lock().clone();\n\n                let containers = self.containers_mut();\n\n                for (i, container) in containers.iter_mut().enumerate() {\n                    let window_count = container.windows().len();\n\n                    if let Some(layout) = layouts.get_mut(i) {\n                        layout.add_padding(border_offset);\n                        layout.add_padding(border_width);\n\n                        if stackbar_manager::should_have_stackbar(window_count) {\n                            let tab_height = STACKBAR_TAB_HEIGHT.load(Ordering::SeqCst);\n                            let total_height = tab_height + container_padding;\n\n                            layout.top += total_height;\n                            layout.bottom -= total_height;\n                        }\n\n                        for window in container.windows() {\n                            if container\n                                .focused_window()\n                                .is_some_and(|w| w.hwnd == window.hwnd)\n                            {\n                                let should_remove_titlebar_for_window = should_act(\n                                    &window.title().unwrap_or_default(),\n                                    &window.exe().unwrap_or_default(),\n                                    &window.class().unwrap_or_default(),\n                                    &window.path().unwrap_or_default(),\n                                    &no_titlebar,\n                                    &regex_identifiers,\n                                )\n                                .is_some();\n\n                                if should_remove_titlebars && should_remove_titlebar_for_window {\n                                    window.remove_title_bar()?;\n                                } else if should_remove_titlebar_for_window {\n                                    window.add_title_bar()?;\n                                }\n\n                                // If a window has been unmaximized via toggle-maximize, this block\n                                // will make sure that it is unmaximized via restore_window\n                                if window.is_maximized() && !managed_maximized_window {\n                                    WindowsApi::restore_window(window.hwnd);\n                                }\n                            }\n                            window.set_position(layout, false)?;\n                        }\n                    }\n                }\n\n                self.latest_layout = layouts;\n            }\n        }\n\n        // Always make sure that the length of the resize dimensions vec is the same as the\n        // number of layouts / containers. This should never actually truncate as the remove_window\n        // function takes care of cleaning up resize dimensions when destroying empty containers\n        let container_count = self.containers().len();\n\n        // since monocle is a toggle, we never want to truncate the resize dimensions since it will\n        // almost always be toggled off and the container will be reintegrated into layout\n        //\n        // without this check, if there are exactly two containers, when one is toggled to monocle\n        // the resize dimensions will be truncated to len == 1, and when it is reintegrated, if it\n        // had a resize adjustment before, that will have been lost\n        if self.monocle_container.is_none() {\n            self.resize_dimensions.resize(container_count, None);\n        }\n\n        Ok(())\n    }\n\n    pub fn container_for_window(&self, hwnd: isize) -> Option<&Container> {\n        self.containers().get(self.container_idx_for_window(hwnd)?)\n    }\n\n    /// If there is a container which holds the window with `hwnd` it will focus that container.\n    /// This function will only emit a focus on the window if it isn't the focused window of that\n    /// container already.\n    pub fn focus_container_by_window(&mut self, hwnd: isize) -> eyre::Result<()> {\n        let container_idx = self\n            .container_idx_for_window(hwnd)\n            .ok_or_eyre(\"there is no container/window\")?;\n\n        let container = self\n            .containers_mut()\n            .get_mut(container_idx)\n            .ok_or_eyre(\"there is no container\")?;\n\n        let window_idx = container\n            .idx_for_window(hwnd)\n            .ok_or_eyre(\"there is no window\")?;\n\n        let mut should_load = false;\n\n        if container.focused_window_idx() != window_idx {\n            should_load = true\n        }\n\n        container.focus_window(window_idx);\n\n        if should_load {\n            container.load_focused_window();\n        }\n\n        self.focus_container(container_idx);\n\n        Ok(())\n    }\n\n    pub fn container_idx_from_current_point(&self) -> Option<usize> {\n        let mut idx = None;\n\n        let point = WindowsApi::cursor_pos().ok()?;\n\n        for (i, _container) in self.containers().iter().enumerate() {\n            if let Some(rect) = self.latest_layout.get(i)\n                && rect.contains_point((point.x, point.y))\n            {\n                idx = Option::from(i);\n            }\n        }\n\n        idx\n    }\n\n    pub fn hwnd_from_exe(&self, exe: &str) -> Option<isize> {\n        for container in self.containers() {\n            if let Some(hwnd) = container.hwnd_from_exe(exe) {\n                return Option::from(hwnd);\n            }\n        }\n\n        if let Some(window) = self.maximized_window\n            && let Ok(window_exe) = window.exe()\n            && exe == window_exe\n        {\n            return Option::from(window.hwnd);\n        }\n\n        if let Some(container) = &self.monocle_container\n            && let Some(hwnd) = container.hwnd_from_exe(exe)\n        {\n            return Option::from(hwnd);\n        }\n\n        for window in self.floating_windows() {\n            if let Ok(window_exe) = window.exe()\n                && exe == window_exe\n            {\n                return Option::from(window.hwnd);\n            }\n        }\n\n        None\n    }\n\n    pub fn location_from_exe(&self, exe: &str) -> Option<WorkspaceWindowLocation> {\n        for (container_idx, container) in self.containers().iter().enumerate() {\n            if let Some(window_idx) = container.idx_from_exe(exe) {\n                return Some(WorkspaceWindowLocation::Container(\n                    container_idx,\n                    window_idx,\n                ));\n            }\n        }\n\n        if let Some(window) = self.maximized_window\n            && let Ok(window_exe) = window.exe()\n            && exe == window_exe\n        {\n            return Some(WorkspaceWindowLocation::Maximized);\n        }\n\n        if let Some(container) = &self.monocle_container\n            && let Some(window_idx) = container.idx_from_exe(exe)\n        {\n            return Some(WorkspaceWindowLocation::Monocle(window_idx));\n        }\n\n        for (window_idx, window) in self.floating_windows().iter().enumerate() {\n            if let Ok(window_exe) = window.exe()\n                && exe == window_exe\n            {\n                return Some(WorkspaceWindowLocation::Floating(window_idx));\n            }\n        }\n\n        None\n    }\n\n    pub fn contains_managed_window(&self, hwnd: isize) -> bool {\n        for container in self.containers() {\n            if container.contains_window(hwnd) {\n                return true;\n            }\n        }\n\n        if let Some(window) = self.maximized_window\n            && hwnd == window.hwnd\n        {\n            return true;\n        }\n\n        if let Some(container) = &self.monocle_container\n            && container.contains_window(hwnd)\n        {\n            return true;\n        }\n\n        false\n    }\n\n    pub fn is_focused_window_monocle_or_maximized(&self) -> eyre::Result<bool> {\n        let hwnd = WindowsApi::foreground_window()?;\n        if let Some(window) = self.maximized_window\n            && hwnd == window.hwnd\n        {\n            return Ok(true);\n        }\n\n        if let Some(container) = &self.monocle_container\n            && container.contains_window(hwnd)\n        {\n            return Ok(true);\n        }\n\n        Ok(false)\n    }\n\n    pub fn is_empty(&self) -> bool {\n        self.containers().is_empty()\n            && self.maximized_window.is_none()\n            && self.monocle_container.is_none()\n            && self.floating_windows().is_empty()\n    }\n\n    pub fn contains_window(&self, hwnd: isize) -> bool {\n        for container in self.containers() {\n            if container.contains_window(hwnd) {\n                return true;\n            }\n        }\n\n        if let Some(window) = self.maximized_window\n            && hwnd == window.hwnd\n        {\n            return true;\n        }\n\n        if let Some(container) = &self.monocle_container\n            && container.contains_window(hwnd)\n        {\n            return true;\n        }\n\n        for window in self.floating_windows() {\n            if hwnd == window.hwnd {\n                return true;\n            }\n        }\n\n        false\n    }\n\n    pub fn promote_container(&mut self) -> eyre::Result<()> {\n        let focused_idx = self.focused_container_idx();\n        let container = self\n            .containers_mut()\n            .remove_respecting_locks(focused_idx)\n            .ok_or_eyre(\"there is no container\")?;\n\n        let primary_idx = match &self.layout {\n            Layout::Default(_) => 0,\n            Layout::Custom(layout) => layout.first_container_idx(\n                layout\n                    .primary_idx()\n                    .ok_or_eyre(\"this custom layout does not have a primary column\")?,\n            ),\n        };\n\n        let insertion_idx = self\n            .containers_mut()\n            .insert_respecting_locks(primary_idx, container);\n        self.focus_container(insertion_idx);\n\n        Ok(())\n    }\n\n    pub fn add_container_to_back(&mut self, container: Container) {\n        self.containers_mut().push_back(container);\n        self.focus_last_container();\n    }\n\n    pub fn add_container_to_front(&mut self, container: Container) {\n        self.containers_mut().push_front(container);\n        self.focus_first_container();\n    }\n\n    // this fn respects locked container indexes - we should use it for pretty much everything\n    // except monocle and maximize toggles\n    pub fn insert_container_at_idx(&mut self, idx: usize, container: Container) -> usize {\n        let insertion_idx = self\n            .containers_mut()\n            .insert_respecting_locks(idx, container);\n\n        if insertion_idx > self.resize_dimensions.len() {\n            self.resize_dimensions.push(None);\n        } else {\n            self.resize_dimensions.insert(insertion_idx, None);\n        }\n\n        self.focus_container(insertion_idx);\n\n        insertion_idx\n    }\n\n    // this fn respects locked container indexes - we should use it for pretty much everything\n    // except monocle and maximize toggles\n    pub fn remove_container_by_idx(&mut self, idx: usize) -> Option<Container> {\n        let container = self.containers_mut().remove_respecting_locks(idx);\n\n        if idx < self.resize_dimensions.len() {\n            self.resize_dimensions.remove(idx);\n        }\n\n        container\n    }\n\n    pub fn container_idx_for_window(&self, hwnd: isize) -> Option<usize> {\n        let mut idx = None;\n        for (i, x) in self.containers().iter().enumerate() {\n            if x.contains_window(hwnd) {\n                idx = Option::from(i);\n            }\n        }\n\n        idx\n    }\n\n    pub fn remove_window(&mut self, hwnd: isize) -> eyre::Result<()> {\n        border_manager::delete_border(hwnd);\n\n        if self.floating_windows().iter().any(|w| w.hwnd == hwnd) {\n            self.floating_windows_mut().retain(|w| w.hwnd != hwnd);\n            return Ok(());\n        }\n\n        if let Some(container) = &mut self.monocle_container\n            && let Some(window_idx) = container\n                .windows()\n                .iter()\n                .position(|window| window.hwnd == hwnd)\n        {\n            container\n                .remove_window_by_idx(window_idx)\n                .ok_or_eyre(\"there is no window\")?;\n\n            if container.windows().is_empty() {\n                self.monocle_container = None;\n                self.monocle_container_restore_idx = None;\n            }\n\n            for c in self.containers() {\n                c.restore();\n            }\n\n            return Ok(());\n        }\n\n        if let Some(window) = self.maximized_window\n            && window.hwnd == hwnd\n        {\n            window.unmaximize();\n            self.maximized_window = None;\n            self.maximized_window_restore_idx = None;\n            return Ok(());\n        }\n\n        let container_idx = self\n            .container_idx_for_window(hwnd)\n            .ok_or_eyre(\"there is no window\")?;\n\n        let container = self\n            .containers_mut()\n            .get_mut(container_idx)\n            .ok_or_eyre(\"there is no container\")?;\n\n        let window_idx = container\n            .windows()\n            .iter()\n            .position(|window| window.hwnd == hwnd)\n            .ok_or_eyre(\"there is no window\")?;\n\n        container\n            .remove_window_by_idx(window_idx)\n            .ok_or_eyre(\"there is no window\")?;\n\n        if container.windows().is_empty() {\n            self.remove_container_by_idx(container_idx);\n            self.focus_previous_container();\n        } else {\n            container.load_focused_window();\n            if let Some(window) = container.focused_window() {\n                window.focus(false)?;\n            }\n        }\n\n        Ok(())\n    }\n\n    pub fn remove_focused_container(&mut self) -> Option<Container> {\n        let focused_idx = self.focused_container_idx();\n        let container = self.remove_container_by_idx(focused_idx);\n        self.focus_previous_container();\n\n        container\n    }\n\n    pub fn remove_container(&mut self, idx: usize) -> Option<Container> {\n        let container = self.remove_container_by_idx(idx);\n        self.focus_previous_container();\n\n        container\n    }\n\n    pub fn preselect_container_idx(&mut self, insertion_idx: usize) {\n        self.preselected_container_idx = Some(insertion_idx);\n        self.insert_container_at_idx(insertion_idx, Container::preselect());\n    }\n\n    pub fn cancel_preselect(&mut self) {\n        if let Some(idx) = self.preselected_container_idx {\n            self.containers_mut().remove_respecting_locks(idx);\n            self.preselected_container_idx = None;\n        }\n    }\n\n    pub fn new_idx_for_direction(&self, direction: OperationDirection) -> Option<usize> {\n        let len = NonZeroUsize::new(self.containers().len())?;\n\n        direction.destination(\n            self.layout.as_boxed_direction().as_ref(),\n            self.layout_flip,\n            self.focused_container_idx(),\n            len,\n            self.layout_options,\n        )\n    }\n\n    pub fn new_idx_for_cycle_direction(&self, direction: CycleDirection) -> Option<usize> {\n        Option::from(direction.next_idx(\n            self.focused_container_idx(),\n            NonZeroUsize::new(self.containers().len())?,\n        ))\n    }\n\n    // this is what we use for stacking\n    pub fn move_window_to_container(&mut self, target_container_idx: usize) -> eyre::Result<()> {\n        let focused_idx = self.focused_container_idx();\n\n        let container = self\n            .focused_container_mut()\n            .ok_or_eyre(\"there is no container\")?;\n\n        let window = container\n            .remove_focused_window()\n            .ok_or_eyre(\"there is no window\")?;\n\n        // This is a little messy\n        let adjusted_target_container_index = if container.windows().is_empty() {\n            self.remove_container_by_idx(focused_idx);\n\n            if focused_idx < target_container_idx {\n                target_container_idx.saturating_sub(1)\n            } else {\n                target_container_idx\n            }\n        } else {\n            container.load_focused_window();\n            target_container_idx\n        };\n\n        let target_container = self\n            .containers_mut()\n            .get_mut(adjusted_target_container_index)\n            .ok_or_eyre(\"there is no container\")?;\n\n        target_container.add_window(window);\n\n        self.focus_container(adjusted_target_container_index);\n        self.focused_container_mut()\n            .ok_or_eyre(\"there is no container\")?\n            .load_focused_window();\n\n        Ok(())\n    }\n\n    pub fn new_container_for_focused_window(&mut self) -> eyre::Result<()> {\n        let focused_container_idx = self.focused_container_idx();\n\n        let container = self\n            .focused_container_mut()\n            .ok_or_eyre(\"there is no container\")?;\n\n        let window = container\n            .remove_focused_window()\n            .ok_or_eyre(\"there is no window\")?;\n\n        if container.windows().is_empty() {\n            self.remove_container_by_idx(focused_container_idx);\n        } else {\n            container.load_focused_window();\n        }\n\n        self.new_container_for_window(window);\n\n        let mut container = Container::default();\n        container.add_window(window);\n        Ok(())\n    }\n\n    pub fn new_container_for_floating_window(&mut self) -> eyre::Result<()> {\n        let focused_idx = self.focused_container_idx();\n        let window = self\n            .remove_focused_floating_window()\n            .ok_or_eyre(\"there is no floating window\")?;\n\n        let mut container = Container::default();\n        container.add_window(window);\n\n        self.insert_container_at_idx(focused_idx, container);\n\n        Ok(())\n    }\n\n    pub fn new_container_for_window(&mut self, window: Window) {\n        let next_idx = if let Some(idx) = self.preselected_container_idx {\n            let next = idx;\n            self.preselected_container_idx = None;\n            self.remove_container_by_idx(next);\n            next\n        } else if self.containers().is_empty() {\n            0\n        } else {\n            self.focused_container_idx() + 1\n        };\n\n        let mut container = Container::default();\n        container.add_window(window);\n\n        self.insert_container_at_idx(next_idx, container);\n    }\n\n    pub fn new_floating_window(&mut self) -> eyre::Result<()> {\n        let window = if let Some(maximized_window) = self.maximized_window {\n            let window = maximized_window;\n            self.maximized_window = None;\n            self.maximized_window_restore_idx = None;\n            window\n        } else if let Some(monocle_container) = &mut self.monocle_container {\n            let window = monocle_container\n                .remove_focused_window()\n                .ok_or_eyre(\"there is no window\")?;\n\n            if monocle_container.windows().is_empty() {\n                self.monocle_container = None;\n                self.monocle_container_restore_idx = None;\n            } else {\n                monocle_container.load_focused_window();\n            }\n\n            window\n        } else {\n            let focused_idx = self.focused_container_idx();\n\n            let container = self\n                .focused_container_mut()\n                .ok_or_eyre(\"there is no container\")?;\n\n            let window = container\n                .remove_focused_window()\n                .ok_or_eyre(\"there is no window\")?;\n\n            if container.windows().is_empty() {\n                self.remove_container_by_idx(focused_idx);\n\n                if focused_idx == self.containers().len() {\n                    self.focus_container(focused_idx.saturating_sub(1));\n                }\n            } else {\n                container.load_focused_window();\n            }\n\n            window\n        };\n\n        self.floating_windows_mut().push_back(window);\n\n        Ok(())\n    }\n\n    fn enforce_resize_constraints(&mut self) {\n        match self.layout {\n            Layout::Default(DefaultLayout::BSP) => self.enforce_resize_constraints_for_bsp(),\n            Layout::Default(DefaultLayout::Columns) => self.enforce_resize_for_columns(),\n            Layout::Default(DefaultLayout::Rows) => self.enforce_resize_for_rows(),\n            Layout::Default(DefaultLayout::VerticalStack) => {\n                self.enforce_resize_for_vertical_stack();\n            }\n            Layout::Default(DefaultLayout::RightMainVerticalStack) => {\n                self.enforce_resize_for_right_vertical_stack();\n            }\n            Layout::Default(DefaultLayout::HorizontalStack) => {\n                self.enforce_resize_for_horizontal_stack();\n            }\n            Layout::Default(DefaultLayout::UltrawideVerticalStack) => {\n                self.enforce_resize_for_ultrawide();\n            }\n            Layout::Default(DefaultLayout::Scrolling) => {\n                self.enforce_resize_for_scrolling();\n            }\n            _ => self.enforce_no_resize(),\n        }\n    }\n\n    fn enforce_resize_constraints_for_bsp(&mut self) {\n        for (i, rect) in self.resize_dimensions.iter_mut().enumerate() {\n            if let Some(rect) = rect {\n                // Even containers can't be resized to the bottom\n                if i % 2 == 0 {\n                    rect.bottom = 0;\n                    // Odd containers can't be resized to the right\n                } else {\n                    rect.right = 0;\n                }\n            }\n        }\n\n        // The first container can never be resized to the left or the top\n        if let Some(Some(first)) = self.resize_dimensions.first_mut() {\n            first.top = 0;\n            first.left = 0;\n        }\n\n        // The last container can never be resized to the bottom or the right\n        if let Some(Some(last)) = self.resize_dimensions.last_mut() {\n            last.bottom = 0;\n            last.right = 0;\n        }\n    }\n\n    fn enforce_resize_for_columns(&mut self) {\n        let resize_dimensions = &mut self.resize_dimensions;\n        match resize_dimensions.len() {\n            0 | 1 => self.enforce_no_resize(),\n            _ => {\n                let len = resize_dimensions.len();\n                for (i, rect) in resize_dimensions.iter_mut().enumerate() {\n                    if let Some(rect) = rect {\n                        rect.top = 0;\n                        rect.bottom = 0;\n\n                        if i == 0 {\n                            rect.left = 0;\n                        }\n                        if i == len - 1 {\n                            rect.right = 0;\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    fn enforce_resize_for_rows(&mut self) {\n        let resize_dimensions = &mut self.resize_dimensions;\n        match resize_dimensions.len() {\n            0 | 1 => self.enforce_no_resize(),\n            _ => {\n                let len = resize_dimensions.len();\n                for (i, rect) in resize_dimensions.iter_mut().enumerate() {\n                    if let Some(rect) = rect {\n                        rect.left = 0;\n                        rect.right = 0;\n\n                        if i == 0 {\n                            rect.top = 0;\n                        }\n                        if i == len - 1 {\n                            rect.bottom = 0;\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    fn enforce_resize_for_vertical_stack(&mut self) {\n        let resize_dimensions = &mut self.resize_dimensions;\n        match resize_dimensions.len() {\n            // Single window can not be resized at all\n            0 | 1 => self.enforce_no_resize(),\n            _ => {\n                // Zero is actually on the left\n                if let Some(left) = resize_dimensions[0].as_mut() {\n                    left.top = 0;\n                    left.bottom = 0;\n                    left.left = 0;\n                }\n\n                // Handle stack on the right\n                let stack_size = resize_dimensions[1..].len();\n                for (i, rect) in resize_dimensions[1..].iter_mut().enumerate() {\n                    if let Some(rect) = rect {\n                        // No containers can resize to the right\n                        rect.right = 0;\n\n                        // First container in stack cant resize up\n                        if i == 0 {\n                            rect.top = 0;\n                        } else if i == stack_size - 1 {\n                            // Last cant be resized to the bottom\n                            rect.bottom = 0;\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    fn enforce_resize_for_right_vertical_stack(&mut self) {\n        let resize_dimensions = &mut self.resize_dimensions;\n        match resize_dimensions.len() {\n            // Single window can not be resized at all\n            0 | 1 => self.enforce_no_resize(),\n            _ => {\n                // Zero is actually on the right\n                if let Some(left) = resize_dimensions[1].as_mut() {\n                    left.top = 0;\n                    left.bottom = 0;\n                    left.right = 0;\n                }\n\n                // Handle stack on the right\n                let stack_size = resize_dimensions[1..].len();\n                for (i, rect) in resize_dimensions[1..].iter_mut().enumerate() {\n                    if let Some(rect) = rect {\n                        // No containers can resize to the left\n                        rect.left = 0;\n\n                        // First container in stack cant resize up\n                        if i == 0 {\n                            rect.top = 0;\n                        } else if i == stack_size - 1 {\n                            // Last cant be resized to the bottom\n                            rect.bottom = 0;\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    fn enforce_resize_for_horizontal_stack(&mut self) {\n        let resize_dimensions = &mut self.resize_dimensions;\n        match resize_dimensions.len() {\n            0 | 1 => self.enforce_no_resize(),\n            _ => {\n                if let Some(left) = resize_dimensions[0].as_mut() {\n                    left.top = 0;\n                    left.left = 0;\n                    left.right = 0;\n                }\n\n                let stack_size = resize_dimensions[1..].len();\n                for (i, rect) in resize_dimensions[1..].iter_mut().enumerate() {\n                    if let Some(rect) = rect {\n                        rect.bottom = 0;\n\n                        if i == 0 {\n                            rect.left = 0;\n                        }\n                        if i == stack_size - 1 {\n                            rect.right = 0;\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    fn enforce_resize_for_ultrawide(&mut self) {\n        let resize_dimensions = &mut self.resize_dimensions;\n        match resize_dimensions.len() {\n            // Single window can not be resized at all\n            0 | 1 => self.enforce_no_resize(),\n            // Two windows can only be resized in the middle\n            2 => {\n                // Zero is actually on the right\n                if let Some(right) = resize_dimensions[0].as_mut() {\n                    right.top = 0;\n                    right.bottom = 0;\n                    right.right = 0;\n                }\n\n                // One is on the left\n                if let Some(left) = resize_dimensions[1].as_mut() {\n                    left.top = 0;\n                    left.bottom = 0;\n                    left.left = 0;\n                }\n            }\n            // Three or more windows means 0 is in center, 1 is at the left, 2.. are a vertical\n            // stack on the right\n            _ => {\n                // Central can be resized left or right\n                if let Some(right) = resize_dimensions[0].as_mut() {\n                    right.top = 0;\n                    right.bottom = 0;\n                }\n\n                // Left one can only be resized to the right\n                if let Some(left) = resize_dimensions[1].as_mut() {\n                    left.top = 0;\n                    left.bottom = 0;\n                    left.left = 0;\n                }\n\n                // Handle stack on the right\n                let stack_size = resize_dimensions[2..].len();\n                for (i, rect) in resize_dimensions[2..].iter_mut().enumerate() {\n                    if let Some(rect) = rect {\n                        // No containers can resize to the right\n                        rect.right = 0;\n\n                        // First container in stack cant resize up\n                        if i == 0 {\n                            rect.top = 0;\n                        } else if i == stack_size - 1 {\n                            // Last cant be resized to the bottom\n                            rect.bottom = 0;\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    fn enforce_resize_for_scrolling(&mut self) {\n        let resize_dimensions = &mut self.resize_dimensions;\n        match resize_dimensions.len() {\n            0 | 1 => self.enforce_no_resize(),\n            _ => {\n                let len = resize_dimensions.len();\n\n                for (i, rect) in resize_dimensions.iter_mut().enumerate() {\n                    if let Some(rect) = rect {\n                        rect.top = 0;\n                        rect.bottom = 0;\n\n                        if i == 0 {\n                            rect.left = 0;\n                        } else if i == len - 1 {\n                            rect.right = 0;\n                        }\n                    }\n                }\n            }\n        }\n    }\n    fn enforce_no_resize(&mut self) {\n        for rect in self.resize_dimensions.iter_mut().flatten() {\n            rect.left = 0;\n            rect.right = 0;\n            rect.top = 0;\n            rect.bottom = 0;\n        }\n    }\n\n    pub fn new_monocle_container(&mut self) -> eyre::Result<()> {\n        let focused_idx = self.focused_container_idx();\n\n        // we shouldn't use remove_container_by_idx here because it doesn't make sense for\n        // monocle and maximized toggles which take over the whole screen before being reinserted\n        // at the same index to respect locked container indexes\n        let container = self\n            .containers_mut()\n            .remove(focused_idx)\n            .ok_or_eyre(\"there is no container\")?;\n\n        // We don't remove any resize adjustments for a monocle, because when this container is\n        // inevitably reintegrated, it would be weird if it doesn't go back to the dimensions\n        // it had before\n\n        self.monocle_container = Option::from(container);\n        self.monocle_container_restore_idx = Option::from(focused_idx);\n        self.focus_previous_container();\n\n        self.monocle_container\n            .as_mut()\n            .ok_or_eyre(\"there is no monocle container\")?\n            .load_focused_window();\n\n        Ok(())\n    }\n\n    pub fn reintegrate_monocle_container(&mut self) -> eyre::Result<()> {\n        let restore_idx = self\n            .monocle_container_restore_idx\n            .ok_or_eyre(\"there is no monocle restore index\")?;\n\n        let container = self\n            .monocle_container\n            .as_ref()\n            .ok_or_eyre(\"there is no monocle container\")?;\n\n        let container = container.clone();\n        if restore_idx >= self.containers().len() {\n            self.containers_mut()\n                .resize(restore_idx, Container::default());\n        }\n\n        // we shouldn't use insert_container_at_index here because it doesn't make sense for\n        // monocle and maximized toggles which take over the whole screen before being reinserted\n        // at the same index to respect locked container indexes\n        self.containers_mut().insert(restore_idx, container);\n        self.focus_container(restore_idx);\n        self.focused_container_mut()\n            .ok_or_eyre(\"there is no container\")?\n            .load_focused_window();\n\n        self.monocle_container = None;\n        self.monocle_container_restore_idx = None;\n\n        Ok(())\n    }\n\n    pub fn cycle_monocle_container(&mut self, direction: CycleDirection) -> eyre::Result<()> {\n        if self.containers().is_empty() {\n            return Ok(());\n        }\n\n        self.reintegrate_monocle_container()?;\n\n        let new_idx = self\n            .new_idx_for_cycle_direction(direction)\n            .ok_or_eyre(\"there is no container to cycle monocle to\")?;\n\n        self.focus_container(new_idx);\n        self.new_monocle_container()?;\n\n        Ok(())\n    }\n\n    pub fn new_maximized_window(&mut self) -> eyre::Result<()> {\n        let focused_idx = self.focused_container_idx();\n\n        if matches!(self.layer, WorkspaceLayer::Floating) {\n            let floating_window_idx = self.focused_floating_window_idx();\n            let floating_window = self.floating_windows_mut().remove(floating_window_idx);\n            self.maximized_window = floating_window;\n            self.maximized_window_restore_idx = Option::from(focused_idx);\n            if let Some(window) = self.maximized_window {\n                window.maximize();\n            }\n\n            return Ok(());\n        }\n\n        let monocle_restore_idx = self.monocle_container_restore_idx;\n        if let Some(monocle_container) = &mut self.monocle_container {\n            let window = monocle_container\n                .remove_focused_window()\n                .ok_or_eyre(\"there is no window\")?;\n\n            if monocle_container.windows().is_empty() {\n                self.monocle_container = None;\n                self.monocle_container_restore_idx = None;\n            } else {\n                monocle_container.load_focused_window();\n            }\n\n            self.maximized_window = Option::from(window);\n            self.maximized_window_restore_idx = monocle_restore_idx;\n            if let Some(window) = self.maximized_window {\n                window.maximize();\n            }\n\n            return Ok(());\n        }\n\n        let container = self\n            .focused_container_mut()\n            .ok_or_eyre(\"there is no container\")?;\n\n        let window = container\n            .remove_focused_window()\n            .ok_or_eyre(\"there is no window\")?;\n\n        if container.windows().is_empty() {\n            // we shouldn't use remove_container_by_idx here because it doesn't make sense for\n            // monocle and maximized toggles which take over the whole screen before being reinserted\n            // at the same index to respect locked container indexes\n            self.containers_mut().remove(focused_idx);\n            if self.resize_dimensions.get(focused_idx).is_some() {\n                self.resize_dimensions.remove(focused_idx);\n            }\n        } else {\n            container.load_focused_window();\n        }\n\n        self.maximized_window = Option::from(window);\n        self.maximized_window_restore_idx = Option::from(focused_idx);\n\n        if let Some(window) = self.maximized_window {\n            window.maximize();\n        }\n\n        self.focus_previous_container();\n\n        Ok(())\n    }\n\n    pub fn reintegrate_maximized_window(&mut self) -> eyre::Result<()> {\n        let restore_idx = self\n            .maximized_window_restore_idx\n            .ok_or_eyre(\"there is no monocle restore index\")?;\n\n        let window = self\n            .maximized_window\n            .as_ref()\n            .ok_or_eyre(\"there is no monocle container\")?;\n\n        let window = *window;\n        if !self.containers().is_empty() && restore_idx > self.containers().len().saturating_sub(1)\n        {\n            self.containers_mut()\n                .resize(restore_idx, Container::default());\n        }\n\n        let mut container = Container::default();\n        container.windows_mut().push_back(window);\n\n        // we shouldn't use insert_container_at_index here because it doesn't make sense for\n        // monocle and maximized toggles which take over the whole screen before being reinserted\n        // at the same index to respect locked container indexes\n        self.containers_mut().insert(restore_idx, container);\n        self.focus_container(restore_idx);\n\n        self.focused_container_mut()\n            .ok_or_eyre(\"there is no container\")?\n            .load_focused_window();\n\n        self.maximized_window = None;\n        self.maximized_window_restore_idx = None;\n\n        Ok(())\n    }\n\n    #[tracing::instrument(skip(self))]\n    pub fn focus_container(&mut self, idx: usize) {\n        tracing::info!(\"focusing container\");\n\n        self.containers.focus(idx);\n    }\n\n    pub fn swap_containers(&mut self, i: usize, j: usize) {\n        self.containers.elements_mut().swap_respecting_locks(i, j);\n        self.focus_container(j);\n    }\n\n    pub fn remove_focused_floating_window(&mut self) -> Option<Window> {\n        let hwnd = WindowsApi::foreground_window().ok()?;\n\n        let mut idx = None;\n        for (i, window) in self.floating_windows().iter().enumerate() {\n            if hwnd == window.hwnd {\n                idx = Option::from(i);\n            }\n        }\n\n        match idx {\n            None => None,\n            Some(idx) => {\n                if self.floating_windows().get(idx).is_some() {\n                    self.floating_windows_mut().remove(idx)\n                } else {\n                    None\n                }\n            }\n        }\n    }\n\n    pub fn visible_windows(&self) -> Vec<Option<&Window>> {\n        let mut vec = vec![];\n\n        vec.push(self.maximized_window.as_ref());\n\n        if let Some(monocle) = &self.monocle_container {\n            vec.push(monocle.focused_window());\n        }\n\n        for container in self.containers() {\n            vec.push(container.focused_window());\n        }\n\n        for window in self.floating_windows() {\n            vec.push(Some(window));\n        }\n\n        vec\n    }\n\n    pub fn visible_window_details(&self) -> Vec<WindowDetails> {\n        let mut vec: Vec<WindowDetails> = vec![];\n\n        if let Some(maximized) = self.maximized_window\n            && let Ok(details) = (maximized).try_into()\n        {\n            vec.push(details);\n        }\n\n        if let Some(monocle) = &self.monocle_container\n            && let Some(focused) = monocle.focused_window()\n            && let Ok(details) = (*focused).try_into()\n        {\n            vec.push(details);\n        }\n\n        for container in self.containers() {\n            if let Some(focused) = container.focused_window()\n                && let Ok(details) = (*focused).try_into()\n            {\n                vec.push(details);\n            }\n        }\n\n        for window in self.floating_windows() {\n            if let Ok(details) = (*window).try_into() {\n                vec.push(details);\n            }\n        }\n\n        vec\n    }\n\n    pub fn focus_previous_container(&mut self) {\n        let focused_idx = self.focused_container_idx();\n        self.focus_container(focused_idx.saturating_sub(1));\n    }\n\n    fn focus_last_container(&mut self) {\n        self.focus_container(self.containers().len().saturating_sub(1));\n    }\n\n    fn focus_first_container(&mut self) {\n        self.focus_container(0);\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::Window;\n    use crate::container::Container;\n    use std::collections::HashMap;\n\n    #[test]\n    fn test_locked_containers_with_new_window() {\n        let mut ws = Workspace::default();\n\n        let mut state = HashMap::new();\n\n        // add 4 containers\n        for i in 0..4 {\n            let mut container = Container::default();\n            if i == 3 {\n                container.locked = true; // set index 3 locked\n            }\n            state.insert(i, container.id.to_string());\n            ws.add_container_to_back(container);\n        }\n        assert_eq!(ws.containers().len(), 4);\n\n        // focus container at index 2\n        ws.focus_container(2);\n\n        // simulate a new window being launched on this workspace\n        ws.new_container_for_window(Window::from(123));\n\n        // new length should be 5, with the focus on the new window at index 4\n        assert_eq!(ws.containers().len(), 5);\n        assert_eq!(ws.focused_container_idx(), 4);\n        assert_eq!(\n            ws.focused_container()\n                .unwrap()\n                .focused_window()\n                .unwrap()\n                .hwnd,\n            123\n        );\n\n        // when inserting a new container at index 0, index 3's container should not change\n        ws.focus_container(0);\n        ws.new_container_for_window(Window::from(234));\n        assert_eq!(\n            ws.containers()[3].id.to_string(),\n            state.get(&3).unwrap().to_string()\n        );\n    }\n\n    #[test]\n    fn test_locked_containers_remove_window() {\n        let mut ws = Workspace::default();\n\n        // add 4 containers\n        for i in 0..4 {\n            let mut container = Container::default();\n            container.windows_mut().push_back(Window::from(i));\n            if i == 1 {\n                container.locked = true;\n            }\n            ws.add_container_to_back(container);\n        }\n        assert_eq!(ws.containers().len(), 4);\n\n        ws.remove_window(0).unwrap();\n        assert_eq!(ws.containers()[0].focused_window().unwrap().hwnd, 2);\n        // index 1 should still be the same\n        assert_eq!(ws.containers()[1].focused_window().unwrap().hwnd, 1);\n        assert_eq!(ws.containers()[2].focused_window().unwrap().hwnd, 3);\n    }\n\n    #[test]\n    fn test_locked_containers_toggle_float() {\n        let mut ws = Workspace::default();\n\n        // add 4 containers\n        for i in 0..4 {\n            let mut container = Container::default();\n            container.windows_mut().push_back(Window::from(i));\n            if i == 1 {\n                container.locked = true;\n            }\n            ws.add_container_to_back(container);\n        }\n        assert_eq!(ws.containers().len(), 4);\n\n        // set index 0 focused\n        ws.focus_container(0);\n\n        // float index 0\n        ws.new_floating_window().unwrap();\n\n        assert_eq!(ws.containers()[0].focused_window().unwrap().hwnd, 2);\n        // index 1 should still be the same\n        assert_eq!(ws.containers()[1].focused_window().unwrap().hwnd, 1);\n        assert_eq!(ws.containers()[2].focused_window().unwrap().hwnd, 3);\n\n        // unfloat - have to do this semi-manually becuase of calls to WindowsApi in\n        // new_container_for_floating_window which usually handles unfloating\n        let window = ws.floating_windows_mut().pop_back().unwrap();\n        let mut container = Container::default();\n        container.add_window(window);\n        ws.insert_container_at_idx(ws.focused_container_idx(), container);\n\n        // all indexes should be at their original position\n        for i in 0..4 {\n            assert_eq!(\n                ws.containers()[i].focused_window().unwrap().hwnd,\n                i as isize\n            );\n        }\n    }\n\n    #[test]\n    fn test_locked_containers_stack() {\n        let mut ws = Workspace::default();\n\n        // add 6 containers\n        for i in 0..6 {\n            let mut container = Container::default();\n            container.windows_mut().push_back(Window::from(i));\n            if i == 4 {\n                container.locked = true;\n            }\n            ws.add_container_to_back(container);\n        }\n        assert_eq!(ws.containers().len(), 6);\n\n        // set index 3 focused\n        ws.focus_container(3);\n\n        // stack index 3 on top of index 2\n        ws.move_window_to_container(2).unwrap();\n\n        assert_eq!(ws.containers()[0].focused_window().unwrap().hwnd, 0);\n        assert_eq!(ws.containers()[1].focused_window().unwrap().hwnd, 1);\n        assert_eq!(ws.containers()[2].windows().len(), 2);\n        assert_eq!(ws.containers()[3].focused_window().unwrap().hwnd, 5);\n        // index 4 should still be the same\n        assert_eq!(ws.containers()[4].focused_window().unwrap().hwnd, 4);\n\n        // unstack\n        ws.new_container_for_focused_window().unwrap();\n\n        // all indexes should be at their original position\n        for i in 0..6 {\n            assert_eq!(\n                ws.containers()[i].focused_window().unwrap().hwnd,\n                i as isize\n            )\n        }\n    }\n\n    #[test]\n    fn test_contains_window() {\n        // Create default workspace\n        let mut workspace = Workspace::default();\n\n        // Add a window to the container\n        let mut container = Container::default();\n        container.windows_mut().push_back(Window::from(0));\n\n        // Add container\n        workspace.add_container_to_back(container);\n\n        // Should be true\n        assert!(workspace.contains_window(0));\n\n        // Should be false\n        assert!(!workspace.is_empty())\n    }\n\n    #[test]\n    fn test_add_container_to_back() {\n        let mut workspace = Workspace::default();\n\n        {\n            // Container with 3 windows\n            let mut container = Container::default();\n            for i in 0..3 {\n                container.windows_mut().push_back(Window::from(i));\n            }\n            workspace.add_container_to_back(container);\n        }\n\n        {\n            // Container with 1 window\n            let mut container = Container::default();\n            container.windows_mut().push_back(Window::from(1));\n            workspace.add_container_to_back(container);\n        }\n        // Should have 2 containers\n        assert_eq!(workspace.containers().len(), 2);\n\n        // Get focused container. Should be the index of the last container added\n        let container = workspace.focused_container_mut().unwrap();\n\n        // Should be focused on the container with 1 window\n        assert_eq!(container.windows().len(), 1);\n    }\n\n    #[test]\n    fn test_add_container_to_front() {\n        let mut workspace = Workspace::default();\n\n        {\n            // Container with 1 window\n            let mut container = Container::default();\n            container.windows_mut().push_back(Window::from(1));\n            workspace.add_container_to_front(container);\n        }\n\n        {\n            // Container with 3 windows\n            let mut container = Container::default();\n            for i in 0..3 {\n                container.windows_mut().push_back(Window::from(i));\n            }\n            workspace.add_container_to_front(container);\n        }\n        // Should have 2 containers\n        assert_eq!(workspace.containers().len(), 2);\n\n        // Get focused container. Should be the index of the last container added\n        let container = workspace.focused_container_mut().unwrap();\n\n        // Should be focused on the container with 3 windows\n        assert_eq!(container.windows().len(), 3);\n    }\n\n    #[test]\n    fn test_remove_non_existent_window() {\n        let mut workspace = Workspace::default();\n\n        {\n            // Add a container with one window\n            let mut container = Container::default();\n            container.windows_mut().push_back(Window::from(1));\n            workspace.add_container_to_back(container);\n        }\n\n        // Attempt to remove a non-existent window\n        let result = workspace.remove_window(2);\n\n        // Should return an error\n        assert!(\n            result.is_err(),\n            \"Expected an error when removing a non-existent window\"\n        );\n\n        // Get focused container. Should be the index of the last container added\n        let container = workspace.focused_container_mut().unwrap();\n\n        // Should still have 1 window\n        assert_eq!(container.windows().len(), 1);\n    }\n\n    #[test]\n    fn test_remove_focused_container() {\n        let mut workspace = Workspace::default();\n\n        {\n            // Container with 1 window\n            let mut container = Container::default();\n            container.windows_mut().push_back(Window::from(1));\n            workspace.add_container_to_back(container);\n        }\n\n        {\n            // Container with 1 window\n            let mut container = Container::default();\n            container.windows_mut().push_back(Window::from(1));\n            workspace.add_container_to_back(container);\n        }\n        // Should have 2 containers\n        assert_eq!(workspace.containers().len(), 2);\n\n        // Should be focused on the container at index 1\n        assert_eq!(workspace.focused_container_idx(), 1);\n\n        // Store the container at index 1 before removal\n        let container_to_remove = workspace.containers().get(1).cloned();\n        workspace.remove_focused_container();\n\n        // Should only have 1 container\n        assert_eq!(workspace.containers().len(), 1);\n\n        // Should be focused on the container at index 0\n        assert_eq!(workspace.focused_container_idx(), 0);\n\n        // Ensure the container at index 1 before removal is no longer present\n        assert!(container_to_remove.is_some());\n        assert!(\n            !workspace\n                .containers()\n                .contains(&container_to_remove.unwrap())\n        );\n    }\n\n    #[test]\n    fn test_insert_container_at_idx() {\n        let mut workspace = Workspace::default();\n\n        for i in 0..4 {\n            let mut container = Container::default();\n            container.windows_mut().push_back(Window::from(i));\n            workspace.add_container_to_back(container);\n        }\n\n        // Should have 4 containers\n        assert_eq!(workspace.containers().len(), 4);\n\n        // Should be focused on the last container\n        assert_eq!(workspace.focused_container_idx(), 3);\n\n        // Insert a container at index 4\n        workspace.insert_container_at_idx(4, Container::default());\n\n        // Should have 5 containers\n        assert_eq!(workspace.containers().len(), 5);\n\n        // Should be focused on the newly inserted container\n        assert_eq!(workspace.focused_container_idx(), 4);\n    }\n\n    #[test]\n    fn test_remove_container_by_idx() {\n        let mut workspace = Workspace::default();\n\n        for i in 0..3 {\n            let mut container = Container::default();\n            container.windows_mut().push_back(Window::from(i));\n            workspace.add_container_to_back(container);\n        }\n\n        // Should have 3 containers\n        assert_eq!(workspace.containers().len(), 3);\n\n        // Should be focused on the last container\n        assert_eq!(workspace.focused_container_idx(), 2);\n\n        // Store the container at index 1 before removal\n        let container_to_remove = workspace.containers().get(1).cloned();\n\n        // Remove the container at index 1\n        workspace.remove_container_by_idx(1);\n\n        // Should have 2 containers\n        assert_eq!(workspace.containers().len(), 2);\n\n        // Ensure the container at index 1 before removal is no longer present\n        assert!(container_to_remove.is_some());\n        assert!(\n            !workspace\n                .containers()\n                .contains(&container_to_remove.unwrap())\n        );\n    }\n\n    #[test]\n    fn test_remove_container() {\n        let mut workspace = Workspace::default();\n\n        for i in 0..3 {\n            let mut container = Container::default();\n            container.windows_mut().push_back(Window::from(i));\n            workspace.add_container_to_back(container);\n        }\n\n        // Should have 3 containers\n        assert_eq!(workspace.containers().len(), 3);\n\n        // Should be focused on the last container\n        assert_eq!(workspace.focused_container_idx(), 2);\n\n        // Store the container at index 2 before removal\n        let container_to_remove = workspace.containers().get(2).cloned();\n\n        // Remove the container at index 2\n        workspace.remove_container(2);\n\n        // Should be focused on the previous container which is index 1\n        assert_eq!(workspace.focused_container_idx(), 1);\n\n        // Should have 2 containers\n        assert_eq!(workspace.containers().len(), 2);\n\n        // Ensure the container at index 1 before removal is no longer present\n        assert!(container_to_remove.is_some());\n        assert!(\n            !workspace\n                .containers()\n                .contains(&container_to_remove.unwrap())\n        );\n    }\n\n    #[test]\n    fn test_focus_container() {\n        let mut workspace = Workspace::default();\n\n        for i in 0..3 {\n            let mut container = Container::default();\n            container.windows_mut().push_back(Window::from(i));\n            workspace.add_container_to_back(container);\n        }\n\n        // Should have 3 containers\n        assert_eq!(workspace.containers().len(), 3);\n\n        // Should be focused on the last container\n        assert_eq!(workspace.focused_container_idx(), 2);\n\n        // Focus on container 1\n        workspace.focus_container(1);\n        assert_eq!(workspace.focused_container_idx(), 1);\n\n        // Focus on container 0\n        workspace.focus_container(0);\n        assert_eq!(workspace.focused_container_idx(), 0);\n\n        // Focus on container 2\n        workspace.focus_container(2);\n        assert_eq!(workspace.focused_container_idx(), 2);\n    }\n\n    #[test]\n    fn test_focus_previous_container() {\n        let mut workspace = Workspace::default();\n\n        for i in 0..3 {\n            let mut container = Container::default();\n            container.windows_mut().push_back(Window::from(i));\n            workspace.add_container_to_back(container);\n        }\n\n        // Should have 3 containers\n        assert_eq!(workspace.containers().len(), 3);\n\n        // Should be focused on the last container\n        assert_eq!(workspace.focused_container_idx(), 2);\n\n        // Focus on the previous container\n        workspace.focus_previous_container();\n\n        // Should be focused on container 1\n        assert_eq!(workspace.focused_container_idx(), 1);\n    }\n\n    #[test]\n    fn test_focus_last_container() {\n        let mut workspace = Workspace::default();\n\n        for i in 0..3 {\n            let mut container = Container::default();\n            container.windows_mut().push_back(Window::from(i));\n            workspace.add_container_to_back(container);\n        }\n\n        // Should have 3 containers\n        assert_eq!(workspace.containers().len(), 3);\n\n        // Change focus to the first container for the test\n        workspace.focus_container(0);\n        assert_eq!(workspace.focused_container_idx(), 0);\n\n        // Focus on the last container\n        workspace.focus_last_container();\n\n        // Should be focused on container 1\n        assert_eq!(workspace.focused_container_idx(), 2);\n    }\n\n    #[test]\n    fn test_focus_first_container() {\n        let mut workspace = Workspace::default();\n\n        for i in 0..3 {\n            let mut container = Container::default();\n            container.windows_mut().push_back(Window::from(i));\n            workspace.add_container_to_back(container);\n        }\n\n        // Should have 3 containers\n        assert_eq!(workspace.containers().len(), 3);\n\n        // Should be focused on the last container\n        assert_eq!(workspace.focused_container_idx(), 2);\n\n        // Focus on the first container\n        workspace.focus_first_container();\n\n        // Should be focused on container 1\n        assert_eq!(workspace.focused_container_idx(), 0);\n    }\n\n    #[test]\n    fn test_swap_containers() {\n        let mut workspace = Workspace::default();\n\n        {\n            let mut container = Container::default();\n            for i in 0..3 {\n                container.windows_mut().push_back(Window::from(i));\n            }\n            workspace.add_container_to_back(container);\n        }\n\n        {\n            let mut container = Container::default();\n            container.windows_mut().push_back(Window::from(1));\n            workspace.add_container_to_back(container);\n        }\n\n        // Should have 2 containers\n        assert_eq!(workspace.containers().len(), 2);\n\n        {\n            // Should be focused on container 1\n            assert_eq!(workspace.focused_container_idx(), 1);\n\n            // Should have 1 window\n            let container = workspace.focused_container_mut().unwrap();\n            assert_eq!(container.windows().len(), 1);\n        }\n\n        // Swap containers 0 and 1\n        workspace.swap_containers(0, 1);\n\n        {\n            // Should be focused on container 0\n            assert_eq!(workspace.focused_container_idx(), 1);\n\n            let container = workspace.focused_container_mut().unwrap();\n            assert_eq!(container.windows().len(), 3);\n        }\n    }\n\n    #[test]\n    fn test_new_container_for_window() {\n        let mut workspace = Workspace::default();\n\n        {\n            let mut container = Container::default();\n            container.windows_mut().push_back(Window::from(1));\n            workspace.add_container_to_back(container);\n        }\n\n        // Add new window to container\n        workspace.new_container_for_window(Window::from(2));\n\n        // Container 0 should have 1 window\n        let container = workspace.focused_container_mut().unwrap();\n        assert_eq!(container.windows().len(), 1);\n\n        // Should return true that window 2 exists\n        assert!(workspace.contains_window(2));\n    }\n\n    #[test]\n    fn test_move_window_to_container() {\n        let mut workspace = Workspace::default();\n\n        {\n            // Container with 0 windows\n            let container = Container::default();\n            workspace.add_container_to_back(container);\n        }\n\n        {\n            // Container with 3 windows\n            let mut container = Container::default();\n            for i in 0..3 {\n                container.windows_mut().push_back(Window::from(i));\n            }\n            workspace.add_container_to_back(container);\n        }\n\n        // Move A Window from container 1 to container 0\n        workspace.move_window_to_container(0).unwrap();\n\n        // Focus on container 0\n        workspace.focus_container(0);\n\n        // Container 0 should have 1 window\n        let container = workspace.focused_container_mut().unwrap();\n        assert_eq!(container.windows().len(), 1);\n    }\n\n    #[test]\n    fn test_move_window_to_non_existent_container() {\n        let mut workspace = Workspace::default();\n\n        // Add a container with one window\n        let mut container = Container::default();\n        container.windows_mut().push_back(Window::from(1));\n        workspace.add_container_to_back(container);\n\n        // Try to move window to a non-existent container\n        let result = workspace.move_window_to_container(8);\n\n        // Should return an error\n        assert!(\n            result.is_err(),\n            \"Expected an error when moving a window to a non-existent container\"\n        );\n    }\n\n    #[test]\n    fn test_remove_window() {\n        let mut workspace = Workspace::default();\n\n        {\n            // Container with 1 window\n            let mut container = Container::default();\n            for i in 0..3 {\n                container.windows_mut().push_back(Window::from(i));\n            }\n            workspace.add_container_to_back(container);\n        }\n\n        // Remove window 1\n        workspace.remove_window(1).ok();\n\n        // Should have 2 windows\n        let container = workspace.focused_container_mut().unwrap();\n        assert_eq!(container.windows().len(), 2);\n\n        // Check that window 1 is removed\n        assert!(!workspace.contains_window(1));\n    }\n\n    #[test]\n    fn test_new_container_for_focused_window() {\n        let mut workspace = Workspace::default();\n\n        {\n            // Container with 1 window\n            let mut container = Container::default();\n            for i in 0..3 {\n                container.windows_mut().push_back(Window::from(i));\n            }\n            workspace.add_container_to_back(container);\n        }\n\n        // Add focused window to new container\n        workspace.new_container_for_focused_window().ok();\n\n        // Should have 2 containers\n        assert_eq!(workspace.containers().len(), 2);\n\n        {\n            // Inspect new container. Should contain 1 window. Window name should be 0\n            workspace.focus_container(1);\n            let container = workspace.focused_container_mut().unwrap();\n            assert_eq!(container.windows().len(), 1);\n            assert!(workspace.contains_window(0));\n        }\n    }\n\n    #[test]\n    fn test_focus_container_by_window() {\n        let mut workspace = Workspace::default();\n\n        {\n            // Container with 3 windows\n            let mut container = Container::default();\n            for i in 0..3 {\n                container.windows_mut().push_back(Window::from(i));\n            }\n            workspace.add_container_to_back(container);\n        }\n\n        {\n            // Container with 1 window\n            let mut container = Container::default();\n            container.windows_mut().push_back(Window::from(4));\n            workspace.add_container_to_back(container);\n        }\n\n        // Focus container by window\n        workspace.focus_container_by_window(1).unwrap();\n\n        // Should be focused on workspace 0\n        assert_eq!(workspace.focused_container_idx(), 0);\n\n        // Should be focused on window 1 and hwnd should be 1\n        let focused_container = workspace.focused_container_mut().unwrap();\n        assert_eq!(\n            focused_container.focused_window(),\n            Some(&Window { hwnd: 1 })\n        );\n        assert_eq!(focused_container.focused_window_idx(), 1);\n    }\n\n    #[test]\n    fn test_contains_managed_window() {\n        let mut workspace = Workspace::default();\n\n        {\n            // Container with 3 windows\n            let mut container = Container::default();\n            for i in 0..3 {\n                container.windows_mut().push_back(Window::from(i));\n            }\n            workspace.add_container_to_back(container);\n        }\n\n        {\n            // Container with 1 window\n            let mut container = Container::default();\n            container.windows_mut().push_back(Window::from(4));\n            workspace.add_container_to_back(container);\n        }\n\n        // Should return true, window is in container 1\n        assert!(workspace.contains_managed_window(4));\n\n        // Should return true, all the windows are in container 0\n        for i in 0..3 {\n            assert!(workspace.contains_managed_window(i));\n        }\n\n        // Should return false since window was never added\n        assert!(!workspace.contains_managed_window(5));\n    }\n\n    #[test]\n    fn test_new_floating_window() {\n        let mut workspace = Workspace::default();\n\n        {\n            // Container with 3 windows\n            let mut container = Container::default();\n            for i in 0..3 {\n                container.windows_mut().push_back(Window::from(i));\n            }\n            workspace.add_container_to_back(container);\n        }\n\n        // Add window to floating_windows\n        workspace.new_floating_window().ok();\n\n        // Should have 1 floating window\n        assert_eq!(workspace.floating_windows().len(), 1);\n\n        // Should have only 2 windows now\n        let container = workspace.focused_container_mut().unwrap();\n        assert_eq!(container.windows().len(), 2);\n\n        // Should contain hwnd 0 since this is the first window in the container\n        let floating_windows = workspace.floating_windows_mut();\n        assert!(floating_windows.contains(&Window { hwnd: 0 }));\n    }\n\n    #[test]\n    fn test_visible_windows() {\n        let mut workspace = Workspace::default();\n\n        {\n            // Create and add a default Container with 2 windows\n            let mut container = Container::default();\n            container.windows_mut().push_back(Window::from(100));\n            container.windows_mut().push_back(Window::from(200));\n            workspace.add_container_to_back(container);\n        }\n\n        {\n            // visible_windows should return None and 100\n            let visible_windows = workspace.visible_windows();\n            assert_eq!(visible_windows.len(), 2);\n            assert!(visible_windows[0].is_none());\n            assert_eq!(visible_windows[1].unwrap().hwnd, 100);\n        }\n\n        {\n            // Create and add a default Container with 1 window\n            let mut container = Container::default();\n            container.windows_mut().push_back(Window::from(300));\n            workspace.add_container_to_back(container);\n        }\n\n        {\n            // visible_windows should return None, 100, and 300\n            let visible_windows = workspace.visible_windows();\n            assert_eq!(visible_windows.len(), 3);\n            assert!(visible_windows[0].is_none());\n            assert_eq!(visible_windows[1].unwrap().hwnd, 100);\n            assert_eq!(visible_windows[2].unwrap().hwnd, 300);\n        }\n\n        // Maximize window 200\n        workspace.maximized_window = Some(Window { hwnd: 200 });\n\n        {\n            // visible_windows should return 200, 100, and 300\n            let visible_windows = workspace.visible_windows();\n            assert_eq!(visible_windows.len(), 3);\n            assert_eq!(visible_windows[0].unwrap().hwnd, 200);\n            assert_eq!(visible_windows[1].unwrap().hwnd, 100);\n            assert_eq!(visible_windows[2].unwrap().hwnd, 300);\n        }\n    }\n}\n"
  },
  {
    "path": "komorebi-bar/Cargo.toml",
    "content": "[package]\nname = \"komorebi-bar\"\nversion = \"0.1.41\"\nedition = \"2024\"\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\nkomorebi-client = { path = \"../komorebi-client\", default-features = false }\nkomorebi-themes = { path = \"../komorebi-themes\", default-features = false }\n\nchrono-tz = { workspace = true }\nchrono = { workspace = true }\nclap = { workspace = true }\ncolor-eyre = { workspace = true }\ncrossbeam-channel = { workspace = true }\ndirs = { workspace = true }\ndunce = { workspace = true }\neframe = { workspace = true }\negui-phosphor = { git = \"https://github.com/amPerl/egui-phosphor\", rev = \"d13688738478ecd12b426e3e74c59d6577a85b59\" }\nfont-loader = \"0.11\"\nhotwatch = { workspace = true }\nimage = \"0.25\"\nlazy_static = { workspace = true }\nnetdev = \"0.40\"\nnum = \"0.4\"\nnum-derive = \"0.4\"\nnum-traits = \"0.2\"\nparking_lot = { workspace = true }\nrandom_word = { version = \"0.5\", features = [\"en\"] }\nreqwest = { version = \"0.12\", features = [\"blocking\"] }\nschemars = { workspace = true, optional = true }\nserde = { workspace = true }\nserde_json = { workspace = true }\nstarship-battery = \"0.10\"\nsysinfo = { workspace = true }\ntracing = { workspace = true }\ntracing-subscriber = { workspace = true }\nwhich = { workspace = true }\nwindows = { workspace = true }\nwindows-core = { workspace = true }\nwindows-icons = { git = \"https://github.com/LGUG2Z/windows-icons\", rev = \"0c9d7ee1b807347c507d3a9862dd007b4d3f4354\" }\nwindows-icons-fallback = { package = \"windows-icons\", git = \"https://github.com/LGUG2Z/windows-icons\", rev = \"d67cc9920aa9b4883393e411fb4fa2ddd4c498b5\" }\n\n[features]\ndefault = [\"schemars\"]\nschemars = [\n  \"dep:schemars\",\n  \"komorebi-client/default\",\n  \"komorebi-themes/default\",\n]\n"
  },
  {
    "path": "komorebi-bar/src/bar.rs",
    "content": "use crate::AUTO_SELECT_FILL_COLOUR;\nuse crate::AUTO_SELECT_TEXT_COLOUR;\nuse crate::BAR_HEIGHT;\nuse crate::DEFAULT_PADDING;\nuse crate::KomorebiEvent;\nuse crate::MAX_LABEL_WIDTH;\nuse crate::MONITOR_LEFT;\nuse crate::MONITOR_RIGHT;\nuse crate::MONITOR_TOP;\nuse crate::config::KomobarConfig;\nuse crate::config::KomobarTheme;\nuse crate::config::MonitorConfigOrIndex;\nuse crate::config::Position;\nuse crate::config::PositionConfig;\nuse crate::config::get_individual_spacing;\nuse crate::process_hwnd;\nuse crate::render::Color32Ext;\nuse crate::render::Grouping;\nuse crate::render::RenderConfig;\nuse crate::render::RenderExt;\nuse crate::widgets::komorebi::Komorebi;\nuse crate::widgets::komorebi::MonitorInfo;\nuse crate::widgets::widget::BarWidget;\nuse crate::widgets::widget::WidgetConfig;\nuse color_eyre::eyre;\nuse crossbeam_channel::Receiver;\nuse crossbeam_channel::TryRecvError;\nuse eframe::egui::Align;\nuse eframe::egui::Align2;\nuse eframe::egui::Area;\nuse eframe::egui::CentralPanel;\nuse eframe::egui::Color32;\nuse eframe::egui::Context;\nuse eframe::egui::FontData;\nuse eframe::egui::FontDefinitions;\nuse eframe::egui::FontFamily;\nuse eframe::egui::FontId;\nuse eframe::egui::Frame;\nuse eframe::egui::Id;\nuse eframe::egui::Layout;\nuse eframe::egui::Margin;\nuse eframe::egui::PointerButton;\nuse eframe::egui::Rgba;\nuse eframe::egui::Style;\nuse eframe::egui::TextStyle;\nuse eframe::egui::Vec2;\nuse eframe::egui::Visuals;\nuse font_loader::system_fonts;\nuse font_loader::system_fonts::FontPropertyBuilder;\nuse komorebi_client::Colour;\nuse komorebi_client::MonitorNotification;\nuse komorebi_client::NotificationEvent;\nuse komorebi_client::PathExt;\nuse komorebi_client::SocketMessage;\nuse komorebi_client::VirtualDesktopNotification;\nuse komorebi_themes::Base16Wrapper;\nuse komorebi_themes::Catppuccin;\nuse komorebi_themes::KomobarThemeBase16;\nuse komorebi_themes::KomobarThemeCatppuccin;\nuse komorebi_themes::KomobarThemeCustom;\nuse komorebi_themes::catppuccin_egui;\nuse lazy_static::lazy_static;\nuse parking_lot::Mutex;\nuse std::cell::RefCell;\nuse std::collections::HashMap;\nuse std::io::Error;\nuse std::io::ErrorKind;\nuse std::io::Write;\nuse std::os::windows::process::CommandExt;\nuse std::path::PathBuf;\nuse std::process::ChildStdin;\nuse std::process::Command;\nuse std::process::Stdio;\nuse std::rc::Rc;\nuse std::sync::Arc;\nuse std::sync::atomic::Ordering;\n\nconst CREATE_NO_WINDOW: u32 = 0x0800_0000;\n\nlazy_static! {\n    static ref SESSION_STDIN: Mutex<Option<ChildStdin>> = Mutex::new(None);\n}\n\nfn start_powershell() -> eyre::Result<()> {\n    // found running session, do nothing\n    if SESSION_STDIN.lock().as_mut().is_some() {\n        tracing::debug!(\"PowerShell session already started\");\n        return Ok(());\n    }\n\n    tracing::debug!(\"Starting PowerShell session\");\n\n    let mut child = Command::new(\"powershell.exe\")\n        .args([\"-NoLogo\", \"-NoProfile\", \"-Command\", \"-\"])\n        .stdin(Stdio::piped())\n        .creation_flags(CREATE_NO_WINDOW)\n        .spawn()?;\n\n    let stdin = child.stdin.take().expect(\"stdin piped\");\n\n    // Store stdin for later commands\n    let mut session_stdin = SESSION_STDIN.lock();\n    *session_stdin = Option::from(stdin);\n\n    Ok(())\n}\n\nfn stop_powershell() -> eyre::Result<()> {\n    tracing::debug!(\"Stopping PowerShell session\");\n\n    if let Some(mut session_stdin) = SESSION_STDIN.lock().take() {\n        if let Err(e) = session_stdin.write_all(b\"exit\\n\") {\n            tracing::error!(error = %e, \"failed to write exit command to PowerShell stdin\");\n            return Err(e.into());\n        }\n        if let Err(e) = session_stdin.flush() {\n            tracing::error!(error = %e, \"failed to flush PowerShell stdin\");\n            return Err(e.into());\n        }\n\n        tracing::debug!(\"PowerShell session stopped\");\n    } else {\n        tracing::debug!(\"PowerShell session already stopped\");\n    }\n\n    Ok(())\n}\n\npub fn exec_powershell(cmd: &str) -> eyre::Result<()> {\n    if let Some(session_stdin) = SESSION_STDIN.lock().as_mut() {\n        if let Err(e) = writeln!(session_stdin, \"{cmd}\") {\n            tracing::error!(error = %e, cmd = cmd, \"failed to write command to PowerShell stdin\");\n            return Err(e.into());\n        }\n\n        if let Err(e) = session_stdin.flush() {\n            tracing::error!(error = %e, \"failed to flush PowerShell stdin\");\n            return Err(e.into());\n        }\n\n        return Ok(());\n    }\n\n    Err(Error::new(ErrorKind::NotFound, \"PowerShell session not started\").into())\n}\n\npub struct Komobar {\n    pub hwnd: Option<isize>,\n    pub monitor_index: Option<usize>,\n    pub disabled: bool,\n    pub config: KomobarConfig,\n    pub render_config: Rc<RefCell<RenderConfig>>,\n    pub monitor_info: Option<Rc<RefCell<MonitorInfo>>>,\n    pub left_widgets: Vec<Box<dyn BarWidget>>,\n    pub center_widgets: Vec<Box<dyn BarWidget>>,\n    pub right_widgets: Vec<Box<dyn BarWidget>>,\n    pub rx_gui: Receiver<KomorebiEvent>,\n    pub rx_config: Receiver<KomobarConfig>,\n    pub bg_color: Rc<RefCell<Color32>>,\n    pub bg_color_with_alpha: Rc<RefCell<Color32>>,\n    pub scale_factor: f32,\n    pub size_rect: komorebi_client::Rect,\n    pub work_area_offset: komorebi_client::Rect,\n    applied_theme_on_first_frame: bool,\n    mouse_follows_focus: bool,\n    input_config: InputConfig,\n}\n\nstruct InputConfig {\n    accumulated_scroll_delta: Vec2,\n    act_on_vertical_scroll: bool,\n    act_on_horizontal_scroll: bool,\n    vertical_scroll_threshold: f32,\n    horizontal_scroll_threshold: f32,\n    vertical_scroll_max_threshold: f32,\n    horizontal_scroll_max_threshold: f32,\n}\n\npub fn apply_theme(\n    ctx: &Context,\n    theme: KomobarTheme,\n    bg_color: Rc<RefCell<Color32>>,\n    bg_color_with_alpha: Rc<RefCell<Color32>>,\n    transparency_alpha: Option<u8>,\n    grouping: Option<Grouping>,\n    render_config: Rc<RefCell<RenderConfig>>,\n) {\n    let (auto_select_fill, auto_select_text) = match theme {\n        KomobarTheme::Catppuccin(KomobarThemeCatppuccin {\n            name: catppuccin,\n            accent: catppuccin_value,\n            auto_select_fill: catppuccin_auto_select_fill,\n            auto_select_text: catppuccin_auto_select_text,\n        }) => {\n            match catppuccin {\n                Catppuccin::Frappe => {\n                    catppuccin_egui::set_theme(ctx, catppuccin_egui::FRAPPE);\n                    let catppuccin_value = catppuccin_value.unwrap_or_default();\n                    let accent = catppuccin_value.color32(catppuccin.as_theme());\n\n                    ctx.style_mut(|style| {\n                        style.visuals.selection.stroke.color = accent;\n                        style.visuals.widgets.hovered.fg_stroke.color = accent;\n                        style.visuals.widgets.active.fg_stroke.color = accent;\n                        style.visuals.override_text_color = None;\n                    });\n\n                    bg_color.replace(catppuccin_egui::FRAPPE.base);\n                }\n                Catppuccin::Latte => {\n                    catppuccin_egui::set_theme(ctx, catppuccin_egui::LATTE);\n                    let catppuccin_value = catppuccin_value.unwrap_or_default();\n                    let accent = catppuccin_value.color32(catppuccin.as_theme());\n\n                    ctx.style_mut(|style| {\n                        style.visuals.selection.stroke.color = accent;\n                        style.visuals.widgets.hovered.fg_stroke.color = accent;\n                        style.visuals.widgets.active.fg_stroke.color = accent;\n                        style.visuals.override_text_color = None;\n                    });\n\n                    bg_color.replace(catppuccin_egui::LATTE.base);\n                }\n                Catppuccin::Macchiato => {\n                    catppuccin_egui::set_theme(ctx, catppuccin_egui::MACCHIATO);\n                    let catppuccin_value = catppuccin_value.unwrap_or_default();\n                    let accent = catppuccin_value.color32(catppuccin.as_theme());\n\n                    ctx.style_mut(|style| {\n                        style.visuals.selection.stroke.color = accent;\n                        style.visuals.widgets.hovered.fg_stroke.color = accent;\n                        style.visuals.widgets.active.fg_stroke.color = accent;\n                        style.visuals.override_text_color = None;\n                    });\n\n                    bg_color.replace(catppuccin_egui::MACCHIATO.base);\n                }\n                Catppuccin::Mocha => {\n                    catppuccin_egui::set_theme(ctx, catppuccin_egui::MOCHA);\n                    let catppuccin_value = catppuccin_value.unwrap_or_default();\n                    let accent = catppuccin_value.color32(catppuccin.as_theme());\n\n                    ctx.style_mut(|style| {\n                        style.visuals.selection.stroke.color = accent;\n                        style.visuals.widgets.hovered.fg_stroke.color = accent;\n                        style.visuals.widgets.active.fg_stroke.color = accent;\n                        style.visuals.override_text_color = None;\n                    });\n\n                    bg_color.replace(catppuccin_egui::MOCHA.base);\n                }\n            }\n\n            (\n                catppuccin_auto_select_fill.map(|c| c.color32(catppuccin.as_theme())),\n                catppuccin_auto_select_text.map(|c| c.color32(catppuccin.as_theme())),\n            )\n        }\n        KomobarTheme::Base16(KomobarThemeBase16 {\n            name: base16,\n            accent: base16_value,\n            auto_select_fill: base16_auto_select_fill,\n            auto_select_text: base16_auto_select_text,\n        }) => {\n            ctx.set_style(base16.style());\n            let base16_value = base16_value.unwrap_or_default();\n            let accent = base16_value.color32(Base16Wrapper::Base16(base16));\n\n            ctx.style_mut(|style| {\n                style.visuals.selection.stroke.color = accent;\n                style.visuals.widgets.hovered.fg_stroke.color = accent;\n                style.visuals.widgets.active.fg_stroke.color = accent;\n            });\n\n            bg_color.replace(base16.background());\n\n            (\n                base16_auto_select_fill.map(|c| c.color32(Base16Wrapper::Base16(base16))),\n                base16_auto_select_text.map(|c| c.color32(Base16Wrapper::Base16(base16))),\n            )\n        }\n        KomobarTheme::Custom(KomobarThemeCustom {\n            colours,\n            accent: base16_value,\n            auto_select_fill: base16_auto_select_fill,\n            auto_select_text: base16_auto_select_text,\n        }) => {\n            let background = colours.background();\n            ctx.set_style(colours.style());\n            let base16_value = base16_value.unwrap_or_default();\n            let accent = base16_value.color32(Base16Wrapper::Custom(colours.clone()));\n\n            ctx.style_mut(|style| {\n                style.visuals.selection.stroke.color = accent;\n                style.visuals.widgets.hovered.fg_stroke.color = accent;\n                style.visuals.widgets.active.fg_stroke.color = accent;\n            });\n\n            bg_color.replace(background);\n\n            (\n                base16_auto_select_fill.map(|c| c.color32(Base16Wrapper::Custom(colours.clone()))),\n                base16_auto_select_text.map(|c| c.color32(Base16Wrapper::Custom(colours.clone()))),\n            )\n        }\n    };\n\n    AUTO_SELECT_FILL_COLOUR.store(\n        auto_select_fill.map_or(0, |c| Colour::from(c).into()),\n        Ordering::SeqCst,\n    );\n    AUTO_SELECT_TEXT_COLOUR.store(\n        auto_select_text.map_or(0, |c| Colour::from(c).into()),\n        Ordering::SeqCst,\n    );\n\n    // Apply transparency_alpha\n    let theme_color = *bg_color.borrow();\n\n    bg_color_with_alpha.replace(theme_color.try_apply_alpha(transparency_alpha));\n\n    // apply rounding to the widgets\n    if let Some(Grouping::Bar(config) | Grouping::Alignment(config) | Grouping::Widget(config)) =\n        &grouping\n        && let Some(rounding) = config.rounding\n    {\n        ctx.style_mut(|style| {\n            style.visuals.widgets.noninteractive.corner_radius = rounding.into();\n            style.visuals.widgets.inactive.corner_radius = rounding.into();\n            style.visuals.widgets.hovered.corner_radius = rounding.into();\n            style.visuals.widgets.active.corner_radius = rounding.into();\n            style.visuals.widgets.open.corner_radius = rounding.into();\n        });\n    }\n\n    // Update RenderConfig's background_color so that widgets will have the new color\n    render_config.borrow_mut().background_color = *bg_color.borrow();\n}\n\nimpl Komobar {\n    pub fn apply_config(\n        &mut self,\n        ctx: &Context,\n        previous_monitor_info: Option<Rc<RefCell<MonitorInfo>>>,\n    ) {\n        MAX_LABEL_WIDTH.store(\n            self.config.max_label_width.unwrap_or(400.0) as i32,\n            Ordering::SeqCst,\n        );\n\n        if let Some(font_family) = &self.config.font_family {\n            tracing::info!(\"attempting to add custom font family: {font_family}\");\n            Self::add_custom_font(ctx, font_family);\n        }\n\n        // Update the `size_rect` so that the bar position can be changed on the EGUI update\n        // function\n        self.update_size_rect();\n\n        self.try_apply_theme(ctx);\n\n        if let Some(font_size) = &self.config.font_size {\n            tracing::info!(\"attempting to set custom font size: {font_size}\");\n            Self::set_font_size(ctx, *font_size);\n        }\n\n        self.render_config.replace((&self.config).new_renderconfig(\n            ctx,\n            *self.bg_color.borrow(),\n            self.config.icon_scale,\n        ));\n\n        let mut monitor_info = previous_monitor_info;\n        let mut komorebi_widgets = Vec::new();\n\n        for (idx, widget_config) in self.config.left_widgets.iter().enumerate() {\n            if let WidgetConfig::Komorebi(config) = widget_config {\n                komorebi_widgets.push((Komorebi::from(config), idx, Alignment::Left));\n            }\n        }\n\n        if let Some(center_widgets) = &self.config.center_widgets {\n            for (idx, widget_config) in center_widgets.iter().enumerate() {\n                if let WidgetConfig::Komorebi(config) = widget_config {\n                    komorebi_widgets.push((Komorebi::from(config), idx, Alignment::Center));\n                }\n            }\n        }\n\n        for (idx, widget_config) in self.config.right_widgets.iter().enumerate() {\n            if let WidgetConfig::Komorebi(config) = widget_config {\n                komorebi_widgets.push((Komorebi::from(config), idx, Alignment::Right));\n            }\n        }\n\n        let mut left_widgets = self\n            .config\n            .left_widgets\n            .iter()\n            .filter(|config| config.enabled())\n            .map(|config| config.as_boxed_bar_widget())\n            .collect::<Vec<Box<dyn BarWidget>>>();\n\n        let mut center_widgets = match &self.config.center_widgets {\n            Some(center_widgets) => center_widgets\n                .iter()\n                .filter(|config| config.enabled())\n                .map(|config| config.as_boxed_bar_widget())\n                .collect::<Vec<Box<dyn BarWidget>>>(),\n            None => vec![],\n        };\n\n        let mut right_widgets = self\n            .config\n            .right_widgets\n            .iter()\n            .filter(|config| config.enabled())\n            .map(|config| config.as_boxed_bar_widget())\n            .collect::<Vec<Box<dyn BarWidget>>>();\n\n        if !komorebi_widgets.is_empty() {\n            komorebi_widgets\n                .into_iter()\n                .for_each(|(mut widget, idx, side)| {\n                    match monitor_info {\n                        None => {\n                            monitor_info = Some(widget.monitor_info.clone());\n                        }\n                        Some(ref previous) => {\n                            if widget.workspaces.is_some() {\n                                previous\n                                    .borrow_mut()\n                                    .update_from_self(&widget.monitor_info.borrow());\n                            }\n\n                            widget.monitor_info = previous.clone();\n                        }\n                    }\n\n                    let boxed: Box<dyn BarWidget> = Box::new(widget);\n                    match side {\n                        Alignment::Left => left_widgets[idx] = boxed,\n                        Alignment::Center => center_widgets[idx] = boxed,\n                        Alignment::Right => right_widgets[idx] = boxed,\n                    }\n                });\n        }\n\n        right_widgets.reverse();\n\n        self.left_widgets = left_widgets;\n        self.center_widgets = center_widgets;\n        self.right_widgets = right_widgets;\n\n        let (usr_monitor_index, config_work_area_offset) = match &self.config.monitor {\n            Some(MonitorConfigOrIndex::MonitorConfig(monitor_config)) => {\n                (monitor_config.index, monitor_config.work_area_offset)\n            }\n            Some(MonitorConfigOrIndex::Index(idx)) => (*idx, None),\n            None => (0, None),\n        };\n\n        let mapped_info = self.monitor_info.as_ref().map(|info| {\n            let monitor = info.borrow();\n            (\n                monitor.monitor_usr_idx_map.get(&usr_monitor_index).copied(),\n                monitor.mouse_follows_focus,\n            )\n        });\n\n        if let Some(info) = mapped_info {\n            self.monitor_index = info.0;\n            self.mouse_follows_focus = info.1;\n        }\n\n        if let Some(monitor_index) = self.monitor_index {\n            if let (prev_rect, Some(new_rect)) = (&self.work_area_offset, &config_work_area_offset)\n            {\n                if new_rect != prev_rect {\n                    self.work_area_offset = *new_rect;\n                    if let Err(error) = komorebi_client::send_message(\n                        &SocketMessage::MonitorWorkAreaOffset(monitor_index, *new_rect),\n                    ) {\n                        tracing::error!(\n                            \"error applying work area offset to monitor '{}': {}\",\n                            monitor_index,\n                            error,\n                        );\n                    } else {\n                        tracing::info!(\"work area offset applied to monitor: {}\", monitor_index);\n                    }\n                }\n            } else if let Some(height) = self.config.height.or(Some(BAR_HEIGHT)) {\n                // We only add the `bottom_margin` to the work_area_offset since the top margin is\n                // already considered on the `size_rect.top`\n                let bottom_margin = self\n                    .config\n                    .margin\n                    .as_ref()\n                    .map_or(0, |v| v.to_individual(0.0).bottom as i32);\n                let new_rect = komorebi_client::Rect {\n                    left: 0,\n                    top: (height as i32)\n                        + (self.size_rect.top - MONITOR_TOP.load(Ordering::SeqCst))\n                        + bottom_margin,\n                    right: 0,\n                    bottom: (height as i32)\n                        + (self.size_rect.top - MONITOR_TOP.load(Ordering::SeqCst))\n                        + bottom_margin,\n                };\n\n                if new_rect != self.work_area_offset {\n                    self.work_area_offset = new_rect;\n                    if let Err(error) = komorebi_client::send_message(\n                        &SocketMessage::MonitorWorkAreaOffset(monitor_index, new_rect),\n                    ) {\n                        tracing::error!(\n                            \"error applying work area offset to monitor '{monitor_index}': {error}\"\n                        );\n                    } else {\n                        tracing::info!(\"work area offset applied to monitor: {monitor_index}\",);\n                    }\n                }\n            }\n        } else if self.monitor_info.is_some() && !self.disabled {\n            tracing::warn!(\n                \"couldn't find the monitor index of this bar! Disabling the bar until the monitor connects...\"\n            );\n            self.disabled = true;\n        } else {\n            tracing::warn!(\n                \"couldn't find the monitor index of this bar, if the bar is starting up this is normal until it receives the first state from komorebi.\"\n            );\n            self.disabled = true;\n        }\n\n        if let Some(mouse) = &self.config.mouse {\n            self.input_config.act_on_vertical_scroll =\n                mouse.on_scroll_up.is_some() || mouse.on_scroll_down.is_some();\n            self.input_config.act_on_horizontal_scroll =\n                mouse.on_scroll_left.is_some() || mouse.on_scroll_right.is_some();\n            self.input_config.vertical_scroll_threshold = mouse\n                .vertical_scroll_threshold\n                .unwrap_or(30.0)\n                .clamp(10.0, 300.0);\n            self.input_config.horizontal_scroll_threshold = mouse\n                .horizontal_scroll_threshold\n                .unwrap_or(30.0)\n                .clamp(10.0, 300.0);\n            // limit how many \"ticks\" can be accumulated\n            self.input_config.vertical_scroll_max_threshold =\n                self.input_config.vertical_scroll_threshold * 3.0;\n            self.input_config.horizontal_scroll_max_threshold =\n                self.input_config.horizontal_scroll_threshold * 3.0;\n\n            if mouse.has_command() {\n                start_powershell().unwrap_or_else(|_| {\n                    tracing::error!(\"failed to start powershell session\");\n                });\n            } else {\n                stop_powershell().unwrap_or_else(|_| {\n                    tracing::error!(\"failed to stop powershell session\");\n                });\n            }\n        }\n\n        tracing::info!(\"widget configuration options applied\");\n\n        self.monitor_info = monitor_info;\n    }\n\n    /// Updates the `size_rect` field. Returns a bool indicating if the field was changed or not\n    fn update_size_rect(&mut self) {\n        let position = self.config.position.clone().unwrap_or(PositionConfig {\n            start: Some(Position {\n                x: MONITOR_LEFT.load(Ordering::SeqCst) as f32,\n                y: MONITOR_TOP.load(Ordering::SeqCst) as f32,\n            }),\n            end: Some(Position {\n                x: MONITOR_RIGHT.load(Ordering::SeqCst) as f32,\n                y: BAR_HEIGHT,\n            }),\n        });\n\n        let mut start = position.start.unwrap_or(Position {\n            x: MONITOR_LEFT.load(Ordering::SeqCst) as f32,\n            y: MONITOR_TOP.load(Ordering::SeqCst) as f32,\n        });\n\n        let mut end = position.end.unwrap_or(Position {\n            x: MONITOR_RIGHT.load(Ordering::SeqCst) as f32,\n            y: BAR_HEIGHT,\n        });\n\n        if let Some(height) = self.config.height {\n            end.y = height;\n        }\n\n        let margin = get_individual_spacing(0.0, &self.config.margin);\n\n        start.y += margin.top;\n        start.x += margin.left;\n        end.x -= margin.left + margin.right;\n\n        if end.y == 0.0 {\n            tracing::warn!(\n                \"position.end.y is set to 0.0 which will make your bar invisible on a config reload - this is usually set to 50.0 by default\"\n            )\n        }\n\n        self.size_rect = komorebi_client::Rect {\n            left: start.x as i32,\n            top: start.y as i32,\n            right: end.x as i32,\n            bottom: end.y as i32,\n        };\n    }\n\n    fn try_apply_theme(&mut self, ctx: &Context) {\n        match &self.config.theme {\n            Some(theme) => {\n                apply_theme(\n                    ctx,\n                    theme.clone(),\n                    self.bg_color.clone(),\n                    self.bg_color_with_alpha.clone(),\n                    self.config.transparency_alpha,\n                    self.config.grouping,\n                    self.render_config.clone(),\n                );\n            }\n            None => {\n                let home_dir: PathBuf = std::env::var(\"KOMOREBI_CONFIG_HOME\").map_or_else(\n                    |_| dirs::home_dir().expect(\"there is no home directory\"),\n                    |home_path| {\n                        let home = home_path.replace_env();\n\n                        assert!(\n                            home.is_dir(),\n                            \"$Env:KOMOREBI_CONFIG_HOME is set to '{home_path}', which is not a valid directory\"\n                        );\n\n                        home\n\n                    },\n                );\n\n                let bar_transparency_alpha = self.config.transparency_alpha;\n                let bar_grouping = self.config.grouping;\n                let config = home_dir.join(\"komorebi.json\");\n                match komorebi_client::StaticConfig::read(&config) {\n                    Ok(config) => {\n                        if let Some(theme) = config.theme {\n                            apply_theme(\n                                ctx,\n                                KomobarTheme::from(theme),\n                                self.bg_color.clone(),\n                                self.bg_color_with_alpha.clone(),\n                                bar_transparency_alpha,\n                                bar_grouping,\n                                self.render_config.clone(),\n                            );\n                        }\n                    }\n                    Err(_) => {\n                        ctx.set_style(Style::default());\n                        self.bg_color.replace(Style::default().visuals.panel_fill);\n\n                        // apply rounding to the widgets since we didn't call `apply_theme`\n                        if let Some(\n                            Grouping::Bar(config)\n                            | Grouping::Alignment(config)\n                            | Grouping::Widget(config),\n                        ) = &bar_grouping\n                            && let Some(rounding) = config.rounding\n                        {\n                            ctx.style_mut(|style| {\n                                style.visuals.widgets.noninteractive.corner_radius =\n                                    rounding.into();\n                                style.visuals.widgets.inactive.corner_radius = rounding.into();\n                                style.visuals.widgets.hovered.corner_radius = rounding.into();\n                                style.visuals.widgets.active.corner_radius = rounding.into();\n                                style.visuals.widgets.open.corner_radius = rounding.into();\n                            });\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    pub fn new(\n        cc: &eframe::CreationContext<'_>,\n        rx_gui: Receiver<KomorebiEvent>,\n        rx_config: Receiver<KomobarConfig>,\n        config: KomobarConfig,\n    ) -> Self {\n        let mut komobar = Self {\n            hwnd: process_hwnd(),\n            monitor_index: None,\n            disabled: false,\n            config,\n            render_config: Rc::new(RefCell::new(RenderConfig::new())),\n            monitor_info: None,\n            left_widgets: vec![],\n            center_widgets: vec![],\n            right_widgets: vec![],\n            rx_gui,\n            rx_config,\n            bg_color: Rc::new(RefCell::new(Style::default().visuals.panel_fill)),\n            bg_color_with_alpha: Rc::new(RefCell::new(Style::default().visuals.panel_fill)),\n            scale_factor: cc.egui_ctx.native_pixels_per_point().unwrap_or(1.0),\n            size_rect: komorebi_client::Rect::default(),\n            work_area_offset: komorebi_client::Rect::default(),\n            applied_theme_on_first_frame: false,\n            mouse_follows_focus: false,\n            input_config: InputConfig {\n                accumulated_scroll_delta: Vec2::new(0.0, 0.0),\n                act_on_vertical_scroll: false,\n                act_on_horizontal_scroll: false,\n                vertical_scroll_threshold: 0.0,\n                horizontal_scroll_threshold: 0.0,\n                vertical_scroll_max_threshold: 0.0,\n                horizontal_scroll_max_threshold: 0.0,\n            },\n        };\n\n        komobar.apply_config(&cc.egui_ctx, None);\n        // needs a double apply the first time for some reason\n        komobar.apply_config(&cc.egui_ctx, None);\n\n        komobar\n    }\n\n    fn set_font_size(ctx: &Context, font_size: f32) {\n        ctx.style_mut(|style| {\n            style.text_styles = [\n                (TextStyle::Small, FontId::new(9.0, FontFamily::Proportional)),\n                (\n                    TextStyle::Body,\n                    FontId::new(font_size, FontFamily::Proportional),\n                ),\n                (\n                    TextStyle::Button,\n                    FontId::new(font_size, FontFamily::Proportional),\n                ),\n                (\n                    TextStyle::Heading,\n                    FontId::new(18.0, FontFamily::Proportional),\n                ),\n                (\n                    TextStyle::Monospace,\n                    FontId::new(font_size, FontFamily::Monospace),\n                ),\n            ]\n            .into();\n        });\n    }\n\n    fn add_custom_font(ctx: &Context, name: &str) {\n        let mut fonts = FontDefinitions::default();\n        egui_phosphor::add_to_fonts(&mut fonts, egui_phosphor::Variant::Regular);\n\n        let mut fallbacks = HashMap::new();\n\n        fallbacks.insert(\"Microsoft YaHei\", \"C:\\\\Windows\\\\Fonts\\\\msyh.ttc\"); // chinese\n        fallbacks.insert(\"Malgun Gothic\", \"C:\\\\Windows\\\\Fonts\\\\malgun.ttf\"); // korean\n        fallbacks.insert(\"Leelawadee UI\", \"C:\\\\Windows\\\\Fonts\\\\LeelawUI.ttf\"); // thai\n\n        for (name, path) in fallbacks {\n            if let Ok(bytes) = std::fs::read(path) {\n                fonts\n                    .font_data\n                    .insert(name.to_owned(), Arc::from(FontData::from_owned(bytes)));\n\n                for family in [FontFamily::Proportional, FontFamily::Monospace] {\n                    fonts\n                        .families\n                        .entry(family)\n                        .or_default()\n                        .insert(0, name.to_owned());\n                }\n            }\n        }\n\n        let property = FontPropertyBuilder::new().family(name).build();\n\n        if let Some((font, _)) = system_fonts::get(&property) {\n            fonts\n                .font_data\n                .insert(name.to_owned(), Arc::new(FontData::from_owned(font)));\n\n            for family in [FontFamily::Proportional, FontFamily::Monospace] {\n                fonts\n                    .families\n                    .entry(family)\n                    .or_default()\n                    .insert(0, name.to_owned());\n            }\n        }\n\n        // Tell egui to use these fonts:\n        ctx.set_fonts(fonts);\n    }\n\n    pub fn position_bar(&self) {\n        if let Some(hwnd) = self.hwnd {\n            let window = komorebi_client::Window::from(hwnd);\n            match window.set_position(&self.size_rect, false) {\n                Ok(_) => {\n                    tracing::info!(\"updated bar position\");\n                }\n                Err(error) => {\n                    tracing::error!(\"{error}\")\n                }\n            }\n        }\n    }\n\n    fn update_monitor_coordinates(&mut self, monitor_size: &komorebi_client::Rect) {\n        // Store the new monitor coordinates\n        MONITOR_TOP.store(monitor_size.top, Ordering::SeqCst);\n        MONITOR_LEFT.store(monitor_size.left, Ordering::SeqCst);\n        MONITOR_RIGHT.store(monitor_size.right, Ordering::SeqCst);\n\n        // Since the `config.position` is changed on `main.rs` we need to update it here.\n        // If the user had set up some `start` position, that will be overriden here\n        // since we have no way to know what was that value since it might have been\n        // changed on `main.rs`. However if the users use the new configs this won't be\n        // a problem for them.\n        if let Some(start) = self.config.position.as_mut().and_then(|p| p.start.as_mut()) {\n            start.x = monitor_size.left as f32;\n            start.y = monitor_size.top as f32;\n        }\n    }\n}\nimpl eframe::App for Komobar {\n    // Needed for transparency\n    fn clear_color(&self, _visuals: &Visuals) -> [f32; 4] {\n        Rgba::TRANSPARENT.to_array()\n    }\n\n    fn update(&mut self, ctx: &Context, _frame: &mut eframe::Frame) {\n        if self.hwnd.is_none() {\n            self.hwnd = process_hwnd();\n        }\n\n        if self.scale_factor != ctx.native_pixels_per_point().unwrap_or(1.0) {\n            self.scale_factor = ctx.native_pixels_per_point().unwrap_or(1.0);\n            self.apply_config(ctx, self.monitor_info.clone());\n        }\n\n        if let Ok(updated_config) = self.rx_config.try_recv() {\n            self.config = updated_config;\n            self.apply_config(ctx, self.monitor_info.clone());\n        }\n\n        match self.rx_gui.try_recv() {\n            Err(error) => match error {\n                TryRecvError::Empty => {}\n                TryRecvError::Disconnected => {\n                    tracing::error!(\n                        \"failed to receive komorebi notification on gui thread: {error}\"\n                    );\n                }\n            },\n            Ok(KomorebiEvent::Notification(notification)) => {\n                let state = &notification.state;\n                let usr_monitor_index = match &self.config.monitor {\n                    Some(MonitorConfigOrIndex::MonitorConfig(monitor_config)) => {\n                        monitor_config.index\n                    }\n                    Some(MonitorConfigOrIndex::Index(idx)) => *idx,\n                    None => 0,\n                };\n\n                let monitor_index = state.monitor_usr_idx_map.get(&usr_monitor_index).copied();\n                self.monitor_index = monitor_index;\n                let mut should_apply_config = false;\n\n                match notification.event {\n                    NotificationEvent::VirtualDesktop(\n                        VirtualDesktopNotification::EnteredAssociatedVirtualDesktop,\n                    ) => {\n                        tracing::debug!(\n                            \"back on komorebi's associated virtual desktop - restoring bar\"\n                        );\n                        if let Some(hwnd) = self.hwnd {\n                            komorebi_client::WindowsApi::restore_window(hwnd);\n                        }\n                    }\n                    NotificationEvent::VirtualDesktop(\n                        VirtualDesktopNotification::LeftAssociatedVirtualDesktop,\n                    ) => {\n                        tracing::debug!(\n                            \"no longer on komorebi's associated virtual desktop - minimizing bar\"\n                        );\n                        if let Some(hwnd) = self.hwnd {\n                            komorebi_client::WindowsApi::minimize_window(hwnd);\n                        }\n                    }\n                    _ => {}\n                }\n\n                if self.monitor_index.is_none()\n                    || self\n                        .monitor_index\n                        .is_some_and(|idx| idx >= state.monitors.elements().len())\n                {\n                    if !self.disabled {\n                        // Monitor for this bar got disconnected lets disable the bar until it\n                        // reconnects\n                        self.disabled = true;\n                        tracing::warn!(\n                            \"This bar's monitor got disconnected. The bar will be disabled until it reconnects...\"\n                        );\n                    }\n                    return;\n                } else {\n                    if self.disabled {\n                        tracing::info!(\"Found this bar's monitor. The bar will be enabled!\");\n\n                        // Restore the bar in case it has been minimized when the monitor\n                        // disconnected\n                        if let Some(hwnd) = self.hwnd {\n                            let window = komorebi_client::Window::from(hwnd);\n                            if window.is_miminized() {\n                                komorebi_client::WindowsApi::restore_window(hwnd);\n                            }\n                        }\n\n                        // Reset the current `work_area_offset` so that it gets recalculated and\n                        // properly applied again, since if the monitor has connected for the first\n                        // time it won't have the work_area_offset applied but the bar thinks it\n                        // does.\n                        self.work_area_offset = komorebi_client::Rect::default();\n\n                        should_apply_config = true;\n                    }\n                    self.disabled = false;\n                }\n\n                if matches!(\n                    notification.event,\n                    NotificationEvent::Monitor(MonitorNotification::DisplayConnectionChange)\n                ) {\n                    let monitor_index = self.monitor_index.expect(\"should have a monitor index\");\n\n                    let monitor_size = state.monitors.elements()[monitor_index].size;\n\n                    self.update_monitor_coordinates(&monitor_size);\n\n                    should_apply_config = true;\n                }\n\n                if self.disabled {\n                    return;\n                }\n\n                // Check if monitor coordinates/size has changed\n                if let Some(monitor_index) = self.monitor_index {\n                    let monitor_size = state.monitors.elements()[monitor_index].size;\n                    let top = MONITOR_TOP.load(Ordering::SeqCst);\n                    let left = MONITOR_LEFT.load(Ordering::SeqCst);\n                    let right = MONITOR_RIGHT.load(Ordering::SeqCst);\n                    let rect = komorebi_client::Rect {\n                        top,\n                        left,\n                        bottom: monitor_size.bottom,\n                        right,\n                    };\n                    if monitor_size != rect {\n                        tracing::info!(\n                            \"Monitor coordinates/size has changed, storing new coordinates: {:#?}\",\n                            monitor_size\n                        );\n\n                        self.update_monitor_coordinates(&monitor_size);\n\n                        should_apply_config = true;\n                    }\n                }\n\n                if let Some(monitor_info) = &self.monitor_info {\n                    monitor_info.borrow_mut().update(\n                        self.monitor_index,\n                        notification.state,\n                        self.render_config.borrow().show_all_icons,\n                    );\n                    handle_notification(\n                        ctx,\n                        notification.event,\n                        self.bg_color.clone(),\n                        self.bg_color_with_alpha.clone(),\n                        self.config.transparency_alpha,\n                        self.config.grouping,\n                        self.config.theme.clone(),\n                        self.render_config.clone(),\n                    );\n                }\n\n                if should_apply_config {\n                    self.apply_config(ctx, self.monitor_info.clone());\n\n                    // Reposition the Bar\n                    self.position_bar();\n                }\n            }\n            Ok(KomorebiEvent::Reconnect) => {\n                if let Some(monitor_index) = self.monitor_index {\n                    if let Err(error) = komorebi_client::send_message(\n                        &SocketMessage::MonitorWorkAreaOffset(monitor_index, self.work_area_offset),\n                    ) {\n                        tracing::error!(\n                            \"error applying work area offset to monitor '{}': {}\",\n                            monitor_index,\n                            error,\n                        );\n                    } else {\n                        tracing::info!(\"work area offset applied to monitor: {}\", monitor_index);\n                    }\n                }\n            }\n        }\n\n        if self.disabled {\n            // The check for disabled is performed above, if we get here and the bar is still\n            // disabled then we should return without drawing anything.\n            return;\n        }\n\n        if !self.applied_theme_on_first_frame {\n            self.try_apply_theme(ctx);\n            self.applied_theme_on_first_frame = true;\n        }\n\n        // Check if egui's Window size is the expected one, if not, update it\n        if let Some(current_rect) = ctx.input(|i| i.viewport().outer_rect) {\n            // Get the correct size according to scale factor\n            let current_rect = komorebi_client::Rect {\n                left: (current_rect.min.x * self.scale_factor) as i32,\n                top: (current_rect.min.y * self.scale_factor) as i32,\n                right: ((current_rect.max.x - current_rect.min.x) * self.scale_factor) as i32,\n                bottom: ((current_rect.max.y - current_rect.min.y) * self.scale_factor) as i32,\n            };\n\n            if self.size_rect != current_rect {\n                self.position_bar();\n            }\n        }\n\n        let frame = match &self.config.padding {\n            None => {\n                if let Some(frame) = &self.config.frame {\n                    Frame::NONE\n                        .inner_margin(Margin::symmetric(\n                            frame.inner_margin.x as i8,\n                            frame.inner_margin.y as i8,\n                        ))\n                        .fill(*self.bg_color_with_alpha.borrow())\n                } else {\n                    Frame::NONE\n                        .inner_margin(Margin::same(0))\n                        .fill(*self.bg_color_with_alpha.borrow())\n                }\n            }\n            Some(padding) => {\n                let padding = padding.to_individual(DEFAULT_PADDING);\n                Frame::NONE\n                    .inner_margin(Margin {\n                        top: padding.top as i8,\n                        bottom: padding.bottom as i8,\n                        left: padding.left as i8,\n                        right: padding.right as i8,\n                    })\n                    .fill(*self.bg_color_with_alpha.borrow())\n            }\n        };\n\n        let mut render_config = self.render_config.borrow_mut();\n\n        let frame = render_config.change_frame_on_bar(frame, &ctx.style());\n\n        CentralPanel::default().frame(frame).show(ctx, |ui| {\n            if let Some(mouse_config) = &self.config.mouse {\n                let command = if ui\n                    .input(|i| i.pointer.button_double_clicked(PointerButton::Primary))\n                {\n                    tracing::debug!(\"Input: primary button double clicked\");\n                    &mouse_config.on_primary_double_click\n                } else if ui.input(|i| i.pointer.button_clicked(PointerButton::Secondary)) {\n                    tracing::debug!(\"Input: secondary button clicked\");\n                    &mouse_config.on_secondary_click\n                } else if ui.input(|i| i.pointer.button_clicked(PointerButton::Middle)) {\n                    tracing::debug!(\"Input: middle button clicked\");\n                    &mouse_config.on_middle_click\n                } else if ui.input(|i| i.pointer.button_clicked(PointerButton::Extra1)) {\n                    tracing::debug!(\"Input: extra1 button clicked\");\n                    &mouse_config.on_extra1_click\n                } else if ui.input(|i| i.pointer.button_clicked(PointerButton::Extra2)) {\n                    tracing::debug!(\"Input: extra2 button clicked\");\n                    &mouse_config.on_extra2_click\n                } else if self.input_config.act_on_vertical_scroll\n                    || self.input_config.act_on_horizontal_scroll\n                {\n                    let scroll_delta = ui.input(|input| input.smooth_scroll_delta);\n\n                    self.input_config.accumulated_scroll_delta += scroll_delta;\n\n                    if scroll_delta.y != 0.0 && self.input_config.act_on_vertical_scroll {\n                        // Do not store more than the max threshold\n                        self.input_config.accumulated_scroll_delta.y =\n                            self.input_config.accumulated_scroll_delta.y.clamp(\n                                -self.input_config.vertical_scroll_max_threshold,\n                                self.input_config.vertical_scroll_max_threshold,\n                            );\n\n                        // When the accumulated scroll passes the threshold, trigger a tick.\n                        if self.input_config.accumulated_scroll_delta.y.abs()\n                            >= self.input_config.vertical_scroll_threshold\n                        {\n                            let direction_command =\n                                if self.input_config.accumulated_scroll_delta.y > 0.0 {\n                                    &mouse_config.on_scroll_up\n                                } else {\n                                    &mouse_config.on_scroll_down\n                                };\n\n                            // Remove one tick's worth of scroll from the accumulator, preserving any excess.\n                            self.input_config.accumulated_scroll_delta.y -=\n                                self.input_config.vertical_scroll_threshold\n                                    * self.input_config.accumulated_scroll_delta.y.signum();\n\n                            tracing::debug!(\n                                \"Input: vertical scroll ticked. excess: {} | threshold: {}\",\n                                self.input_config.accumulated_scroll_delta.y,\n                                self.input_config.vertical_scroll_threshold\n                            );\n\n                            direction_command\n                        } else {\n                            &None\n                        }\n                    } else if scroll_delta.x != 0.0 && self.input_config.act_on_horizontal_scroll {\n                        // Do not store more than the max threshold\n                        self.input_config.accumulated_scroll_delta.x =\n                            self.input_config.accumulated_scroll_delta.x.clamp(\n                                -self.input_config.horizontal_scroll_max_threshold,\n                                self.input_config.horizontal_scroll_max_threshold,\n                            );\n\n                        // When the accumulated scroll passes the threshold, trigger a tick.\n                        if self.input_config.accumulated_scroll_delta.x.abs()\n                            >= self.input_config.horizontal_scroll_threshold\n                        {\n                            let direction_command =\n                                if self.input_config.accumulated_scroll_delta.x > 0.0 {\n                                    &mouse_config.on_scroll_left\n                                } else {\n                                    &mouse_config.on_scroll_right\n                                };\n\n                            // Remove one tick's worth of scroll from the accumulator, preserving any excess.\n                            self.input_config.accumulated_scroll_delta.x -=\n                                self.input_config.horizontal_scroll_threshold\n                                    * self.input_config.accumulated_scroll_delta.x.signum();\n\n                            tracing::debug!(\n                                \"Input: horizontal scroll ticked. excess: {} | threshold: {}\",\n                                self.input_config.accumulated_scroll_delta.x,\n                                self.input_config.horizontal_scroll_threshold\n                            );\n\n                            direction_command\n                        } else {\n                            &None\n                        }\n                    } else {\n                        &None\n                    }\n                } else {\n                    &None\n                };\n\n                if let Some(command) = command {\n                    command.execute(self.mouse_follows_focus);\n                }\n            }\n\n            // Apply grouping logic for the bar as a whole\n            let area_frame = if let Some(frame) = &self.config.frame {\n                Frame::NONE\n                    .inner_margin(Margin::symmetric(0, frame.inner_margin.y as i8))\n                    .outer_margin(Margin::same(0))\n            } else {\n                Frame::NONE\n                    .inner_margin(Margin::same(0))\n                    .outer_margin(Margin::same(0))\n            };\n\n            let available_height = ui.max_rect().max.y;\n            ctx.style_mut(|style| {\n                style.spacing.interact_size.y = available_height;\n            });\n\n            if !self.left_widgets.is_empty() {\n                // Left-aligned widgets layout\n                Area::new(Id::new(\"left_panel\"))\n                    .anchor(Align2::LEFT_CENTER, [0.0, 0.0]) // Align in the left center of the window\n                    .show(ctx, |ui| {\n                        let mut left_area_frame = area_frame;\n                        if let Some(padding) = self\n                            .config\n                            .padding\n                            .as_ref()\n                            .map(|s| s.to_individual(DEFAULT_PADDING))\n                        {\n                            left_area_frame.inner_margin.left = padding.left as i8;\n                            left_area_frame.inner_margin.top = padding.top as i8;\n                            left_area_frame.inner_margin.bottom = padding.bottom as i8;\n                        } else if let Some(frame) = &self.config.frame {\n                            left_area_frame.inner_margin.left = frame.inner_margin.x as i8;\n                            left_area_frame.inner_margin.top = frame.inner_margin.y as i8;\n                            left_area_frame.inner_margin.bottom = frame.inner_margin.y as i8;\n                        }\n\n                        left_area_frame.show(ui, |ui| {\n                            ui.horizontal(|ui| {\n                                let mut render_conf = render_config.clone();\n                                render_conf.alignment = Some(Alignment::Left);\n\n                                render_config.apply_on_alignment(ui, |ui| {\n                                    for w in &mut self.left_widgets {\n                                        w.render(ctx, ui, &mut render_conf);\n                                    }\n                                });\n                            });\n                        });\n                    });\n            }\n\n            if !self.right_widgets.is_empty() {\n                // Right-aligned widgets layout\n                Area::new(Id::new(\"right_panel\"))\n                    .anchor(Align2::RIGHT_CENTER, [0.0, 0.0]) // Align in the right center of the window\n                    .show(ctx, |ui| {\n                        let mut right_area_frame = area_frame;\n                        if let Some(padding) = self\n                            .config\n                            .padding\n                            .as_ref()\n                            .map(|s| s.to_individual(DEFAULT_PADDING))\n                        {\n                            right_area_frame.inner_margin.right = padding.right as i8;\n                            right_area_frame.inner_margin.top = padding.top as i8;\n                            right_area_frame.inner_margin.bottom = padding.bottom as i8;\n                        } else if let Some(frame) = &self.config.frame {\n                            right_area_frame.inner_margin.right = frame.inner_margin.x as i8;\n                            right_area_frame.inner_margin.top = frame.inner_margin.y as i8;\n                            right_area_frame.inner_margin.bottom = frame.inner_margin.y as i8;\n                        }\n\n                        right_area_frame.show(ui, |ui| {\n                            let initial_size = Vec2 {\n                                x: ui.available_size_before_wrap().x,\n                                y: ui.spacing().interact_size.y,\n                            };\n                            ui.allocate_ui_with_layout(\n                                initial_size,\n                                Layout::right_to_left(Align::Center),\n                                |ui| {\n                                    let mut render_conf = render_config.clone();\n                                    render_conf.alignment = Some(Alignment::Right);\n\n                                    render_config.apply_on_alignment(ui, |ui| {\n                                        for w in &mut self.right_widgets {\n                                            w.render(ctx, ui, &mut render_conf);\n                                        }\n                                    });\n                                },\n                            );\n                        });\n                    });\n            }\n\n            if !self.center_widgets.is_empty() {\n                // Floating center widgets\n                Area::new(Id::new(\"center_panel\"))\n                    .anchor(Align2::CENTER_CENTER, [0.0, 0.0]) // Align in the center of the window\n                    .show(ctx, |ui| {\n                        let mut center_area_frame = area_frame;\n                        if let Some(padding) = self\n                            .config\n                            .padding\n                            .as_ref()\n                            .map(|s| s.to_individual(DEFAULT_PADDING))\n                        {\n                            center_area_frame.inner_margin.top = padding.top as i8;\n                            center_area_frame.inner_margin.bottom = padding.bottom as i8;\n                        } else if let Some(frame) = &self.config.frame {\n                            center_area_frame.inner_margin.top = frame.inner_margin.y as i8;\n                            center_area_frame.inner_margin.bottom = frame.inner_margin.y as i8;\n                        }\n\n                        center_area_frame.show(ui, |ui| {\n                            ui.horizontal(|ui| {\n                                let mut render_conf = render_config.clone();\n                                render_conf.alignment = Some(Alignment::Center);\n\n                                render_config.apply_on_alignment(ui, |ui| {\n                                    for w in &mut self.center_widgets {\n                                        w.render(ctx, ui, &mut render_conf);\n                                    }\n                                });\n                            });\n                        });\n                    });\n            }\n        });\n    }\n}\n\n#[derive(Copy, Clone)]\npub enum Alignment {\n    Left,\n    Center,\n    Right,\n}\n\n#[allow(clippy::too_many_arguments)]\nfn handle_notification(\n    ctx: &Context,\n    event: komorebi_client::NotificationEvent,\n    bg_color: Rc<RefCell<Color32>>,\n    bg_color_with_alpha: Rc<RefCell<Color32>>,\n    transparency_alpha: Option<u8>,\n    grouping: Option<Grouping>,\n    default_theme: Option<KomobarTheme>,\n    render_config: Rc<RefCell<RenderConfig>>,\n) {\n    if let NotificationEvent::Socket(message) = event {\n        match message {\n            SocketMessage::ReloadStaticConfiguration(path) => {\n                if let Ok(config) = komorebi_client::StaticConfig::read(&path) {\n                    if let Some(theme) = config.theme {\n                        apply_theme(\n                            ctx,\n                            KomobarTheme::from(theme),\n                            bg_color.clone(),\n                            bg_color_with_alpha.clone(),\n                            transparency_alpha,\n                            grouping,\n                            render_config,\n                        );\n                        tracing::info!(\"applied theme from updated komorebi.json\");\n                    } else if let Some(default_theme) = default_theme {\n                        apply_theme(\n                            ctx,\n                            default_theme,\n                            bg_color.clone(),\n                            bg_color_with_alpha.clone(),\n                            transparency_alpha,\n                            grouping,\n                            render_config,\n                        );\n                        tracing::info!(\n                            \"removed theme from updated komorebi.json and applied default theme\"\n                        );\n                    } else {\n                        tracing::warn!(\n                            \"theme was removed from updated komorebi.json but there was no default theme to apply\"\n                        );\n                    }\n                }\n            }\n            SocketMessage::Theme(theme) => {\n                apply_theme(\n                    ctx,\n                    KomobarTheme::from(*theme),\n                    bg_color,\n                    bg_color_with_alpha.clone(),\n                    transparency_alpha,\n                    grouping,\n                    render_config,\n                );\n                tracing::info!(\"applied theme from komorebi socket message\");\n            }\n            _ => {}\n        }\n    }\n}\n"
  },
  {
    "path": "komorebi-bar/src/config.rs",
    "content": "use crate::DEFAULT_PADDING;\nuse crate::bar::exec_powershell;\nuse crate::render::Grouping;\nuse crate::widgets::widget::WidgetConfig;\nuse eframe::egui::Pos2;\nuse eframe::egui::TextBuffer;\nuse eframe::egui::Vec2;\nuse komorebi_client::PathExt;\nuse komorebi_client::Rect;\nuse komorebi_client::SocketMessage;\nuse serde::Deserialize;\nuse serde::Serialize;\nuse std::collections::HashMap;\nuse std::path::PathBuf;\n\n#[derive(Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// The `komorebi.bar.json` configuration file reference for `v0.1.41`\npub struct KomobarConfig {\n    /// Bar height\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = 50)))]\n    pub height: Option<f32>,\n    /// Bar padding. Use one value for all sides or use a grouped padding for horizontal and/or\n    /// vertical definition which can each take a single value for a symmetric padding or two\n    /// values for each side, i.e.:\n    /// ```json\n    /// \"padding\": {\n    ///     \"horizontal\": 10\n    /// }\n    /// ```\n    /// or:\n    /// ```json\n    /// \"padding\": {\n    ///     \"horizontal\": [left, right]\n    /// }\n    /// ```\n    /// You can also set individual padding on each side like this:\n    /// ```json\n    /// \"padding\": {\n    ///     \"top\": 10,\n    ///     \"bottom\": 10,\n    ///     \"left\": 10,\n    ///     \"right\": 10,\n    /// }\n    /// ```\n    /// By default, padding is set to 10 on all sides.\n    pub padding: Option<Padding>,\n    /// Bar margin. Use one value for all sides or use a grouped margin for horizontal and/or\n    /// vertical definition which can each take a single value for a symmetric margin or two\n    /// values for each side, i.e.:\n    /// ```json\n    /// \"margin\": {\n    ///     \"horizontal\": 10\n    /// }\n    /// ```\n    /// or:\n    /// ```json\n    /// \"margin\": {\n    ///     \"vertical\": [top, bottom]\n    /// }\n    /// ```\n    /// You can also set individual margin on each side like this:\n    /// ```json\n    /// \"margin\": {\n    ///     \"top\": 10,\n    ///     \"bottom\": 10,\n    ///     \"left\": 10,\n    ///     \"right\": 10,\n    /// }\n    /// ```\n    /// By default, margin is set to 0 on all sides.\n    pub margin: Option<Margin>,\n    /// Bar positioning options\n    #[serde(alias = \"viewport\")]\n    pub position: Option<PositionConfig>,\n    /// Frame options (see: https://docs.rs/egui/latest/egui/containers/frame/struct.Frame.html)\n    pub frame: Option<FrameConfig>,\n    /// The monitor index or the full monitor options\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = MonitorConfigOrIndex::Index(0))))]\n    pub monitor: Option<MonitorConfigOrIndex>,\n    /// Font family\n    pub font_family: Option<String>,\n    /// Font size\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = 12.5)))]\n    pub font_size: Option<f32>,\n    /// Scale of the icons relative to the font_size [[1.0-2.0]]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = 1.4)))]\n    pub icon_scale: Option<f32>,\n    /// Max label width before text truncation\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = 400.0)))]\n    pub max_label_width: Option<f32>,\n    /// Theme\n    pub theme: Option<KomobarTheme>,\n    /// Alpha value for the color transparency [[0-255]]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = 200)))]\n    pub transparency_alpha: Option<u8>,\n    /// Spacing between widgets\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = 10.0)))]\n    pub widget_spacing: Option<f32>,\n    /// Visual grouping for widgets\n    pub grouping: Option<Grouping>,\n    /// Options for mouse interaction on the bar\n    pub mouse: Option<MouseConfig>,\n    /// Left side widgets (ordered left-to-right)\n    pub left_widgets: Vec<WidgetConfig>,\n    /// Center widgets (ordered left-to-right)\n    pub center_widgets: Option<Vec<WidgetConfig>>,\n    /// Right side widgets (ordered left-to-right)\n    pub right_widgets: Vec<WidgetConfig>,\n}\n\nimpl KomobarConfig {\n    pub fn aliases(raw: &str) {\n        let mut map = HashMap::new();\n        map.insert(\"position\", [\"viewport\"]);\n        map.insert(\"end\", [\"inner_frame\"]);\n\n        let mut display = false;\n\n        for aliases in map.values() {\n            for a in aliases {\n                if raw.contains(a) {\n                    display = true;\n                }\n            }\n        }\n\n        if display {\n            println!(\n                \"\\nYour bar configuration file contains some options that have been renamed or deprecated:\\n\"\n            );\n            for (canonical, aliases) in map {\n                for alias in aliases {\n                    if raw.contains(alias) {\n                        println!(r#\"\"{alias}\" is now \"{canonical}\"\"#);\n                    }\n                }\n            }\n        }\n    }\n\n    pub fn show_all_icons_on_komorebi_workspace(widgets: &[WidgetConfig]) -> bool {\n        widgets\n            .iter()\n            .any(|w| matches!(w, WidgetConfig::Komorebi(config) if config.workspaces.is_some_and(|w| w.enable && w.display.is_some_and(|s| matches!(s,\n            WorkspacesDisplayFormat::AllIcons\n            | WorkspacesDisplayFormat::AllIconsAndText\n            | WorkspacesDisplayFormat::AllIconsAndTextOnSelected)))))\n    }\n}\n\n#[derive(Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Position configuration\npub struct PositionConfig {\n    /// The desired starting position of the bar (0,0 = top left of the screen)\n    #[serde(alias = \"position\")]\n    pub start: Option<Position>,\n    /// The desired size of the bar from the starting position (usually monitor width x desired height)\n    #[serde(alias = \"inner_size\")]\n    pub end: Option<Position>,\n}\n\n#[derive(Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Frame configuration\npub struct FrameConfig {\n    /// Margin inside the painted frame\n    pub inner_margin: Position,\n}\n\n#[derive(Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n#[serde(untagged)]\n/// Monitor configuration or monitor index\npub enum MonitorConfigOrIndex {\n    /// The monitor index where you want the bar to show\n    Index(usize),\n    /// The full monitor options with the index and an optional work_area_offset\n    MonitorConfig(MonitorConfig),\n}\n\n#[derive(Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Monitor configuration\npub struct MonitorConfig {\n    /// Komorebi monitor index of the monitor on which to render the bar\n    pub index: usize,\n    /// Automatically apply a work area offset for this monitor to accommodate the bar\n    pub work_area_offset: Option<Rect>,\n}\n\npub type Padding = SpacingKind;\npub type Margin = SpacingKind;\n\n#[derive(Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n#[serde(untagged)]\n// WARNING: To any developer messing with this code in the future: The order here matters!\n// `Grouped` needs to come last, otherwise serde might mistaken an `IndividualSpacingConfig` for a\n// `GroupedSpacingConfig` with both `vertical` and `horizontal` set to `None` ignoring the\n// individual values.\n/// Spacing kind\npub enum SpacingKind {\n    /// Spacing applied to all sides\n    All(f32),\n    /// Individual spacing applied to each side\n    Individual(IndividualSpacingConfig),\n    /// Grouped vertical and horizontal spacing\n    Grouped(GroupedSpacingConfig),\n}\n\nimpl SpacingKind {\n    pub fn to_individual(&self, default: f32) -> IndividualSpacingConfig {\n        match self {\n            SpacingKind::All(m) => IndividualSpacingConfig::all(*m),\n            SpacingKind::Grouped(grouped_spacing_config) => {\n                let vm = grouped_spacing_config.vertical.as_ref().map_or(\n                    IndividualSpacingConfig::vertical(default),\n                    |vm| match vm {\n                        GroupedSpacingOptions::Symmetrical(m) => {\n                            IndividualSpacingConfig::vertical(*m)\n                        }\n                        GroupedSpacingOptions::Split(tm, bm) => {\n                            IndividualSpacingConfig::vertical(*tm).bottom(*bm)\n                        }\n                    },\n                );\n                let hm = grouped_spacing_config.horizontal.as_ref().map_or(\n                    IndividualSpacingConfig::horizontal(default),\n                    |hm| match hm {\n                        GroupedSpacingOptions::Symmetrical(m) => {\n                            IndividualSpacingConfig::horizontal(*m)\n                        }\n                        GroupedSpacingOptions::Split(lm, rm) => {\n                            IndividualSpacingConfig::horizontal(*lm).right(*rm)\n                        }\n                    },\n                );\n                IndividualSpacingConfig {\n                    top: vm.top,\n                    bottom: vm.bottom,\n                    left: hm.left,\n                    right: hm.right,\n                }\n            }\n            SpacingKind::Individual(m) => *m,\n        }\n    }\n}\n\n#[derive(Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Grouped vertical and horizontal spacing\npub struct GroupedSpacingConfig {\n    /// Vertical grouped spacing\n    pub vertical: Option<GroupedSpacingOptions>,\n    /// Horizontal grouped spacing\n    pub horizontal: Option<GroupedSpacingOptions>,\n}\n\n#[derive(Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n#[serde(untagged)]\n/// Grouped spacing options\npub enum GroupedSpacingOptions {\n    /// Symmetrical grouped spacing\n    Symmetrical(f32),\n    /// Split grouped spacing\n    Split(f32, f32),\n}\n\n#[derive(Copy, Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Individual spacing configuration\npub struct IndividualSpacingConfig {\n    /// Spacing for the top\n    pub top: f32,\n    /// Spacing for the bottom\n    pub bottom: f32,\n    /// Spacing for the left\n    pub left: f32,\n    /// Spacing for the right\n    pub right: f32,\n}\n\n#[allow(dead_code)]\nimpl IndividualSpacingConfig {\n    pub const ZERO: Self = IndividualSpacingConfig {\n        top: 0.0,\n        bottom: 0.0,\n        left: 0.0,\n        right: 0.0,\n    };\n\n    pub fn all(value: f32) -> Self {\n        IndividualSpacingConfig {\n            top: value,\n            bottom: value,\n            left: value,\n            right: value,\n        }\n    }\n\n    pub fn horizontal(value: f32) -> Self {\n        IndividualSpacingConfig {\n            top: 0.0,\n            bottom: 0.0,\n            left: value,\n            right: value,\n        }\n    }\n\n    pub fn vertical(value: f32) -> Self {\n        IndividualSpacingConfig {\n            top: value,\n            bottom: value,\n            left: 0.0,\n            right: 0.0,\n        }\n    }\n\n    pub fn top(self, value: f32) -> Self {\n        IndividualSpacingConfig { top: value, ..self }\n    }\n\n    pub fn bottom(self, value: f32) -> Self {\n        IndividualSpacingConfig {\n            bottom: value,\n            ..self\n        }\n    }\n\n    pub fn left(self, value: f32) -> Self {\n        IndividualSpacingConfig {\n            left: value,\n            ..self\n        }\n    }\n\n    pub fn right(self, value: f32) -> Self {\n        IndividualSpacingConfig {\n            right: value,\n            ..self\n        }\n    }\n}\n\npub fn get_individual_spacing(\n    default: f32,\n    spacing: &Option<SpacingKind>,\n) -> IndividualSpacingConfig {\n    spacing\n        .as_ref()\n        .map_or(IndividualSpacingConfig::all(default), |s| {\n            s.to_individual(default)\n        })\n}\n\n#[derive(Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n#[serde(untagged)]\n/// Mouse message\npub enum MouseMessage {\n    /// Send a message to the komorebi client.\n    /// By default, a batch of messages are sent in the following order:\n    /// FocusMonitorAtCursor =>\n    /// MouseFollowsFocus(false) =>\n    /// {message} =>\n    /// MouseFollowsFocus({original.value})\n    ///\n    /// Example:\n    /// ```json\n    /// \"on_extra2_click\": {\n    ///   \"message\": {\n    ///     \"type\": \"NewWorkspace\"\n    ///   }\n    /// },\n    /// ```\n    /// or:\n    /// ```json\n    /// \"on_middle_click\": {\n    ///   \"focus_monitor_at_cursor\": false,\n    ///   \"ignore_mouse_follows_focus\": false,\n    ///   \"message\": {\n    ///     \"type\": \"TogglePause\"\n    ///   }\n    /// }\n    /// ```\n    /// or:\n    /// ```json\n    /// \"on_scroll_up\": {\n    ///   \"message\": {\n    ///     \"type\": \"CycleFocusWorkspace\",\n    ///     \"content\": \"Previous\"\n    ///   }\n    /// }\n    /// ```\n    Komorebi(KomorebiMouseMessage),\n    /// Execute a custom command.\n    /// CMD (%variable%), Bash ($variable) and PowerShell ($Env:variable) variables will be resolved.\n    /// Example: `komorebic toggle-pause`\n    Command(String),\n}\n\n#[derive(Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Komorebi socket mouse message\npub struct KomorebiMouseMessage {\n    /// Send the FocusMonitorAtCursor message\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = true)))]\n    pub focus_monitor_at_cursor: Option<bool>,\n    /// Wrap the {message} with a MouseFollowsFocus(false) and MouseFollowsFocus({original.value}) message\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = true)))]\n    pub ignore_mouse_follows_focus: Option<bool>,\n    /// The message to send to the komorebi client\n    pub message: komorebi_client::SocketMessage,\n}\n\n#[derive(Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Mouse configuration\npub struct MouseConfig {\n    /// Command to send on primary/left double button click\n    pub on_primary_double_click: Option<MouseMessage>,\n    /// Command to send on secondary/right button click\n    pub on_secondary_click: Option<MouseMessage>,\n    /// Command to send on middle button click\n    pub on_middle_click: Option<MouseMessage>,\n    /// Command to send on extra1/back button click\n    pub on_extra1_click: Option<MouseMessage>,\n    /// Command to send on extra2/forward button click\n    pub on_extra2_click: Option<MouseMessage>,\n\n    /// Defines how many points a user needs to scroll vertically to make a \"tick\" on a mouse/touchpad/touchscreen\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = 30.0)))]\n    pub vertical_scroll_threshold: Option<f32>,\n    /// Command to send on scrolling up (every tick)\n    pub on_scroll_up: Option<MouseMessage>,\n    /// Command to send on scrolling down (every tick)\n    pub on_scroll_down: Option<MouseMessage>,\n\n    /// Defines how many points a user needs to scroll horizontally to make a \"tick\" on a mouse/touchpad/touchscreen\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = 30.0)))]\n    pub horizontal_scroll_threshold: Option<f32>,\n    /// Command to send on scrolling left (every tick)\n    pub on_scroll_left: Option<MouseMessage>,\n    /// Command to send on scrolling right (every tick)\n    pub on_scroll_right: Option<MouseMessage>,\n}\n\nimpl MouseConfig {\n    pub fn has_command(&self) -> bool {\n        [\n            &self.on_primary_double_click,\n            &self.on_secondary_click,\n            &self.on_middle_click,\n            &self.on_extra1_click,\n            &self.on_extra2_click,\n            &self.on_scroll_up,\n            &self.on_scroll_down,\n            &self.on_scroll_left,\n            &self.on_scroll_right,\n        ]\n        .iter()\n        .any(|opt| matches!(opt, Some(MouseMessage::Command(_))))\n    }\n}\n\nimpl MouseMessage {\n    pub fn execute(&self, mouse_follows_focus: bool) {\n        match self {\n            MouseMessage::Komorebi(config) => {\n                let mut messages = Vec::new();\n\n                if config.focus_monitor_at_cursor.unwrap_or(true) {\n                    messages.push(SocketMessage::FocusMonitorAtCursor);\n                }\n\n                if config.ignore_mouse_follows_focus.unwrap_or(true) {\n                    messages.push(SocketMessage::MouseFollowsFocus(false));\n                    messages.push(config.message.clone());\n                    messages.push(SocketMessage::MouseFollowsFocus(mouse_follows_focus));\n                } else {\n                    messages.push(config.message.clone());\n                }\n\n                tracing::debug!(\"Sending messages: {messages:?}\");\n\n                if komorebi_client::send_batch(messages).is_err() {\n                    tracing::error!(\"could not send commands\");\n                }\n            }\n            MouseMessage::Command(cmd) => {\n                tracing::debug!(\"Executing command: {}\", cmd);\n\n                let cmd_no_env = cmd.replace_env();\n\n                if exec_powershell(cmd_no_env.to_str().expect(\"Invalid command\")).is_err() {\n                    tracing::error!(\"Failed to execute '{}'\", cmd);\n                }\n            }\n        };\n    }\n}\n\nimpl KomobarConfig {\n    pub fn read(path: &PathBuf) -> color_eyre::Result<Self> {\n        let content = std::fs::read_to_string(path)?;\n        let mut value: Self = match path.extension().unwrap().to_string_lossy().as_str() {\n            \"json\" => serde_json::from_str(&content)?,\n            _ => panic!(\"unsupported format\"),\n        };\n\n        if value.frame.is_none() {\n            value.frame = Some(FrameConfig {\n                inner_margin: Position {\n                    x: DEFAULT_PADDING,\n                    y: DEFAULT_PADDING,\n                },\n            });\n        }\n\n        Ok(value)\n    }\n}\n\n#[derive(Copy, Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Position\npub struct Position {\n    /// X coordinate\n    pub x: f32,\n    /// Y coordinate\n    pub y: f32,\n}\n\nimpl From<Position> for Vec2 {\n    fn from(value: Position) -> Self {\n        Self {\n            x: value.x,\n            y: value.y,\n        }\n    }\n}\n\nimpl From<Position> for Pos2 {\n    fn from(value: Position) -> Self {\n        Self {\n            x: value.x,\n            y: value.y,\n        }\n    }\n}\n\npub use komorebi_themes::KomobarTheme;\n\n#[derive(Copy, Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Label prefix\npub enum LabelPrefix {\n    /// Show no prefix\n    None,\n    /// Show an icon\n    Icon,\n    /// Show text\n    Text,\n    /// Show an icon and text\n    IconAndText,\n}\n\n#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Display format\npub enum DisplayFormat {\n    /// Show only icon\n    Icon,\n    /// Show only text\n    Text,\n    /// Show an icon and text for the selected element, and text on the rest\n    TextAndIconOnSelected,\n    /// Show both icon and text\n    IconAndText,\n    /// Show an icon and text for the selected element, and icons on the rest\n    IconAndTextOnSelected,\n}\n\nmacro_rules! extend_enum {\n    ($(#[$type_meta:meta])* $existing_enum:ident, $new_enum:ident, { $($(#[$meta:meta])* $variant:ident),* $(,)? }) => {\n        #[derive(Copy, Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq)]\n        #[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n        $(#[$type_meta])*\n        pub enum $new_enum {\n            // Add new variants\n            $(\n                $(#[$meta])*\n                $variant,\n            )*\n            // Include a variant that wraps the existing enum and flatten it when deserializing\n            #[serde(untagged)]\n            Existing($existing_enum),\n        }\n\n        // Implement From for the existing enum\n        impl From<$existing_enum> for $new_enum {\n            fn from(value: $existing_enum) -> Self {\n                $new_enum::Existing(value)\n            }\n        }\n    };\n}\n\nextend_enum!(\n    /// Workspaces display format\n    DisplayFormat, WorkspacesDisplayFormat, {\n    /// Show all icons only\n    AllIcons,\n    /// Show both all icons and text\n    AllIconsAndText,\n    /// Show all icons and text for the selected element, and all icons on the rest\n    AllIconsAndTextOnSelected,\n});\n\n#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Media widget display format\npub enum MediaDisplayFormat {\n    /// Show only the media info icon\n    Icon,\n    /// Show only the media info text (artist - title)\n    Text,\n    /// Show both icon and text\n    IconAndText,\n    /// Show only the control buttons (previous, play/pause, next)\n    ControlsOnly,\n    /// Show icon with control buttons\n    IconAndControls,\n    /// Show text with control buttons\n    TextAndControls,\n    /// Show icon, text, and control buttons\n    Full,\n}\n\n#[cfg(test)]\nmod tests {\n    use serde::Deserialize;\n    use serde::Serialize;\n    use serde_json::json;\n\n    #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]\n    #[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n    pub enum OriginalDisplayFormat {\n        /// Show None Of The Things\n        NoneOfTheThings,\n    }\n\n    extend_enum!(OriginalDisplayFormat, ExtendedDisplayFormat, {\n        /// Show Some Of The Things\n        SomeOfTheThings,\n    });\n\n    #[derive(serde::Deserialize)]\n    struct ExampleConfig {\n        #[allow(unused)]\n        format: ExtendedDisplayFormat,\n    }\n\n    #[test]\n    pub fn extend_new_variant() {\n        let raw = json!({\n            \"format\": \"SomeOfTheThings\",\n        })\n        .to_string();\n\n        assert!(serde_json::from_str::<ExampleConfig>(&raw).is_ok())\n    }\n\n    #[test]\n    pub fn extend_existing_variant() {\n        let raw = json!({\n            \"format\": \"NoneOfTheThings\",\n        })\n        .to_string();\n\n        assert!(serde_json::from_str::<ExampleConfig>(&raw).is_ok())\n    }\n\n    #[test]\n    pub fn extend_invalid_variant() {\n        let raw = json!({\n            \"format\": \"ALLOFTHETHINGS\",\n        })\n        .to_string();\n\n        assert!(serde_json::from_str::<ExampleConfig>(&raw).is_err())\n    }\n}\n"
  },
  {
    "path": "komorebi-bar/src/main.rs",
    "content": "mod bar;\nmod config;\nmod render;\nmod selected_frame;\nmod ui;\nmod widgets;\n\nuse crate::bar::Komobar;\nuse crate::config::KomobarConfig;\nuse crate::config::Position;\nuse crate::config::PositionConfig;\nuse clap::Parser;\nuse config::MonitorConfigOrIndex;\nuse eframe::egui::ViewportBuilder;\nuse font_loader::system_fonts;\nuse hotwatch::EventKind;\nuse hotwatch::Hotwatch;\nuse komorebi_client::PathExt;\nuse komorebi_client::SocketMessage;\nuse komorebi_client::SubscribeOptions;\nuse komorebi_client::replace_env_in_path;\nuse std::io::BufReader;\nuse std::io::Read;\nuse std::path::PathBuf;\nuse std::sync::atomic::AtomicI32;\nuse std::sync::atomic::AtomicU32;\nuse std::sync::atomic::AtomicUsize;\nuse std::sync::atomic::Ordering;\nuse std::time::Duration;\nuse tracing_subscriber::EnvFilter;\nuse windows::Win32::Foundation::HWND;\nuse windows::Win32::Foundation::LPARAM;\nuse windows::Win32::System::Threading::GetCurrentProcessId;\nuse windows::Win32::System::Threading::GetCurrentThreadId;\nuse windows::Win32::UI::HiDpi::DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2;\nuse windows::Win32::UI::HiDpi::SetProcessDpiAwarenessContext;\nuse windows::Win32::UI::WindowsAndMessaging::EnumThreadWindows;\nuse windows::Win32::UI::WindowsAndMessaging::GetWindowThreadProcessId;\nuse windows_core::BOOL;\n\npub static MAX_LABEL_WIDTH: AtomicI32 = AtomicI32::new(400);\npub static MONITOR_LEFT: AtomicI32 = AtomicI32::new(0);\npub static MONITOR_TOP: AtomicI32 = AtomicI32::new(0);\npub static MONITOR_RIGHT: AtomicI32 = AtomicI32::new(0);\npub static MONITOR_INDEX: AtomicUsize = AtomicUsize::new(0);\npub static BAR_HEIGHT: f32 = 50.0;\npub static DEFAULT_PADDING: f32 = 10.0;\n\npub static AUTO_SELECT_FILL_COLOUR: AtomicU32 = AtomicU32::new(0);\npub static AUTO_SELECT_TEXT_COLOUR: AtomicU32 = AtomicU32::new(0);\n\n#[derive(Parser)]\n#[clap(author, about, version)]\nstruct Opts {\n    /// Print the JSON schema of the configuration file and exit\n    #[clap(long)]\n    schema: bool,\n    /// Print a list of fonts available on this system and exit\n    #[clap(long)]\n    fonts: bool,\n    /// Path to a JSON or YAML configuration file\n    #[clap(short, long)]\n    #[clap(value_parser = replace_env_in_path)]\n    config: Option<PathBuf>,\n    /// Write an example komorebi.bar.json to disk\n    #[clap(long)]\n    quickstart: bool,\n    /// Print a list of aliases that can be renamed to canonical variants\n    #[clap(long)]\n    #[clap(hide = true)]\n    aliases: bool,\n}\n\nextern \"system\" fn enum_window(hwnd: HWND, lparam: LPARAM) -> BOOL {\n    unsafe {\n        let mut process_id = 0;\n        GetWindowThreadProcessId(hwnd, Some(&mut process_id));\n\n        if process_id == GetCurrentProcessId() {\n            *(lparam.0 as *mut HWND) = hwnd;\n            BOOL::from(false) // Stop enumeration\n        } else {\n            BOOL::from(true) // Continue enumeration\n        }\n    }\n}\n\nfn process_hwnd() -> Option<isize> {\n    unsafe {\n        let mut hwnd = HWND::default();\n        let _ = EnumThreadWindows(\n            GetCurrentThreadId(),\n            Some(enum_window),\n            LPARAM(&mut hwnd as *mut HWND as isize),\n        );\n\n        if hwnd.0 as isize == 0 {\n            None\n        } else {\n            Some(hwnd.0 as isize)\n        }\n    }\n}\n\npub enum KomorebiEvent {\n    Notification(Box<komorebi_client::Notification>),\n    Reconnect,\n}\n\nfn main() -> color_eyre::Result<()> {\n    unsafe { SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) }?;\n\n    let opts: Opts = Opts::parse();\n\n    #[cfg(feature = \"schemars\")]\n    if opts.schema {\n        let bar_config = schemars::schema_for!(KomobarConfig);\n        let schema = serde_json::to_string_pretty(&bar_config)?;\n\n        println!(\"{schema}\");\n        std::process::exit(0);\n    }\n\n    if opts.fonts {\n        for font in system_fonts::query_all() {\n            println!(\"{font}\");\n        }\n\n        std::process::exit(0);\n    }\n\n    if std::env::var(\"RUST_LIB_BACKTRACE\").is_err() {\n        unsafe {\n            std::env::set_var(\"RUST_LIB_BACKTRACE\", \"1\");\n        }\n    }\n\n    color_eyre::install()?;\n\n    if std::env::var(\"RUST_LOG\").is_err() {\n        unsafe {\n            std::env::set_var(\"RUST_LOG\", \"info\");\n        }\n    }\n\n    tracing::subscriber::set_global_default(\n        tracing_subscriber::fmt::Subscriber::builder()\n            .with_env_filter(EnvFilter::from_default_env())\n            .finish(),\n    )?;\n\n    let home_dir: PathBuf = std::env::var(\"KOMOREBI_CONFIG_HOME\").map_or_else(\n        |_| dirs::home_dir().expect(\"there is no home directory\"),\n        |home_path| {\n            let home = home_path.replace_env();\n\n            assert!(\n                home.is_dir(),\n                \"$Env:KOMOREBI_CONFIG_HOME is set to '{home_path}', which is not a valid directory\"\n            );\n\n            home\n        },\n    );\n\n    if opts.quickstart {\n        let komorebi_bar_json = include_str!(\"../../docs/komorebi.bar.example.json\").to_string();\n        std::fs::write(home_dir.join(\"komorebi.bar.json\"), komorebi_bar_json)?;\n        println!(\n            \"Example komorebi.bar.json file written to {}\",\n            home_dir.display()\n        );\n\n        std::process::exit(0);\n    }\n\n    let default_config_path = home_dir.join(\"komorebi.bar.json\");\n\n    let config_path = opts.config.or_else(|| {\n        default_config_path\n            .is_file()\n            .then_some(default_config_path.clone())\n    });\n\n    let mut config = match config_path {\n        None => {\n            let komorebi_bar_json =\n                include_str!(\"../../docs/komorebi.bar.example.json\").to_string();\n\n            std::fs::write(&default_config_path, komorebi_bar_json)?;\n            tracing::info!(\n                \"created example configuration file: {}\",\n                default_config_path.display()\n            );\n\n            KomobarConfig::read(&default_config_path)?\n        }\n        Some(ref config) => {\n            if !opts.aliases {\n                tracing::info!(\"found configuration file: {}\", config.display());\n            }\n\n            KomobarConfig::read(config)?\n        }\n    };\n\n    let config_path = config_path.unwrap_or(default_config_path);\n\n    if opts.aliases {\n        KomobarConfig::aliases(&std::fs::read_to_string(&config_path)?);\n        std::process::exit(0);\n    }\n\n    let state = serde_json::from_str::<komorebi_client::State>(&komorebi_client::send_query(\n        &SocketMessage::State,\n    )?)?;\n\n    let (usr_monitor_index, work_area_offset) = match &config.monitor {\n        Some(MonitorConfigOrIndex::MonitorConfig(monitor_config)) => {\n            (monitor_config.index, monitor_config.work_area_offset)\n        }\n        Some(MonitorConfigOrIndex::Index(idx)) => (*idx, None),\n        None => (0, None),\n    };\n\n    let monitor_index = state\n        .monitor_usr_idx_map\n        .get(&usr_monitor_index)\n        .map_or(usr_monitor_index, |i| *i);\n\n    MONITOR_RIGHT.store(\n        state.monitors.elements()[monitor_index].size.right,\n        Ordering::SeqCst,\n    );\n\n    MONITOR_TOP.store(\n        state.monitors.elements()[monitor_index].size.top,\n        Ordering::SeqCst,\n    );\n\n    MONITOR_LEFT.store(\n        state.monitors.elements()[monitor_index].size.left,\n        Ordering::SeqCst,\n    );\n\n    MONITOR_INDEX.store(monitor_index, Ordering::SeqCst);\n\n    match config.position {\n        None => {\n            config.position = Some(PositionConfig {\n                start: Some(Position {\n                    x: state.monitors.elements()[monitor_index].size.left as f32,\n                    y: state.monitors.elements()[monitor_index].size.top as f32,\n                }),\n                end: Some(Position {\n                    x: state.monitors.elements()[monitor_index].size.right as f32,\n                    y: 50.0,\n                }),\n            })\n        }\n        Some(ref mut position) => {\n            if position.start.is_none() {\n                position.start = Some(Position {\n                    x: state.monitors.elements()[monitor_index].size.left as f32,\n                    y: state.monitors.elements()[monitor_index].size.top as f32,\n                });\n            }\n\n            if position.end.is_none() {\n                position.end = Some(Position {\n                    x: state.monitors.elements()[monitor_index].size.right as f32,\n                    y: 50.0,\n                })\n            }\n        }\n    }\n\n    let viewport_builder = ViewportBuilder::default()\n        .with_decorations(false)\n        .with_transparent(true)\n        .with_taskbar(false);\n\n    let native_options = eframe::NativeOptions {\n        viewport: viewport_builder,\n        ..Default::default()\n    };\n\n    if let Some(rect) = &work_area_offset {\n        komorebi_client::send_message(&SocketMessage::MonitorWorkAreaOffset(monitor_index, *rect))?;\n        tracing::info!(\"work area offset applied to monitor: {}\", monitor_index);\n    }\n\n    let (tx_gui, rx_gui) = crossbeam_channel::unbounded();\n    let (tx_config, rx_config) = crossbeam_channel::unbounded();\n\n    let mut hotwatch = Hotwatch::new()?;\n    let config_path_cl = config_path.clone();\n\n    hotwatch.watch(config_path, move |event| match event.kind {\n        EventKind::Modify(_) | EventKind::Remove(_) => match KomobarConfig::read(&config_path_cl) {\n            Ok(updated) => {\n                tracing::info!(\"configuration file updated: {}\", config_path_cl.display());\n\n                if let Err(error) = tx_config.send(updated) {\n                    tracing::error!(\"could not send configuration update to gui: {error}\")\n                }\n            }\n            Err(error) => {\n                tracing::error!(\"{error}\");\n            }\n        },\n        _ => {}\n    })?;\n\n    tracing::info!(\"watching configuration file for changes\");\n\n    eframe::run_native(\n        \"komorebi-bar\",\n        native_options,\n        Box::new(|cc| {\n            let ctx_repainter = cc.egui_ctx.clone();\n            std::thread::spawn(move || loop {\n                std::thread::sleep(Duration::from_secs(1));\n                ctx_repainter.request_repaint();\n            });\n\n            let ctx_komorebi = cc.egui_ctx.clone();\n            std::thread::spawn(move || {\n                let subscriber_name = format!(\"komorebi-bar-{}\", random_word::get(random_word::Lang::En));\n\n                let listener = komorebi_client::subscribe_with_options(&subscriber_name, SubscribeOptions {\n                    filter_state_changes: true,\n                })\n                    .expect(\"could not subscribe to komorebi notifications\");\n\n                tracing::info!(\"subscribed to komorebi notifications: \\\"{}\\\"\", subscriber_name);\n\n                for client in listener.incoming() {\n                    match client {\n                        Ok(subscription) => {\n                            match subscription.set_read_timeout(Some(Duration::from_secs(1))) {\n                                Ok(()) => {}\n                                Err(error) => tracing::error!(\"{}\", error),\n                            }\n                            let mut buffer = Vec::new();\n                            let mut reader = BufReader::new(subscription);\n\n                            // this is when we know a shutdown has been sent\n                            if matches!(reader.read_to_end(&mut buffer), Ok(0)) {\n                                tracing::info!(\"disconnected from komorebi\");\n\n                                // keep trying to reconnect to komorebi\n                                while komorebi_client::send_message(\n                                    &SocketMessage::AddSubscriberSocket(subscriber_name.clone()),\n                                )\n                                    .is_err()\n                                {\n                                    std::thread::sleep(Duration::from_secs(1));\n                                }\n\n                                tracing::info!(\"reconnected to komorebi\");\n\n                                if let Err(error) = tx_gui.send(KomorebiEvent::Reconnect) {\n                                    tracing::error!(\"could not send komorebi reconnect event to gui thread: {error}\")\n                                }\n\n                                ctx_komorebi.request_repaint();\n                                continue;\n                            }\n\n                            match String::from_utf8(buffer) {\n                                Ok(notification_string) => {\n                                    match serde_json::from_str::<komorebi_client::Notification>(\n                                        &notification_string,\n                                    ) {\n                                        Ok(notification) => {\n                                            tracing::debug!(\"received notification from komorebi\");\n\n                                            if let Err(error) = tx_gui.send(KomorebiEvent::Notification(Box::new(notification))) {\n                                                tracing::error!(\"could not send komorebi notification update to gui thread: {error}\")\n                                            }\n\n                                            ctx_komorebi.request_repaint();\n                                        }\n                                        Err(error) => {\n                                            tracing::error!(\"could not deserialize komorebi notification: {error}\");\n                                        }\n                                    }\n                                }\n                                Err(error) => {\n                                    tracing::error!(\n                                        \"komorebi notification string was invalid utf8: {error}\"\n                                    )\n                                }\n                            }\n                        }\n                        Err(error) => {\n                            tracing::error!(\"{error}\");\n                        }\n                    }\n                }\n            });\n\n            Ok(Box::new(Komobar::new(cc, rx_gui, rx_config, config)))\n        }),\n    )\n        .map_err(|error| color_eyre::eyre::Error::msg(error.to_string()))\n}\n"
  },
  {
    "path": "komorebi-bar/src/render.rs",
    "content": "use crate::AUTO_SELECT_FILL_COLOUR;\nuse crate::AUTO_SELECT_TEXT_COLOUR;\nuse crate::bar::Alignment;\nuse crate::config::KomobarConfig;\nuse crate::config::MonitorConfigOrIndex;\nuse eframe::egui::Color32;\nuse eframe::egui::Context;\nuse eframe::egui::CornerRadius;\nuse eframe::egui::FontId;\nuse eframe::egui::Frame;\nuse eframe::egui::InnerResponse;\nuse eframe::egui::Margin;\nuse eframe::egui::Shadow;\nuse eframe::egui::TextStyle;\nuse eframe::egui::Ui;\nuse komorebi_client::Colour;\nuse komorebi_client::Rgb;\nuse serde::Deserialize;\nuse serde::Serialize;\nuse std::num::NonZeroU32;\nuse std::sync::Arc;\nuse std::sync::atomic::AtomicUsize;\nuse std::sync::atomic::Ordering;\n\nstatic SHOW_KOMOREBI_LAYOUT_OPTIONS: AtomicUsize = AtomicUsize::new(0);\n\n#[derive(Copy, Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n#[serde(tag = \"kind\")]\n/// Grouping\npub enum Grouping {\n    /// No grouping is applied\n    #[cfg_attr(feature = \"schemars\", schemars(title = \"None\"))]\n    None,\n    /// Widgets are grouped as a whole\n    #[cfg_attr(feature = \"schemars\", schemars(title = \"Bar\"))]\n    Bar(GroupingConfig),\n    /// Widgets are grouped by alignment\n    #[cfg_attr(feature = \"schemars\", schemars(title = \"Alignment\"))]\n    Alignment(GroupingConfig),\n    /// Widgets are grouped individually\n    #[cfg_attr(feature = \"schemars\", schemars(title = \"Widget\"))]\n    Widget(GroupingConfig),\n}\n\n#[derive(Clone)]\n/// Render configuration\npub struct RenderConfig {\n    /// Komorebi monitor index of the monitor on which to render the bar\n    pub monitor_idx: usize,\n    /// Spacing between widgets\n    pub spacing: f32,\n    /// Sets how widgets are grouped\n    pub grouping: Grouping,\n    /// Background color\n    pub background_color: Color32,\n    /// Alignment of the widgets\n    pub alignment: Option<Alignment>,\n    /// Add more inner margin when adding a widget group\n    pub more_inner_margin: bool,\n    /// Set to true after the first time the apply_on_widget was called on an alignment\n    pub applied_on_widget: bool,\n    /// FontId for text\n    pub text_font_id: FontId,\n    /// FontId for icon (based on scaling the text font id)\n    pub icon_font_id: FontId,\n    /// Show all icons on the workspace section of the Komorebi widget\n    pub show_all_icons: bool,\n    /// Background color of the selected frame\n    pub auto_select_fill: Option<Color32>,\n    /// Text color of the selected frame\n    pub auto_select_text: Option<Color32>,\n}\n\npub trait RenderExt {\n    fn new_renderconfig(\n        &self,\n        ctx: &Context,\n        background_color: Color32,\n        icon_scale: Option<f32>,\n    ) -> RenderConfig;\n}\n\nimpl RenderExt for &KomobarConfig {\n    fn new_renderconfig(\n        &self,\n        ctx: &Context,\n        background_color: Color32,\n        icon_scale: Option<f32>,\n    ) -> RenderConfig {\n        let text_font_id = ctx\n            .style()\n            .text_styles\n            .get(&TextStyle::Body)\n            .cloned()\n            .unwrap_or_else(FontId::default);\n\n        let mut icon_font_id = text_font_id.clone();\n        icon_font_id.size *= icon_scale.unwrap_or(1.4).clamp(1.0, 2.0);\n\n        let monitor_idx = match &self.monitor {\n            Some(MonitorConfigOrIndex::MonitorConfig(monitor_config)) => monitor_config.index,\n            Some(MonitorConfigOrIndex::Index(idx)) => *idx,\n            None => 0,\n        };\n\n        // check if any of the alignments have a komorebi widget with the workspace set to show all icons\n        let show_all_icons =\n            KomobarConfig::show_all_icons_on_komorebi_workspace(&self.left_widgets)\n                || self\n                    .center_widgets\n                    .as_ref()\n                    .is_some_and(|list| KomobarConfig::show_all_icons_on_komorebi_workspace(list))\n                || KomobarConfig::show_all_icons_on_komorebi_workspace(&self.right_widgets);\n\n        RenderConfig {\n            monitor_idx,\n            spacing: self.widget_spacing.unwrap_or(10.0),\n            grouping: self.grouping.unwrap_or(Grouping::None),\n            background_color,\n            alignment: None,\n            more_inner_margin: false,\n            applied_on_widget: false,\n            text_font_id,\n            icon_font_id,\n            show_all_icons,\n            auto_select_fill: NonZeroU32::new(AUTO_SELECT_FILL_COLOUR.load(Ordering::SeqCst))\n                .map(|c| Colour::Rgb(Rgb::from(c.get())).into()),\n            auto_select_text: NonZeroU32::new(AUTO_SELECT_TEXT_COLOUR.load(Ordering::SeqCst))\n                .map(|c| Colour::Rgb(Rgb::from(c.get())).into()),\n        }\n    }\n}\n\nimpl RenderConfig {\n    pub fn load_show_komorebi_layout_options() -> bool {\n        SHOW_KOMOREBI_LAYOUT_OPTIONS.load(Ordering::SeqCst) != 0\n    }\n\n    pub fn store_show_komorebi_layout_options(show: bool) {\n        SHOW_KOMOREBI_LAYOUT_OPTIONS.store(show as usize, Ordering::SeqCst);\n    }\n\n    pub fn new() -> Self {\n        Self {\n            monitor_idx: 0,\n            spacing: 0.0,\n            grouping: Grouping::None,\n            background_color: Color32::BLACK,\n            alignment: None,\n            more_inner_margin: false,\n            applied_on_widget: false,\n            text_font_id: FontId::default(),\n            icon_font_id: FontId::default(),\n            show_all_icons: false,\n            auto_select_fill: None,\n            auto_select_text: None,\n        }\n    }\n\n    pub fn change_frame_on_bar(\n        &mut self,\n        frame: Frame,\n        ui_style: &Arc<eframe::egui::Style>,\n    ) -> Frame {\n        self.alignment = None;\n\n        if let Grouping::Bar(config) = self.grouping {\n            return self.define_group_frame(\n                //TODO: this outer margin can be a config\n                Some(Margin {\n                    left: 10,\n                    right: 10,\n                    top: 6,\n                    bottom: 6,\n                }),\n                config,\n                ui_style,\n            );\n        }\n\n        frame\n    }\n\n    pub fn apply_on_alignment<R>(\n        &mut self,\n        ui: &mut Ui,\n        add_contents: impl FnOnce(&mut Ui) -> R,\n    ) -> InnerResponse<R> {\n        self.alignment = None;\n\n        if let Grouping::Alignment(config) = self.grouping {\n            return self.define_group(None, config, ui, add_contents);\n        }\n\n        Self::fallback_group(ui, add_contents)\n    }\n\n    pub fn apply_on_widget<R>(\n        &mut self,\n        more_inner_margin: bool,\n        ui: &mut Ui,\n        add_contents: impl FnOnce(&mut Ui) -> R,\n    ) -> InnerResponse<R> {\n        self.more_inner_margin = more_inner_margin;\n        let outer_margin = self.widget_outer_margin(ui);\n\n        if let Grouping::Widget(config) = self.grouping {\n            return self.define_group(Some(outer_margin), config, ui, add_contents);\n        }\n\n        self.fallback_widget_group(Some(outer_margin), ui, add_contents)\n    }\n\n    fn fallback_group<R>(ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> InnerResponse<R> {\n        InnerResponse {\n            inner: add_contents(ui),\n            response: ui.response().clone(),\n        }\n    }\n\n    fn fallback_widget_group<R>(\n        &mut self,\n        outer_margin: Option<Margin>,\n        ui: &mut Ui,\n        add_contents: impl FnOnce(&mut Ui) -> R,\n    ) -> InnerResponse<R> {\n        Frame::NONE\n            .outer_margin(outer_margin.unwrap_or(Margin::ZERO))\n            .inner_margin(match self.more_inner_margin {\n                true => Margin::symmetric(5, 0),\n                false => Margin::same(0),\n            })\n            .show(ui, add_contents)\n    }\n\n    fn define_group<R>(\n        &mut self,\n        outer_margin: Option<Margin>,\n        config: GroupingConfig,\n        ui: &mut Ui,\n        add_contents: impl FnOnce(&mut Ui) -> R,\n    ) -> InnerResponse<R> {\n        self.define_group_frame(outer_margin, config, ui.style())\n            .show(ui, add_contents)\n    }\n\n    pub fn define_group_frame(\n        &mut self,\n        outer_margin: Option<Margin>,\n        config: GroupingConfig,\n        ui_style: &Arc<eframe::egui::Style>,\n    ) -> Frame {\n        Frame::group(ui_style)\n            .outer_margin(outer_margin.unwrap_or(Margin::ZERO))\n            .inner_margin(match self.more_inner_margin {\n                true => Margin::symmetric(6, 1),\n                false => Margin::symmetric(1, 1),\n            })\n            .stroke(ui_style.visuals.widgets.noninteractive.bg_stroke)\n            .corner_radius(match config.rounding {\n                Some(rounding) => rounding.into(),\n                None => ui_style.visuals.widgets.noninteractive.corner_radius,\n            })\n            .fill(\n                self.background_color\n                    .try_apply_alpha(config.transparency_alpha),\n            )\n            .shadow(match config.style {\n                Some(style) => match style {\n                    // new styles can be added if needed here\n                    GroupingStyle::Default => Shadow::NONE,\n                    GroupingStyle::DefaultWithShadowB4O1S3 => Shadow {\n                        blur: 4,\n                        offset: [1, 1],\n                        spread: 3,\n                        color: Color32::BLACK.try_apply_alpha(config.transparency_alpha),\n                    },\n                    GroupingStyle::DefaultWithShadowB4O0S3 => Shadow {\n                        blur: 4,\n                        offset: [0, 0],\n                        spread: 3,\n                        color: Color32::BLACK.try_apply_alpha(config.transparency_alpha),\n                    },\n                    GroupingStyle::DefaultWithShadowB0O1S3 => Shadow {\n                        blur: 0,\n                        offset: [1, 1],\n                        spread: 3,\n                        color: Color32::BLACK.try_apply_alpha(config.transparency_alpha),\n                    },\n                    GroupingStyle::DefaultWithGlowB3O1S2 => Shadow {\n                        blur: 3,\n                        offset: [1, 1],\n                        spread: 2,\n                        color: ui_style\n                            .visuals\n                            .selection\n                            .stroke\n                            .color\n                            .try_apply_alpha(config.transparency_alpha),\n                    },\n                    GroupingStyle::DefaultWithGlowB3O0S2 => Shadow {\n                        blur: 3,\n                        offset: [0, 0],\n                        spread: 2,\n                        color: ui_style\n                            .visuals\n                            .selection\n                            .stroke\n                            .color\n                            .try_apply_alpha(config.transparency_alpha),\n                    },\n                    GroupingStyle::DefaultWithGlowB0O1S2 => Shadow {\n                        blur: 0,\n                        offset: [1, 1],\n                        spread: 2,\n                        color: ui_style\n                            .visuals\n                            .selection\n                            .stroke\n                            .color\n                            .try_apply_alpha(config.transparency_alpha),\n                    },\n                },\n                None => Shadow::NONE,\n            })\n    }\n\n    fn widget_outer_margin(&mut self, ui: &mut Ui) -> Margin {\n        let spacing = if self.applied_on_widget {\n            // Remove the default item spacing from the margin\n            (self.spacing - ui.spacing().item_spacing.x) as i8\n        } else {\n            0\n        };\n\n        if !self.applied_on_widget {\n            self.applied_on_widget = true;\n        }\n\n        Margin {\n            left: match self.alignment {\n                Some(align) => match align {\n                    Alignment::Left => spacing,\n                    Alignment::Center => spacing,\n                    Alignment::Right => 0,\n                },\n                None => 0,\n            },\n            right: match self.alignment {\n                Some(align) => match align {\n                    Alignment::Left => 0,\n                    Alignment::Center => 0,\n                    Alignment::Right => spacing,\n                },\n                None => 0,\n            },\n            top: 0,\n            bottom: 0,\n        }\n    }\n}\n\n#[derive(Copy, Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Grouping configuration\npub struct GroupingConfig {\n    /// Styles for the grouping\n    pub style: Option<GroupingStyle>,\n    /// Alpha value for the color transparency [[0-255]] (default: 200)\n    pub transparency_alpha: Option<u8>,\n    /// Rounding values for the 4 corners. Can be a single or 4 values.\n    pub rounding: Option<RoundingConfig>,\n}\n\n#[derive(Copy, Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Grouping Style\npub enum GroupingStyle {\n    /// Default\n    #[serde(alias = \"CtByte\")]\n    Default,\n    /// A shadow is added under the default group. (blur: 4, offset: x-1 y-1, spread: 3)\n    #[serde(alias = \"CtByteWithShadow\")]\n    #[serde(alias = \"DefaultWithShadow\")]\n    DefaultWithShadowB4O1S3,\n    /// A shadow is added under the default group. (blur: 4, offset: x-0 y-0, spread: 3)\n    DefaultWithShadowB4O0S3,\n    /// A shadow is added under the default group. (blur: 0, offset: x-1 y-1, spread: 3)\n    DefaultWithShadowB0O1S3,\n    /// A glow is added under the default group. (blur: 3, offset: x-1 y-1, spread: 2)\n    DefaultWithGlowB3O1S2,\n    /// A glow is added under the default group. (blur: 3, offset: x-0 y-0, spread: 2)\n    DefaultWithGlowB3O0S2,\n    /// A glow is added under the default group. (blur: 0, offset: x-1 y-1, spread: 2)\n    DefaultWithGlowB0O1S2,\n}\n\n#[derive(Copy, Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n#[serde(untagged)]\n/// Rounding configuration\npub enum RoundingConfig {\n    /// All 4 corners are the same\n    Same(f32),\n    /// All 4 corners are custom. Order: NW, NE, SW, SE\n    Individual([f32; 4]),\n}\n\nimpl From<RoundingConfig> for CornerRadius {\n    fn from(value: RoundingConfig) -> Self {\n        match value {\n            RoundingConfig::Same(value) => Self::same(value as u8),\n            RoundingConfig::Individual(values) => {\n                let values = values.map(|f| f as u8);\n                Self {\n                    nw: values[0],\n                    ne: values[1],\n                    sw: values[2],\n                    se: values[3],\n                }\n            }\n        }\n    }\n}\n\npub trait Color32Ext {\n    fn try_apply_alpha(self, transparency_alpha: Option<u8>) -> Self;\n}\n\nimpl Color32Ext for Color32 {\n    /// Tries to apply the alpha value to the Color32\n    fn try_apply_alpha(self, transparency_alpha: Option<u8>) -> Self {\n        if let Some(alpha) = transparency_alpha {\n            return Color32::from_rgba_unmultiplied(self.r(), self.g(), self.b(), alpha);\n        }\n\n        self\n    }\n}\n"
  },
  {
    "path": "komorebi-bar/src/selected_frame.rs",
    "content": "use eframe::egui::Color32;\nuse eframe::egui::CursorIcon;\nuse eframe::egui::Frame;\nuse eframe::egui::Margin;\nuse eframe::egui::Response;\nuse eframe::egui::Sense;\nuse eframe::egui::Stroke;\nuse eframe::egui::Ui;\n\n/// Same as SelectableLabel, but supports all content\npub struct SelectableFrame {\n    selected: bool,\n    selected_fill: Option<Color32>,\n}\n\nimpl SelectableFrame {\n    pub fn new(selected: bool) -> Self {\n        Self {\n            selected,\n            selected_fill: None,\n        }\n    }\n\n    pub fn new_auto(selected: bool, selected_fill: Option<Color32>) -> Self {\n        Self {\n            selected,\n            selected_fill,\n        }\n    }\n\n    pub fn show<R>(self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> Response {\n        let Self {\n            selected,\n            selected_fill,\n        } = self;\n\n        Frame::NONE\n            .show(ui, |ui| {\n                let response = ui.interact(ui.max_rect(), ui.unique_id(), Sense::click());\n\n                if ui.is_rect_visible(response.rect) {\n                    // take into account the stroke width\n                    let inner_margin = Margin::symmetric(\n                        ui.style().spacing.button_padding.x as i8 - 1,\n                        ui.style().spacing.button_padding.y as i8 - 1,\n                    );\n\n                    // since the stroke is drawn inside the frame, we always reserve space for it\n                    if selected && response.hovered() {\n                        let visuals = ui.style().interact_selectable(&response, selected);\n\n                        Frame::NONE\n                            .stroke(Stroke::new(1.0, visuals.bg_stroke.color))\n                            .corner_radius(visuals.corner_radius)\n                            .fill(selected_fill.unwrap_or(visuals.bg_fill))\n                            .inner_margin(inner_margin)\n                            .show(ui, add_contents);\n                    } else if response.hovered() || response.highlighted() || response.has_focus() {\n                        let visuals = ui.style().interact_selectable(&response, selected);\n\n                        Frame::NONE\n                            .stroke(Stroke::new(1.0, visuals.bg_stroke.color))\n                            .corner_radius(visuals.corner_radius)\n                            .fill(visuals.bg_fill)\n                            .inner_margin(inner_margin)\n                            .show(ui, add_contents);\n                    } else if selected {\n                        let visuals = ui.style().interact_selectable(&response, selected);\n\n                        Frame::NONE\n                            .stroke(Stroke::new(1.0, visuals.bg_fill))\n                            .corner_radius(visuals.corner_radius)\n                            .fill(selected_fill.unwrap_or(visuals.bg_fill))\n                            .inner_margin(inner_margin)\n                            .show(ui, add_contents);\n                    } else {\n                        Frame::NONE\n                            .stroke(Stroke::new(1.0, Color32::TRANSPARENT))\n                            .inner_margin(inner_margin)\n                            .show(ui, add_contents);\n                    }\n                }\n\n                response\n            })\n            .inner\n            .on_hover_cursor(CursorIcon::PointingHand)\n    }\n}\n"
  },
  {
    "path": "komorebi-bar/src/ui.rs",
    "content": "use eframe::egui::Align;\nuse eframe::egui::Layout;\nuse eframe::egui::Response;\nuse eframe::egui::Ui;\nuse eframe::egui::Vec2;\nuse eframe::egui::Widget;\n\npub struct CustomUi<'ui>(pub &'ui mut Ui);\n\nimpl CustomUi<'_> {\n    pub fn add_sized_left_to_right(\n        &mut self,\n        max_size: impl Into<Vec2>,\n        widget: impl Widget,\n    ) -> Response {\n        let layout =\n            Layout::from_main_dir_and_cross_align(self.0.layout().main_dir(), Align::Center);\n        self.0\n            .allocate_ui_with_layout(max_size.into(), layout, |ui| ui.add(widget))\n            .inner\n    }\n}\n"
  },
  {
    "path": "komorebi-bar/src/widgets/applications.rs",
    "content": "use super::ImageIcon;\nuse crate::render::RenderConfig;\nuse crate::selected_frame::SelectableFrame;\nuse crate::widgets::widget::BarWidget;\nuse eframe::egui::Color32;\nuse eframe::egui::Context;\nuse eframe::egui::CornerRadius;\nuse eframe::egui::FontId;\nuse eframe::egui::Frame;\nuse eframe::egui::Image;\nuse eframe::egui::Label;\nuse eframe::egui::Margin;\nuse eframe::egui::RichText;\nuse eframe::egui::Sense;\nuse eframe::egui::Stroke;\nuse eframe::egui::StrokeKind;\nuse eframe::egui::Ui;\nuse eframe::egui::Vec2;\nuse eframe::egui::vec2;\nuse komorebi_client::PathExt;\nuse serde::Deserialize;\nuse serde::Serialize;\nuse std::borrow::Cow;\nuse std::path::Path;\nuse std::process::Command;\nuse std::sync::Arc;\nuse std::time::Duration;\nuse std::time::Instant;\nuse tracing;\nuse which::which;\n\n/// Minimum interval between consecutive application launches to prevent accidental spamming.\nconst MIN_LAUNCH_INTERVAL: Duration = Duration::from_millis(800);\n\n#[derive(Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Applications widget configuration\npub struct ApplicationsConfig {\n    /// Enables or disables the applications widget.\n    pub enable: bool,\n    /// Whether to show the launch command on hover (optional).\n    /// Could be overridden per application. Defaults to `false` if not set.\n    pub show_command_on_hover: Option<bool>,\n    /// Horizontal spacing between application buttons.\n    pub spacing: Option<f32>,\n    /// Default display format for all applications (optional).\n    /// Could be overridden per application. Defaults to `Icon`.\n    pub display: Option<ApplicationsDisplayFormat>,\n    /// List of configured applications to display.\n    pub items: Vec<AppConfig>,\n}\n\n#[derive(Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Application button configuration\npub struct AppConfig {\n    /// Whether to enable this application button (optional).\n    /// Inherits from the global `Applications` setting if omitted.\n    pub enable: Option<bool>,\n    /// Whether to show the launch command on hover (optional).\n    /// Inherits from the global `Applications` setting if omitted.\n    pub show_command_on_hover: Option<bool>,\n    /// Display name of the application.\n    pub name: String,\n    /// Optional icon: a path to an image or a text-based glyph (e.g., from Nerd Fonts).\n    /// If not set, and if the `command` is a path to an executable, an icon might be extracted from it.\n    /// Note: glyphs require a compatible `font_family`.\n    pub icon: Option<String>,\n    /// Command to execute (e.g. path to the application or shell command).\n    pub command: String,\n    /// Display format for this application button (optional). Overrides global format if set.\n    pub display: Option<ApplicationsDisplayFormat>,\n}\n\n#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Default)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Applications widget display format\npub enum ApplicationsDisplayFormat {\n    /// Show only the application icon.\n    #[default]\n    Icon,\n    /// Show only the application name as text.\n    Text,\n    /// Show both the application icon and name.\n    IconAndText,\n}\n\n#[derive(Clone, Debug)]\npub struct Applications {\n    /// Whether the applications widget is enabled.\n    pub enable: bool,\n    /// Horizontal spacing between application buttons.\n    pub spacing: Option<f32>,\n    /// Applications to be rendered in the UI.\n    pub items: Vec<App>,\n}\n\nimpl BarWidget for Applications {\n    fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {\n        if !self.enable {\n            return;\n        }\n\n        let icon_config = IconConfig {\n            font_id: config.icon_font_id.clone(),\n            size: config.icon_font_id.size,\n            color: ctx.style().visuals.selection.stroke.color,\n        };\n\n        if let Some(spacing) = self.spacing {\n            ui.spacing_mut().item_spacing.x = spacing;\n        }\n\n        config.apply_on_widget(false, ui, |ui| {\n            for app in &mut self.items {\n                app.render(ctx, ui, &icon_config);\n            }\n        });\n    }\n}\n\nimpl From<&ApplicationsConfig> for Applications {\n    fn from(applications_config: &ApplicationsConfig) -> Self {\n        let items = applications_config\n            .items\n            .iter()\n            .enumerate()\n            .map(|(index, config)| {\n                let command = UserCommand::new(&config.command);\n\n                App {\n                    enable: config.enable.unwrap_or(applications_config.enable),\n                    #[allow(clippy::obfuscated_if_else)]\n                    name: config\n                        .name\n                        .is_empty()\n                        .then(|| format!(\"App {}\", index + 1))\n                        .unwrap_or_else(|| config.name.clone()),\n                    icon: Icon::try_from_path(config.icon.as_deref())\n                        .or_else(|| Icon::try_from_command(&command)),\n                    command,\n                    display: config\n                        .display\n                        .or(applications_config.display)\n                        .unwrap_or_default(),\n                    show_command_on_hover: config\n                        .show_command_on_hover\n                        .or(applications_config.show_command_on_hover)\n                        .unwrap_or(false),\n                }\n            })\n            .collect();\n\n        Self {\n            enable: applications_config.enable,\n            items,\n            spacing: applications_config.spacing,\n        }\n    }\n}\n\n/// A single resolved application entry used at runtime.\n#[derive(Clone, Debug)]\npub struct App {\n    /// Whether this application is enabled.\n    pub enable: bool,\n    /// Display name of the application. Defaults to \"App N\" if not set.\n    pub name: String,\n    /// Icon to display for this application, if available.\n    pub icon: Option<Icon>,\n    /// Command to execute when the application is launched.\n    pub command: UserCommand,\n    /// Display format (icon, text, or both).\n    pub display: ApplicationsDisplayFormat,\n    /// Whether to show the launch command on hover.\n    pub show_command_on_hover: bool,\n}\n\nimpl App {\n    /// Renders the application button in the provided `Ui` context with a given icon size.\n    #[inline]\n    pub fn render(&mut self, ctx: &Context, ui: &mut Ui, icon_config: &IconConfig) {\n        if self.enable\n            && SelectableFrame::new(false)\n                .show(ui, |ui| {\n                    ui.spacing_mut().item_spacing = Vec2::splat(4.0);\n\n                    match self.display {\n                        ApplicationsDisplayFormat::Icon => self.draw_icon(ctx, ui, icon_config),\n                        ApplicationsDisplayFormat::Text => self.draw_name(ui),\n                        ApplicationsDisplayFormat::IconAndText => {\n                            self.draw_icon(ctx, ui, icon_config);\n                            self.draw_name(ui);\n                        }\n                    }\n\n                    // Add hover text with command information\n                    let response = ui.response();\n                    if self.show_command_on_hover {\n                        response.on_hover_text(format!(\"Launch: {}\", self.command.as_ref()));\n                    }\n                })\n                .clicked()\n        {\n            // Launch the application when clicked\n            self.command.launch_if_ready();\n        }\n    }\n\n    /// Draws the application's icon within the UI if available,\n    /// or falls back to a default placeholder icon.\n    #[inline]\n    fn draw_icon(&self, ctx: &Context, ui: &mut Ui, icon_config: &IconConfig) {\n        if let Some(icon) = &self.icon {\n            icon.draw(ctx, ui, icon_config);\n        } else {\n            Icon::draw_fallback(ui, Vec2::splat(icon_config.size));\n        }\n    }\n\n    /// Displays the application's name as a non-selectable label within the UI.\n    #[inline]\n    fn draw_name(&self, ui: &mut Ui) {\n        ui.add(Label::new(&self.name).selectable(false));\n    }\n}\n\n/// Holds image/text data to be used as an icon in the UI.\n/// This represents source icon data before rendering.\n#[derive(Clone, Debug)]\npub enum Icon {\n    /// RGBA image used for rendering the icon.\n    Image(ImageIcon),\n    /// Text-based icon, e.g. from a font like Nerd Fonts.\n    Text(String),\n}\n\nimpl Icon {\n    /// Attempts to create an [`Icon`] from a string path or text glyph/glyphs.\n    ///\n    /// - Environment variables in the path are resolved using [`PathExt::replace_env`].\n    /// - Uses [`ImageIcon::try_load`] to load and cache the icon image based on the resolved path.\n    /// - If the path is invalid but the string is non-empty, it is interpreted as a text-based icon and\n    ///   returned as [`Icon::Text`].\n    /// - Returns `None` if the input is empty, `None`, or image loading fails.\n    #[inline]\n    pub fn try_from_path(icon: Option<&str>) -> Option<Self> {\n        let icon = icon.map(str::trim)?;\n        if icon.is_empty() {\n            return None;\n        }\n\n        let path = icon.replace_env();\n        if !path.is_file() {\n            return Some(Icon::Text(icon.to_owned()));\n        }\n\n        let image_icon = ImageIcon::try_load(path.as_ref(), || match image::open(&path) {\n            Ok(img) => Some(img),\n            Err(err) => {\n                tracing::error!(\"Failed to load icon from {:?}, error: {}\", path, err);\n                None\n            }\n        })?;\n\n        Some(Icon::Image(image_icon))\n    }\n\n    /// Attempts to create an [`Icon`] by extracting an image from the executable path of a [`UserCommand`].\n    ///\n    /// - Uses [`ImageIcon::try_load`] to load and cache the icon image based on the resolved executable path.\n    /// - Returns [`Icon::Image`] if an icon is successfully extracted.\n    /// - Returns `None` if the executable path is unavailable or icon extraction fails.\n    #[inline]\n    pub fn try_from_command(command: &UserCommand) -> Option<Self> {\n        let path = command.get_executable()?;\n        let image_icon = ImageIcon::try_load(path.as_ref(), || {\n            let path_str = path.to_str()?;\n            windows_icons::get_icon_by_path(path_str)\n                .or_else(|| windows_icons_fallback::get_icon_by_path(path_str))\n        })?;\n        Some(Icon::Image(image_icon))\n    }\n\n    /// Renders the icon in the given [`Ui`] using the provided [`IconConfig`].\n    #[inline]\n    pub fn draw(&self, ctx: &Context, ui: &mut Ui, icon_config: &IconConfig) {\n        match self {\n            Icon::Image(image_icon) => {\n                Frame::NONE\n                    .inner_margin(Margin::same(ui.style().spacing.button_padding.y as i8))\n                    .show(ui, |ui| {\n                        ui.add(\n                            Image::from_texture(&image_icon.texture(ctx))\n                                .maintain_aspect_ratio(true)\n                                .fit_to_exact_size(Vec2::splat(icon_config.size)),\n                        );\n                    });\n            }\n            Icon::Text(icon) => {\n                let rich_text = RichText::new(icon)\n                    .font(icon_config.font_id.clone())\n                    .size(icon_config.size)\n                    .color(icon_config.color);\n                ui.add(Label::new(rich_text).selectable(false));\n            }\n        }\n    }\n\n    /// Draws a fallback icon when the specified icon cannot be loaded.\n    /// Displays a simple crossed-out rectangle as a placeholder.\n    #[inline]\n    pub fn draw_fallback(ui: &mut Ui, icon_size: Vec2) {\n        let (response, painter) = ui.allocate_painter(icon_size, Sense::hover());\n        let stroke = Stroke::new(1.0, ui.style().visuals.text_color());\n        let mut rect = response.rect;\n        let rounding = CornerRadius::same((rect.width() * 0.1) as u8);\n        rect = rect.shrink(stroke.width);\n        let c = rect.center();\n        let r = rect.width() / 2.0;\n        painter.rect_stroke(rect, rounding, stroke, StrokeKind::Outside);\n        painter.line_segment([c - vec2(r, r), c + vec2(r, r)], stroke);\n    }\n}\n\n/// Configuration structure for icon rendering\n#[derive(Clone, Debug)]\npub struct IconConfig {\n    /// Font used for text-based icons\n    pub font_id: FontId,\n    /// Size of the icon\n    pub size: f32,\n    /// Color of the icon used for text-based icons\n    pub color: Color32,\n}\n\n/// A structure to manage command execution with cooldown prevention.\n#[derive(Clone, Debug)]\npub struct UserCommand {\n    /// The command string to execute\n    pub command: Arc<str>,\n    /// Last time this command was executed (used for cooldown control)\n    pub last_launch: Instant,\n}\n\nimpl AsRef<str> for UserCommand {\n    #[inline]\n    fn as_ref(&self) -> &str {\n        &self.command\n    }\n}\n\nimpl UserCommand {\n    /// Creates a new [`UserCommand`] with environment variables in the command path\n    /// resolved using [`PathExt::replace_env`].\n    #[inline]\n    pub fn new(command: &str) -> Self {\n        // Allow immediate launch by initializing last_launch in the past\n        let last_launch = Instant::now() - 2 * MIN_LAUNCH_INTERVAL;\n\n        Self {\n            command: Arc::from(command.replace_env().to_str().unwrap_or_default()),\n            last_launch,\n        }\n    }\n\n    /// Attempts to resolve the executable path from the command string.\n    ///\n    /// Resolution logic:\n    /// - Splits the command by \".exe\" and checks if the first part is an existing file.\n    /// - If not, attempts to locate the binary using [`which`] on this name.\n    /// - If still unresolved, takes the first word (separated by whitespace) and attempts\n    ///   to find it in the system `PATH` using [`which`].\n    ///\n    /// Returns `None` if no executable path can be determined.\n    #[inline]\n    pub fn get_executable(&self) -> Option<Cow<'_, Path>> {\n        if let Some(binary) = self.command.split(\".exe\").next().map(Path::new) {\n            if binary.is_file() {\n                return Some(Cow::Borrowed(binary));\n            } else if let Ok(binary) = which(binary) {\n                return Some(Cow::Owned(binary));\n            }\n        }\n\n        which(self.command.split(' ').next()?).ok().map(Cow::Owned)\n    }\n\n    /// Attempts to launch the specified command in a separate thread if enough time has passed\n    /// since the last launch. This prevents repeated launches from rapid consecutive clicks.\n    ///\n    /// Errors during launch are logged using the `tracing` crate.\n    pub fn launch_if_ready(&mut self) {\n        let now = Instant::now();\n        // Check if enough time has passed since the last launch\n        if now.duration_since(self.last_launch) < MIN_LAUNCH_INTERVAL {\n            return;\n        }\n\n        self.last_launch = now;\n        let command_string = self.command.clone();\n        // Launch the application in a separate thread to avoid blocking the UI\n        std::thread::spawn(move || {\n            if let Err(e) = Command::new(\"cmd\").args([\"/C\", &command_string]).spawn() {\n                tracing::error!(\"Failed to launch command '{}': {}\", command_string, e);\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "komorebi-bar/src/widgets/battery.rs",
    "content": "use crate::config::LabelPrefix;\nuse crate::render::RenderConfig;\nuse crate::selected_frame::SelectableFrame;\nuse crate::widgets::widget::BarWidget;\nuse eframe::egui::Align;\nuse eframe::egui::Context;\nuse eframe::egui::Label;\nuse eframe::egui::TextFormat;\nuse eframe::egui::Ui;\nuse eframe::egui::text::LayoutJob;\nuse serde::Deserialize;\nuse serde::Serialize;\nuse starship_battery::Manager;\nuse starship_battery::State;\nuse starship_battery::units::ratio::percent;\nuse std::process::Command;\nuse std::time::Duration;\nuse std::time::Instant;\n\n#[derive(Copy, Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Battery widget configuration\npub struct BatteryConfig {\n    /// Enable the Battery widget\n    pub enable: bool,\n    /// Hide the widget if the battery is at full charge\n    pub hide_on_full_charge: Option<bool>,\n    /// Data refresh interval in seconds\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = 10)))]\n    pub data_refresh_interval: Option<u64>,\n    /// Display label prefix\n    pub label_prefix: Option<LabelPrefix>,\n    /// Select when the current percentage is under this value [[1-100]]\n    pub auto_select_under: Option<u8>,\n}\n\nimpl From<BatteryConfig> for Battery {\n    fn from(value: BatteryConfig) -> Self {\n        let data_refresh_interval = value.data_refresh_interval.unwrap_or(10);\n\n        Self {\n            enable: value.enable,\n            hide_on_full_charge: value.hide_on_full_charge.unwrap_or(false),\n            manager: Manager::new().unwrap(),\n            last_state: None,\n            data_refresh_interval,\n            label_prefix: value.label_prefix.unwrap_or(LabelPrefix::Icon),\n            auto_select_under: value.auto_select_under.map(|u| u.clamp(1, 100)),\n            state: BatteryState::Discharging,\n            last_updated: Instant::now()\n                .checked_sub(Duration::from_secs(data_refresh_interval))\n                .unwrap(),\n        }\n    }\n}\n\npub enum BatteryState {\n    Charging,\n    Discharging,\n    High,\n    Medium,\n    Low,\n    Warning,\n}\n\n#[derive(Clone, Debug)]\nstruct BatteryOutput {\n    label: String,\n    selected: bool,\n}\n\npub struct Battery {\n    pub enable: bool,\n    hide_on_full_charge: bool,\n    manager: Manager,\n    pub state: BatteryState,\n    data_refresh_interval: u64,\n    label_prefix: LabelPrefix,\n    auto_select_under: Option<u8>,\n    last_state: Option<BatteryOutput>,\n    last_updated: Instant,\n}\n\nimpl Battery {\n    fn output(&mut self) -> Option<BatteryOutput> {\n        let mut output = self.last_state.clone();\n\n        let now = Instant::now();\n        if now.duration_since(self.last_updated) > Duration::from_secs(self.data_refresh_interval) {\n            output = None;\n\n            if let Ok(mut batteries) = self.manager.batteries()\n                && let Some(Ok(first)) = batteries.nth(0)\n            {\n                let percentage = first.state_of_charge().get::<percent>().round() as u8;\n\n                if percentage == 100 && self.hide_on_full_charge {\n                    output = None\n                } else {\n                    match first.state() {\n                        State::Charging => self.state = BatteryState::Charging,\n                        State::Discharging => {\n                            self.state = match percentage {\n                                p if p > 75 => BatteryState::Discharging,\n                                p if p > 50 => BatteryState::High,\n                                p if p > 25 => BatteryState::Medium,\n                                p if p > 10 => BatteryState::Low,\n                                _ => BatteryState::Warning,\n                            }\n                        }\n                        _ => {}\n                    }\n\n                    let selected = self.auto_select_under.is_some_and(|u| percentage <= u);\n\n                    output = Some(BatteryOutput {\n                        label: match self.label_prefix {\n                            LabelPrefix::Text | LabelPrefix::IconAndText => {\n                                format!(\"BAT: {percentage}%\")\n                            }\n                            LabelPrefix::None | LabelPrefix::Icon => {\n                                format!(\"{percentage}%\")\n                            }\n                        },\n                        selected,\n                    })\n                }\n            }\n\n            self.last_state.clone_from(&output);\n            self.last_updated = now;\n        }\n\n        output\n    }\n}\n\nimpl BarWidget for Battery {\n    fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {\n        if self.enable {\n            let output = self.output();\n            if let Some(output) = output {\n                let emoji = match self.state {\n                    BatteryState::Charging => egui_phosphor::regular::BATTERY_CHARGING,\n                    BatteryState::Discharging => egui_phosphor::regular::BATTERY_FULL,\n                    BatteryState::High => egui_phosphor::regular::BATTERY_HIGH,\n                    BatteryState::Medium => egui_phosphor::regular::BATTERY_MEDIUM,\n                    BatteryState::Low => egui_phosphor::regular::BATTERY_LOW,\n                    BatteryState::Warning => egui_phosphor::regular::BATTERY_WARNING,\n                };\n\n                let auto_text_color = config.auto_select_text.filter(|_| output.selected);\n\n                let mut layout_job = LayoutJob::simple(\n                    match self.label_prefix {\n                        LabelPrefix::Icon | LabelPrefix::IconAndText => emoji.to_string(),\n                        LabelPrefix::None | LabelPrefix::Text => String::new(),\n                    },\n                    config.icon_font_id.clone(),\n                    auto_text_color.unwrap_or(ctx.style().visuals.selection.stroke.color),\n                    100.0,\n                );\n\n                layout_job.append(\n                    &output.label,\n                    10.0,\n                    TextFormat {\n                        font_id: config.text_font_id.clone(),\n                        color: auto_text_color.unwrap_or(ctx.style().visuals.text_color()),\n                        valign: Align::Center,\n                        ..Default::default()\n                    },\n                );\n\n                let auto_focus_fill = config.auto_select_fill;\n\n                config.apply_on_widget(false, ui, |ui| {\n                    if SelectableFrame::new_auto(output.selected, auto_focus_fill)\n                        .show(ui, |ui| ui.add(Label::new(layout_job).selectable(false)))\n                        .clicked()\n                        && let Err(error) = Command::new(\"cmd.exe\")\n                            .args([\"/C\", \"start\", \"ms-settings:batterysaver\"])\n                            .spawn()\n                    {\n                        eprintln!(\"{error}\")\n                    }\n                });\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "komorebi-bar/src/widgets/cpu.rs",
    "content": "use crate::config::LabelPrefix;\nuse crate::render::RenderConfig;\nuse crate::selected_frame::SelectableFrame;\nuse crate::widgets::widget::BarWidget;\nuse eframe::egui::Align;\nuse eframe::egui::Context;\nuse eframe::egui::Label;\nuse eframe::egui::TextFormat;\nuse eframe::egui::Ui;\nuse eframe::egui::text::LayoutJob;\nuse serde::Deserialize;\nuse serde::Serialize;\nuse std::process::Command;\nuse std::time::Duration;\nuse std::time::Instant;\nuse sysinfo::RefreshKind;\nuse sysinfo::System;\n\n#[derive(Copy, Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// CPU widget configuration\npub struct CpuConfig {\n    /// Enable the Cpu widget\n    pub enable: bool,\n    /// Data refresh interval in seconds\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = 10)))]\n    pub data_refresh_interval: Option<u64>,\n    /// Display label prefix\n    pub label_prefix: Option<LabelPrefix>,\n    /// Select when the current percentage is over this value [[1-100]]\n    pub auto_select_over: Option<u8>,\n}\n\nimpl From<CpuConfig> for Cpu {\n    fn from(value: CpuConfig) -> Self {\n        let data_refresh_interval = value.data_refresh_interval.unwrap_or(10);\n\n        Self {\n            enable: value.enable,\n            system: System::new_with_specifics(\n                RefreshKind::default().without_memory().without_processes(),\n            ),\n            data_refresh_interval,\n            label_prefix: value.label_prefix.unwrap_or(LabelPrefix::IconAndText),\n            auto_select_over: value.auto_select_over.map(|o| o.clamp(1, 100)),\n            last_updated: Instant::now()\n                .checked_sub(Duration::from_secs(data_refresh_interval))\n                .unwrap(),\n        }\n    }\n}\n\n#[derive(Clone, Debug)]\nstruct CpuOutput {\n    label: String,\n    selected: bool,\n}\n\npub struct Cpu {\n    pub enable: bool,\n    system: System,\n    data_refresh_interval: u64,\n    label_prefix: LabelPrefix,\n    auto_select_over: Option<u8>,\n    last_updated: Instant,\n}\n\nimpl Cpu {\n    fn output(&mut self) -> CpuOutput {\n        let now = Instant::now();\n        if now.duration_since(self.last_updated) > Duration::from_secs(self.data_refresh_interval) {\n            self.system.refresh_cpu_usage();\n            self.last_updated = now;\n        }\n\n        let used = self.system.global_cpu_usage() as u8;\n        let selected = self.auto_select_over.is_some_and(|o| used >= o);\n\n        CpuOutput {\n            label: match self.label_prefix {\n                LabelPrefix::Text | LabelPrefix::IconAndText => format!(\"CPU: {used}%\"),\n                LabelPrefix::None | LabelPrefix::Icon => format!(\"{used}%\"),\n            },\n            selected,\n        }\n    }\n}\n\nimpl BarWidget for Cpu {\n    fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {\n        if self.enable {\n            let output = self.output();\n            if !output.label.is_empty() {\n                let auto_text_color = config.auto_select_text.filter(|_| output.selected);\n\n                let mut layout_job = LayoutJob::simple(\n                    match self.label_prefix {\n                        LabelPrefix::Icon | LabelPrefix::IconAndText => {\n                            egui_phosphor::regular::CPU.to_string()\n                        }\n                        LabelPrefix::None | LabelPrefix::Text => String::new(),\n                    },\n                    config.icon_font_id.clone(),\n                    auto_text_color.unwrap_or(ctx.style().visuals.selection.stroke.color),\n                    100.0,\n                );\n\n                layout_job.append(\n                    &output.label,\n                    10.0,\n                    TextFormat {\n                        font_id: config.text_font_id.clone(),\n                        color: auto_text_color.unwrap_or(ctx.style().visuals.text_color()),\n                        valign: Align::Center,\n                        ..Default::default()\n                    },\n                );\n\n                let auto_focus_fill = config.auto_select_fill;\n\n                config.apply_on_widget(false, ui, |ui| {\n                    if SelectableFrame::new_auto(output.selected, auto_focus_fill)\n                        .show(ui, |ui| ui.add(Label::new(layout_job).selectable(false)))\n                        .clicked()\n                        && let Err(error) =\n                            Command::new(\"cmd.exe\").args([\"/C\", \"taskmgr.exe\"]).spawn()\n                    {\n                        eprintln!(\"{error}\")\n                    }\n                });\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "komorebi-bar/src/widgets/date.rs",
    "content": "use crate::config::LabelPrefix;\nuse crate::render::RenderConfig;\nuse crate::selected_frame::SelectableFrame;\nuse crate::widgets::widget::BarWidget;\nuse chrono::Local;\nuse chrono_tz::Tz;\nuse eframe::egui::Align;\nuse eframe::egui::Context;\nuse eframe::egui::Label;\nuse eframe::egui::TextFormat;\nuse eframe::egui::Ui;\nuse eframe::egui::WidgetText;\nuse eframe::egui::text::LayoutJob;\nuse serde::Deserialize;\nuse serde::Serialize;\nuse std::sync::Arc;\nuse std::time::Duration;\nuse std::time::Instant;\n\n/// Custom format with additive modifiers for integer format specifiers\n#[derive(Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\npub struct CustomModifiers {\n    /// Custom format (https://docs.rs/chrono/latest/chrono/format/strftime/index.html)\n    format: String,\n    /// Additive modifiers for integer format specifiers (e.g. { \"%U\": 1 } to increment the zero-indexed week number by 1)\n    modifiers: std::collections::HashMap<String, i32>,\n}\n\nimpl CustomModifiers {\n    fn apply(&self, output: &str) -> String {\n        let int_formatters = vec![\n            \"%Y\", \"%C\", \"%y\", \"%m\", \"%d\", \"%e\", \"%w\", \"%u\", \"%U\", \"%W\", \"%G\", \"%g\", \"%V\", \"%j\",\n            \"%H\", \"%k\", \"%I\", \"%l\", \"%M\", \"%S\", \"%f\",\n        ];\n\n        let mut modified_output = output.to_string();\n\n        for (modifier, value) in &self.modifiers {\n            // check if formatter is integer type\n            if !int_formatters.contains(&modifier.as_str()) {\n                continue;\n            }\n\n            // get the strftime value of modifier\n            let formatted_modifier = Local::now().format(modifier).to_string();\n\n            // find the gotten value in the original output\n            if let Some(pos) = modified_output.find(&formatted_modifier) {\n                let start = pos;\n                let end = start + formatted_modifier.len();\n                // replace that value with the modified value\n                if let Ok(num) = formatted_modifier.parse::<i32>() {\n                    modified_output.replace_range(start..end, &(num + value).to_string());\n                }\n            }\n        }\n\n        modified_output\n    }\n}\n\n#[derive(Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Date widget configuration\npub struct DateConfig {\n    /// Enable the Date widget\n    pub enable: bool,\n    /// Set the Date format\n    pub format: DateFormat,\n    /// Display label prefix\n    pub label_prefix: Option<LabelPrefix>,\n    /// TimeZone (https://docs.rs/chrono-tz/latest/chrono_tz/enum.Tz.html)\n    ///\n    /// Use a custom format to display additional information, i.e.:\n    /// ```json\n    /// {\n    ///     \"Date\": {\n    ///         \"enable\": true,\n    ///         \"format\": { \"Custom\": \"%D %Z (Tokyo)\" },\n    ///         \"timezone\": \"Asia/Tokyo\"\n    ///      }\n    ///}\n    /// ```\n    pub timezone: Option<String>,\n}\n\nimpl From<DateConfig> for Date {\n    fn from(value: DateConfig) -> Self {\n        let data_refresh_interval = 1;\n\n        Self {\n            enable: value.enable,\n            format: value.format,\n            label_prefix: value.label_prefix.unwrap_or(LabelPrefix::Icon),\n            timezone: value.timezone,\n            data_refresh_interval,\n            last_state: String::new(),\n            last_updated: Instant::now()\n                .checked_sub(Duration::from_secs(data_refresh_interval))\n                .unwrap(),\n        }\n    }\n}\n\n#[derive(Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Date widget format\npub enum DateFormat {\n    /// Month/Date/Year format (09/08/24)\n    MonthDateYear,\n    /// Year-Month-Date format (2024-09-08)\n    YearMonthDate,\n    /// Date-Month-Year format (8-Sep-2024)\n    DateMonthYear,\n    /// Day Date Month Year format (8 September 2024)\n    DayDateMonthYear,\n    /// Custom format (https://docs.rs/chrono/latest/chrono/format/strftime/index.html)\n    #[cfg_attr(feature = \"schemars\", schemars(title = \"Custom\"))]\n    Custom(String),\n    /// Custom format with modifiers\n    #[cfg_attr(feature = \"schemars\", schemars(title = \"CustomModifiers\"))]\n    CustomModifiers(CustomModifiers),\n}\n\nimpl DateFormat {\n    pub fn next(&mut self) {\n        match self {\n            DateFormat::MonthDateYear => *self = Self::YearMonthDate,\n            DateFormat::YearMonthDate => *self = Self::DateMonthYear,\n            DateFormat::DateMonthYear => *self = Self::DayDateMonthYear,\n            DateFormat::DayDateMonthYear => *self = Self::MonthDateYear,\n            _ => {}\n        };\n    }\n\n    pub fn fmt_string(&self) -> String {\n        match self {\n            DateFormat::MonthDateYear => String::from(\"%D\"),\n            DateFormat::YearMonthDate => String::from(\"%F\"),\n            DateFormat::DateMonthYear => String::from(\"%v\"),\n            DateFormat::DayDateMonthYear => String::from(\"%A %e %B %Y\"),\n            DateFormat::Custom(custom) => custom.to_string(),\n            DateFormat::CustomModifiers(custom) => custom.format.clone(),\n        }\n    }\n}\n\n#[derive(Clone, Debug)]\npub struct Date {\n    pub enable: bool,\n    pub format: DateFormat,\n    label_prefix: LabelPrefix,\n    timezone: Option<String>,\n    data_refresh_interval: u64,\n    last_state: String,\n    last_updated: Instant,\n}\n\nimpl Date {\n    fn output(&mut self) -> String {\n        let mut output = self.last_state.clone();\n        let now = Instant::now();\n\n        if now.duration_since(self.last_updated) > Duration::from_secs(self.data_refresh_interval) {\n            let formatted = match &self.timezone {\n                Some(timezone) => match timezone.parse::<Tz>() {\n                    Ok(tz) => Local::now()\n                        .with_timezone(&tz)\n                        .format(&self.format.fmt_string())\n                        .to_string()\n                        .trim()\n                        .to_string(),\n                    Err(_) => format!(\"Invalid timezone: {timezone}\"),\n                },\n                None => Local::now()\n                    .format(&self.format.fmt_string())\n                    .to_string()\n                    .trim()\n                    .to_string(),\n            };\n\n            // if custom modifiers are used, apply them\n            output = match &self.format {\n                DateFormat::CustomModifiers(custom) => custom.apply(&formatted),\n                _ => formatted,\n            };\n\n            self.last_state.clone_from(&output);\n            self.last_updated = now;\n        }\n\n        output\n    }\n}\n\nimpl BarWidget for Date {\n    fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {\n        if self.enable {\n            let mut output = self.output();\n            if !output.is_empty() {\n                let mut layout_job = LayoutJob::simple(\n                    match self.label_prefix {\n                        LabelPrefix::Icon | LabelPrefix::IconAndText => {\n                            egui_phosphor::regular::CALENDAR_DOTS.to_string()\n                        }\n                        LabelPrefix::None | LabelPrefix::Text => String::new(),\n                    },\n                    config.icon_font_id.clone(),\n                    ctx.style().visuals.selection.stroke.color,\n                    100.0,\n                );\n\n                if let LabelPrefix::Text | LabelPrefix::IconAndText = self.label_prefix {\n                    output.insert_str(0, \"DATE: \");\n                }\n\n                layout_job.append(\n                    &output,\n                    10.0,\n                    TextFormat {\n                        font_id: config.text_font_id.clone(),\n                        color: ctx.style().visuals.text_color(),\n                        valign: Align::Center,\n                        ..Default::default()\n                    },\n                );\n\n                config.apply_on_widget(false, ui, |ui| {\n                    if SelectableFrame::new(false)\n                        .show(ui, |ui| {\n                            ui.add(\n                                Label::new(WidgetText::LayoutJob(Arc::from(layout_job.clone())))\n                                    .selectable(false),\n                            )\n                        })\n                        .clicked()\n                    {\n                        self.format.next()\n                    }\n                });\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "komorebi-bar/src/widgets/keyboard.rs",
    "content": "use crate::config::LabelPrefix;\nuse crate::render::RenderConfig;\nuse crate::widgets::widget::BarWidget;\nuse color_eyre::eyre;\nuse eframe::egui::Align;\nuse eframe::egui::Context;\nuse eframe::egui::Label;\nuse eframe::egui::TextFormat;\nuse eframe::egui::Ui;\nuse eframe::egui::WidgetText;\nuse eframe::egui::text::LayoutJob;\nuse serde::Deserialize;\nuse serde::Serialize;\nuse std::sync::Arc;\nuse std::time::Duration;\nuse std::time::Instant;\nuse windows::Win32::Globalization::LCIDToLocaleName;\nuse windows::Win32::Globalization::LOCALE_ALLOW_NEUTRAL_NAMES;\nuse windows::Win32::System::SystemServices::LOCALE_NAME_MAX_LENGTH;\nuse windows::Win32::UI::Input::KeyboardAndMouse::GetKeyboardLayout;\nuse windows::Win32::UI::WindowsAndMessaging::GetForegroundWindow;\nuse windows::Win32::UI::WindowsAndMessaging::GetWindowThreadProcessId;\n\nconst ERROR_TEXT: &str = \"Error\";\n\n#[derive(Copy, Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Keyboard widget configuration\npub struct KeyboardConfig {\n    /// Enable the Input widget\n    pub enable: bool,\n    /// Data refresh interval\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = 10)))]\n    pub data_refresh_interval: Option<u64>,\n    /// Display label prefix\n    pub label_prefix: Option<LabelPrefix>,\n}\n\nimpl From<KeyboardConfig> for Keyboard {\n    fn from(value: KeyboardConfig) -> Self {\n        let data_refresh_interval = value.data_refresh_interval.unwrap_or(10);\n\n        Self {\n            enable: value.enable,\n            data_refresh_interval,\n            label_prefix: value.label_prefix.unwrap_or(LabelPrefix::IconAndText),\n            last_updated: Instant::now(),\n            lang_name: get_lang(),\n        }\n    }\n}\n\npub struct Keyboard {\n    pub enable: bool,\n    data_refresh_interval: u64,\n    label_prefix: LabelPrefix,\n    last_updated: Instant,\n    lang_name: String,\n}\n\n/// Retrieves the name of the active keyboard layout for the current foreground window.\n///\n/// This function determines the active keyboard layout by querying the system for the\n/// foreground window's thread ID and its associated keyboard layout. It then attempts\n/// to retrieve the locale name corresponding to the keyboard layout.\n///\n/// # Failure Cases\n///\n/// This function can fail in two distinct scenarios:\n///\n/// 1. **Failure to Retrieve the Locale Name**:\n///    If the system fails to retrieve the locale name (e.g., due to an invalid or unsupported\n///    language identifier), the function will return `Err(())`.\n///\n/// 2. **Invalid UTF-16 Characters in the Locale Name**:\n///    If the retrieved locale name contains invalid UTF-16 sequences, the conversion to a Rust\n///    `String` will fail, and the function will return `Err(())`.\n///\n/// # Returns\n///\n/// - `Ok(String)`: The name of the active keyboard layout as a valid UTF-8 string.\n/// - `Err(())`: Indicates that the function failed to retrieve the locale name or encountered\n///   invalid UTF-16 characters during conversion.\nfn get_active_keyboard_layout() -> eyre::Result<String, ()> {\n    let foreground_window_tid = unsafe { GetWindowThreadProcessId(GetForegroundWindow(), None) };\n    let lcid = unsafe { GetKeyboardLayout(foreground_window_tid) };\n\n    // Extract the low word (language identifier) from the keyboard layout handle.\n    let lang_id = (lcid.0 as u32) & 0xFFFF;\n    let mut locale_name_buffer = [0; LOCALE_NAME_MAX_LENGTH as usize];\n    let char_count = unsafe {\n        LCIDToLocaleName(\n            lang_id,\n            Some(&mut locale_name_buffer),\n            LOCALE_ALLOW_NEUTRAL_NAMES,\n        )\n    };\n\n    match char_count {\n        0 => Err(()),\n        _ => String::from_utf16(&locale_name_buffer[..char_count as usize]).map_err(|_| ()),\n    }\n}\n\n/// Retrieves the name of the active keyboard layout or a fallback error message.\n///\n/// # Behavior\n///\n/// - **Success Case**:\n///   If [`get_active_keyboard_layout`] succeeds, this function returns the retrieved keyboard\n///   layout name as a `String`.\n///\n/// - **Failure Case**:\n///   If [`get_active_keyboard_layout`] fails, this function returns the value of `ERROR_TEXT`\n///   as a fallback message. This ensures that the function always returns a valid `String`,\n///   even in error scenarios.\n///\n/// # Returns\n///\n/// A `String` representing either:\n/// - The name of the active keyboard layout, or\n/// - The fallback error message (`ERROR_TEXT`) if the layout name cannot be retrieved.\nfn get_lang() -> String {\n    get_active_keyboard_layout()\n        .map(|l| l.trim_end_matches('\\0').to_string())\n        .unwrap_or_else(|_| ERROR_TEXT.to_string())\n}\n\nimpl Keyboard {\n    fn output(&mut self) -> String {\n        let now = Instant::now();\n        if now.duration_since(self.last_updated) > Duration::from_secs(self.data_refresh_interval) {\n            self.last_updated = now;\n            self.lang_name = get_lang();\n        }\n\n        match self.label_prefix {\n            LabelPrefix::Text | LabelPrefix::IconAndText => format!(\"KB: {}\", self.lang_name),\n            LabelPrefix::None | LabelPrefix::Icon => self.lang_name.clone(),\n        }\n    }\n}\n\nimpl BarWidget for Keyboard {\n    fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {\n        if self.enable {\n            let output = self.output();\n            if !output.is_empty() {\n                let mut layout_job = LayoutJob::simple(\n                    match self.label_prefix {\n                        LabelPrefix::Icon | LabelPrefix::IconAndText => {\n                            egui_phosphor::regular::KEYBOARD.to_string()\n                        }\n                        LabelPrefix::None | LabelPrefix::Text => String::new(),\n                    },\n                    config.icon_font_id.clone(),\n                    ctx.style().visuals.selection.stroke.color,\n                    100.0,\n                );\n\n                layout_job.append(\n                    &output,\n                    10.0,\n                    TextFormat {\n                        font_id: config.text_font_id.clone(),\n                        color: ctx.style().visuals.text_color(),\n                        valign: Align::Center,\n                        ..Default::default()\n                    },\n                );\n\n                config.apply_on_widget(true, ui, |ui| {\n                    ui.add(\n                        Label::new(WidgetText::LayoutJob(Arc::from(layout_job.clone())))\n                            .selectable(false),\n                    )\n                });\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "komorebi-bar/src/widgets/komorebi.rs",
    "content": "use super::ImageIcon;\nuse crate::MAX_LABEL_WIDTH;\nuse crate::MONITOR_INDEX;\nuse crate::config::DisplayFormat;\nuse crate::config::DisplayFormat::*;\nuse crate::config::WorkspacesDisplayFormat;\nuse crate::render::RenderConfig;\nuse crate::selected_frame::SelectableFrame;\nuse crate::ui::CustomUi;\nuse crate::widgets::komorebi_layout::KomorebiLayout;\nuse crate::widgets::widget::BarWidget;\nuse eframe::egui::Align;\nuse eframe::egui::Color32;\nuse eframe::egui::Context;\nuse eframe::egui::CornerRadius;\nuse eframe::egui::FontId;\nuse eframe::egui::Frame;\nuse eframe::egui::Image;\nuse eframe::egui::Label;\nuse eframe::egui::Margin;\nuse eframe::egui::Response;\nuse eframe::egui::RichText;\nuse eframe::egui::Sense;\nuse eframe::egui::Stroke;\nuse eframe::egui::StrokeKind;\nuse eframe::egui::TextFormat;\nuse eframe::egui::Ui;\nuse eframe::egui::Vec2;\nuse eframe::egui::text::LayoutJob;\nuse eframe::egui::vec2;\nuse komorebi_client::Container;\nuse komorebi_client::PathExt;\nuse komorebi_client::Rect;\nuse komorebi_client::SocketMessage;\nuse komorebi_client::SocketMessage::*;\nuse komorebi_client::State;\nuse komorebi_client::Window;\nuse komorebi_client::Workspace;\nuse komorebi_client::WorkspaceLayer;\nuse serde::Deserialize;\nuse serde::Serialize;\nuse std::cell::RefCell;\nuse std::collections::BTreeMap;\nuse std::collections::HashMap;\nuse std::io::Result as IoResult;\nuse std::path::Path;\nuse std::rc::Rc;\nuse std::sync::atomic::Ordering;\n\n#[derive(Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Komorebi widget configuration\npub struct KomorebiConfig {\n    /// Configure the Workspaces widget\n    pub workspaces: Option<KomorebiWorkspacesConfig>,\n    /// Configure the Layout widget\n    pub layout: Option<KomorebiLayoutConfig>,\n    /// Configure the Workspace Layer widget\n    pub workspace_layer: Option<KomorebiWorkspaceLayerConfig>,\n    /// Configure the Focused Container widget\n    #[serde(alias = \"focused_window\")]\n    pub focused_container: Option<KomorebiFocusedContainerConfig>,\n    /// Configure the Locked Container widget\n    pub locked_container: Option<KomorebiLockedContainerConfig>,\n    /// Configure the Configuration Switcher widget\n    pub configuration_switcher: Option<KomorebiConfigurationSwitcherConfig>,\n}\n\n#[derive(Copy, Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Komorebi widget workspaces configuration\npub struct KomorebiWorkspacesConfig {\n    /// Enable the Komorebi Workspaces widget\n    pub enable: bool,\n    /// Hide workspaces without any windows\n    pub hide_empty_workspaces: bool,\n    /// Display format of the workspace\n    pub display: Option<WorkspacesDisplayFormat>,\n}\n\n#[derive(Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Komorebi widget layout configuration\npub struct KomorebiLayoutConfig {\n    /// Enable the Komorebi Layout widget\n    pub enable: bool,\n    /// List of layout options\n    pub options: Option<Vec<KomorebiLayout>>,\n    /// Display format of the current layout\n    pub display: Option<DisplayFormat>,\n}\n\n#[derive(Copy, Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Komorebi widget workspace layer configuration\npub struct KomorebiWorkspaceLayerConfig {\n    /// Enable the Komorebi Workspace Layer widget\n    pub enable: bool,\n    /// Display format of the current layer\n    pub display: Option<DisplayFormat>,\n    /// Show the widget event if the layer is Tiling\n    pub show_when_tiling: Option<bool>,\n}\n\n#[derive(Copy, Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Komorebi widget focused container configuration\npub struct KomorebiFocusedContainerConfig {\n    /// Enable the Komorebi Focused Container widget\n    pub enable: bool,\n    /// DEPRECATED: use `display` instead (Show the icon of the currently focused container)\n    #[deprecated(note = \"Use `display` instead\")]\n    pub show_icon: Option<bool>,\n    /// Display format of the currently focused container\n    pub display: Option<DisplayFormat>,\n}\n\n#[derive(Copy, Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Komorebi widget locked container configuration\npub struct KomorebiLockedContainerConfig {\n    /// Enable the Komorebi Locked Container widget\n    pub enable: bool,\n    /// Display format of the current locked state\n    pub display: Option<DisplayFormat>,\n    /// Show the widget event if the layer is unlocked\n    pub show_when_unlocked: Option<bool>,\n}\n\n#[derive(Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Komorebi widget configuration switcher configuration\npub struct KomorebiConfigurationSwitcherConfig {\n    /// Enable the Komorebi Configurations widget\n    pub enable: bool,\n    /// A map of display friendly name => path to configuration.json\n    pub configurations: BTreeMap<String, String>,\n}\n\nimpl From<&KomorebiConfig> for Komorebi {\n    fn from(cfg: &KomorebiConfig) -> Self {\n        let configuration_switcher = cfg.configuration_switcher.clone().map(|mut cs| {\n            for location in cs.configurations.values_mut() {\n                let path = Path::new(location).replace_env();\n                *location = dunce::simplified(&path).to_string_lossy().to_string();\n            }\n            cs\n        });\n\n        Self {\n            monitor_info: Rc::new(RefCell::new(MonitorInfo {\n                hide_empty_workspaces: cfg\n                    .workspaces\n                    .map(|w| w.hide_empty_workspaces)\n                    .unwrap_or_default(),\n                ..Default::default()\n            })),\n            workspaces: cfg.workspaces.and_then(WorkspacesBar::try_from),\n            layout: cfg.layout.clone(),\n            focused_container: cfg\n                .focused_container\n                .and_then(FocusedContainerBar::try_from),\n            workspace_layer: cfg.workspace_layer.and_then(WorkspaceLayerBar::try_from),\n            locked_container: cfg.locked_container.and_then(LockedContainerBar::try_from),\n            configuration_switcher,\n        }\n    }\n}\n\n#[derive(Clone, Debug)]\npub struct Komorebi {\n    pub monitor_info: Rc<RefCell<MonitorInfo>>,\n    pub workspaces: Option<WorkspacesBar>,\n    pub layout: Option<KomorebiLayoutConfig>,\n    pub focused_container: Option<FocusedContainerBar>,\n    pub workspace_layer: Option<WorkspaceLayerBar>,\n    pub locked_container: Option<LockedContainerBar>,\n    pub configuration_switcher: Option<KomorebiConfigurationSwitcherConfig>,\n}\n\nimpl BarWidget for Komorebi {\n    fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {\n        self.render_workspaces(ctx, ui, config);\n        self.render_workspace_layer(ctx, ui, config);\n        self.render_layout(ctx, ui, config);\n        self.render_config_switcher(ui, config);\n        self.render_locked_container(ctx, ui, config);\n        self.render_focused_container(ctx, ui, config);\n    }\n}\n\nimpl Komorebi {\n    /// Renders the workspace bar for the current monitor.\n    /// Updates the focused workspace when a workspace is clicked.\n    fn render_workspaces(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {\n        let monitor_info = &mut *self.monitor_info.borrow_mut();\n\n        let bar = match &mut self.workspaces {\n            Some(wg) if !monitor_info.workspaces.is_empty() => wg,\n            _ => return,\n        };\n\n        bar.text_size = Vec2::splat(config.text_font_id.size);\n        bar.icon_size = Vec2::splat(config.icon_font_id.size);\n\n        config.apply_on_widget(false, ui, |ui| {\n            for (index, workspace) in monitor_info.workspaces.iter().enumerate() {\n                if !workspace.should_show {\n                    continue;\n                }\n\n                let response = SelectableFrame::new(workspace.is_selected)\n                    .show(ui, |ui| (bar.renderer)(bar, ctx, ui, workspace));\n\n                if response.clicked() {\n                    let message = FocusMonitorWorkspaceNumber(monitor_info.monitor_index, index);\n                    if Self::send_with_mouse_follow_off(monitor_info, message).is_ok() {\n                        monitor_info.focused_workspace_idx = Some(index);\n                    }\n                }\n            }\n        });\n    }\n\n    /// Renders the workspace layer bar for the current monitor.\n    /// Handles user clicks to toggle the workspace layer.\n    fn render_workspace_layer(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {\n        let Some(bar) = &self.workspace_layer else {\n            return;\n        };\n\n        let monitor_info = &*self.monitor_info.borrow();\n        let Some(layer) = monitor_info.focused_workspace_layer() else {\n            return;\n        };\n\n        if (matches!(layer, WorkspaceLayer::Floating)\n            || bar.show_when_tiling && matches!(layer, WorkspaceLayer::Tiling))\n        {\n            let size = Vec2::splat(config.icon_font_id.size);\n            config.apply_on_widget(false, ui, |ui| {\n                let layer_frame = SelectableFrame::new(false)\n                    .show(ui, |ui| (bar.renderer)(ctx, ui, &layer, size))\n                    .on_hover_text(layer.to_string());\n\n                if layer_frame.clicked() {\n                    let _ = Self::send_messages(&[\n                        FocusMonitorAtCursor,\n                        MouseFollowsFocus(false),\n                        ToggleWorkspaceLayer,\n                        MouseFollowsFocus(monitor_info.mouse_follows_focus),\n                    ]);\n                }\n            });\n        }\n    }\n\n    /// Renders the configuration switcher bar.\n    /// For each available configuration, displays a selectable label.\n    /// On click, sends a message to replace the current Komorebi configuration.\n    fn render_config_switcher(&mut self, ui: &mut Ui, config: &mut RenderConfig) {\n        let configuration_switcher = match &self.configuration_switcher {\n            Some(config) if config.enable => config,\n            _ => return,\n        };\n        for (name, location) in configuration_switcher.configurations.iter() {\n            let path = Path::new(location);\n            if path.is_file() {\n                config.apply_on_widget(false, ui, |ui| {\n                    let response = SelectableFrame::new(false)\n                        .show(ui, |ui| ui.add(Label::new(name).selectable(false)));\n                    if response.clicked() {\n                        let path = dunce::canonicalize(path).unwrap_or_else(|_| path.to_owned());\n                        let _ = Self::send_messages(&[ReplaceConfiguration(path)]);\n                    }\n                });\n            }\n        }\n    }\n\n    fn render_layout(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {\n        if let Some(layout_config) = &self.layout\n            && layout_config.enable\n        {\n            let monitor_info = &mut *self.monitor_info.borrow_mut();\n            let workspace_idx = monitor_info.focused_workspace_idx;\n            monitor_info\n                .layout\n                .show(ctx, ui, config, layout_config, workspace_idx);\n        }\n    }\n\n    /// Renders the locked container bar for the current monitor.\n    ///\n    /// Shows lock status and allows toggling the lock state when clicked.\n    /// Only renders if a focused container exists and either the container is locked\n    /// or `show_when_unlocked` is enabled in the bar configuration.\n    fn render_locked_container(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {\n        let Some(bar) = &self.locked_container else {\n            return;\n        };\n\n        let monitor_info = &*self.monitor_info.borrow();\n        let is_locked = monitor_info\n            .focused_container()\n            .map(|container| container.is_locked)\n            .unwrap_or_default();\n\n        let (icon_font, txt_font) = (config.icon_font_id.clone(), config.text_font_id.clone());\n        if (bar.show_when_unlocked || is_locked) && monitor_info.focused_container().is_some() {\n            config.apply_on_widget(false, ui, |ui| {\n                SelectableFrame::new(false)\n                    .show(ui, |ui| {\n                        (bar.renderer)(ctx, ui, is_locked, icon_font, txt_font)\n                    })\n                    .clicked()\n                    .then(|| Self::send_messages(&[FocusMonitorAtCursor, ToggleLock]));\n            });\n        }\n    }\n\n    fn render_focused_container(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {\n        let Some(bar) = &self.focused_container else {\n            return;\n        };\n        let monitor_info = &*self.monitor_info.borrow();\n        let Some(container) = monitor_info.focused_container() else {\n            return;\n        };\n        config.apply_on_widget(false, ui, |ui| {\n            let (len, focused_idx) = (container.windows.len(), container.focused_window_idx);\n\n            for (idx, window) in container.windows.iter().enumerate() {\n                let selected = idx == focused_idx && len != 1;\n                let text_color = if selected {\n                    ctx.style().visuals.selection.stroke.color\n                } else {\n                    ui.style().visuals.text_color()\n                };\n\n                let response = SelectableFrame::new(selected).show(ui, |ui| {\n                    (bar.renderer)(bar, ctx, ui, window, text_color, idx == focused_idx)\n                });\n\n                if response.clicked() && !selected {\n                    let _ = Self::send_with_mouse_follow_off(monitor_info, FocusStackWindow(idx));\n                }\n            }\n        });\n    }\n\n    /// Sends a message to Komorebi, temporarily disabling MouseFollowsFocus if it's enabled.\n    fn send_with_mouse_follow_off(monitor: &MonitorInfo, message: SocketMessage) -> IoResult<()> {\n        let messages: &[SocketMessage] = if monitor.mouse_follows_focus {\n            &[MouseFollowsFocus(false), message, MouseFollowsFocus(true)]\n        } else {\n            &[message]\n        };\n        Self::send_messages(messages)\n    }\n\n    /// Sends a batch of messages to Komorebi, logging errors on failure.\n    fn send_messages(messages: &[SocketMessage]) -> IoResult<()> {\n        komorebi_client::send_batch(messages).map_err(|err| {\n            tracing::error!(\"Failed to send message(s): {:?}\\nError: {}\", messages, err);\n            err\n        })\n    }\n}\n\n/// Workspace bar with a pre-selected render strategy for efficient\n/// workspace display\n#[derive(Clone, Debug)]\npub struct WorkspacesBar {\n    /// Chosen rendering function for this widget\n    renderer: fn(&Self, &Context, &mut Ui, &WorkspaceInfo),\n    /// Text size (default: 12.5)\n    text_size: Vec2,\n    /// Icon size (default: 12.5 * 1.4)\n    icon_size: Vec2,\n}\n\nimpl WorkspacesBar {\n    /// Creates a `WorkspacesBar` instance from a workspace configuration.\n    ///\n    /// Selects a render strategy based on the given display format\n    /// for optimal performance. Returns `None` if the widget is disabled.\n    fn try_from(value: KomorebiWorkspacesConfig) -> Option<Self> {\n        use WorkspacesDisplayFormat::*;\n        if !value.enable {\n            return None;\n        }\n        // Selects a render strategy according to the workspace config's display format\n        // for better performance\n        let renderer: fn(&Self, &Context, &mut Ui, &WorkspaceInfo) =\n            match value.display.unwrap_or(DisplayFormat::Text.into()) {\n                // 1: Show icons if any, fallback if none | Only hover workspace name\n                AllIcons | Existing(DisplayFormat::Icon) => |bar, ctx, ui, ws| {\n                    bar.show_icons(ctx, ui, ws)\n                        .unwrap_or_else(|| bar.show_fallback_icon(ctx, ui, ws))\n                        .on_hover_text(&ws.name);\n                },\n                // 2: Show icons, with no fallback | Label workspace name (no hover)\n                AllIconsAndText | Existing(DisplayFormat::IconAndText) => |bar, ctx, ui, ws| {\n                    bar.show_icons(ctx, ui, ws);\n                    Self::show_label(ctx, ui, ws);\n                },\n                // 3: Show icons, fallback if no icons and not selected | Label workspace name if selected else hover\n                AllIconsAndTextOnSelected | Existing(DisplayFormat::IconAndTextOnSelected) => {\n                    |bar, ctx, ui, ws| {\n                        if bar.show_icons(ctx, ui, ws).is_none() && !ws.is_selected {\n                            bar.show_fallback_icon(ctx, ui, ws);\n                        }\n                        if ws.is_selected {\n                            Self::show_label(ctx, ui, ws);\n                        } else {\n                            ui.response().on_hover_text(&ws.name);\n                        }\n                    }\n                }\n                // 4: Show icons if selected and has icons (no fallback) | Label workspace name\n                Existing(DisplayFormat::TextAndIconOnSelected) => |bar, ctx, ui, ws| {\n                    if ws.is_selected {\n                        bar.show_icons(ctx, ui, ws);\n                    }\n                    Self::show_label(ctx, ui, ws);\n                },\n                // 5: Never show icon (no icons at all) | Label workspace name always\n                Existing(DisplayFormat::Text) => |_, ctx, ui, ws| {\n                    Self::show_label(ctx, ui, ws);\n                },\n            };\n\n        Some(Self {\n            renderer,\n            icon_size: Vec2::splat(12.5),\n            text_size: Vec2::splat(12.5 * 1.4),\n        })\n    }\n\n    /// Draws all window icons for the workspace, using larger size for the focused container.\n    /// Returns response if icons exist, or None.\n    fn show_icons(&self, ctx: &Context, ui: &mut Ui, ws: &WorkspaceInfo) -> Option<Response> {\n        ws.has_icons.then(|| {\n            Frame::NONE\n                .inner_margin(Margin::same(ui.style().spacing.button_padding.y as i8))\n                .show(ui, |ui| {\n                    for container in &ws.containers {\n                        for icon in container.windows.iter().filter_map(|win| win.icon.as_ref()) {\n                            ui.add(\n                                Image::from(&icon.texture(ctx))\n                                    .maintain_aspect_ratio(true)\n                                    .fit_to_exact_size(if container.is_focused {\n                                        self.icon_size\n                                    } else {\n                                        self.text_size\n                                    }),\n                            );\n                        }\n                    }\n                })\n                .response\n        })\n    }\n\n    /// Draws a fallback icon (a rectangle with a diagonal) for the workspace.\n    fn show_fallback_icon(&self, ctx: &Context, ui: &mut Ui, ws: &WorkspaceInfo) -> Response {\n        let (response, painter) = ui.allocate_painter(self.icon_size, Sense::hover());\n        let stroke: Stroke = Stroke::new(\n            1.0,\n            if ws.is_selected {\n                ctx.style().visuals.selection.stroke.color\n            } else {\n                ui.style().visuals.text_color()\n            },\n        );\n        let mut rect = response.rect;\n        let rounding = CornerRadius::same((rect.width() * 0.1) as u8);\n        rect = rect.shrink(stroke.width);\n        let c = rect.center();\n        let r = rect.width() / 2.0;\n        painter.rect_stroke(rect, rounding, stroke, StrokeKind::Outside);\n        painter.line_segment([c - vec2(r, r), c + vec2(r, r)], stroke);\n        response\n    }\n\n    /// Shows the workspace label (colored if selected).\n    fn show_label(ctx: &Context, ui: &mut Ui, ws: &WorkspaceInfo) -> Response {\n        if ws.is_selected {\n            let text = RichText::new(&ws.name).color(ctx.style().visuals.selection.stroke.color);\n            ui.add(Label::new(text).selectable(false))\n        } else {\n            ui.add(Label::new(&ws.name).selectable(false))\n        }\n    }\n}\n\n/// Structure for displaying the focused container's windows in the bar.\n/// Uses a pre-selected rendering strategy (based on config) for efficient display\n#[derive(Clone, Debug)]\npub struct FocusedContainerBar {\n    /// Chosen rendering function for this widget\n    renderer: fn(&Self, &Context, &mut Ui, &WindowInfo, Color32, focused: bool),\n    /// Icon size (default: 12.5 * 1.4)\n    icon_size: Vec2,\n}\n\nimpl FocusedContainerBar {\n    /// Creates a `FocusedContainerBar` from the given configuration.\n    ///\n    /// Selects a render strategy based on the display format or legacy icon setting.\n    /// Returns `None` if the widget is disabled.\n    fn try_from(value: KomorebiFocusedContainerConfig) -> Option<Self> {\n        if !value.enable {\n            return None;\n        }\n\n        // Handle legacy setting - convert show_icon to display format\n        #[allow(deprecated)]\n        let format = value\n            .display\n            .unwrap_or(if value.show_icon.unwrap_or(false) {\n                IconAndText\n            } else {\n                Text\n            });\n\n        // Select renderer strategy based on display format for better performance\n        let renderer: fn(&FocusedContainerBar, &Context, &mut Ui, &WindowInfo, Color32, bool) =\n            match format {\n                Icon => |_self, ctx, ui, info, _color, _focused| {\n                    FocusedContainerBar::show_icon::<true>(_self, ctx, ui, info);\n                },\n                Text => |_self, _ctx, ui, info, color, _focused| {\n                    FocusedContainerBar::show_title(_self, ui, info, color);\n                },\n                IconAndText => |_self, ctx, ui, info, color, _focused| {\n                    FocusedContainerBar::show_icon::<false>(_self, ctx, ui, info);\n                    FocusedContainerBar::show_title(_self, ui, info, color);\n                },\n                IconAndTextOnSelected => |_self, ctx, ui, info, color, focused| {\n                    FocusedContainerBar::show_icon::<false>(_self, ctx, ui, info);\n                    if focused {\n                        FocusedContainerBar::show_title(_self, ui, info, color);\n                    }\n                },\n                TextAndIconOnSelected => |_self, ctx, ui, info, color, focused| {\n                    if focused {\n                        FocusedContainerBar::show_icon::<false>(_self, ctx, ui, info);\n                    }\n                    FocusedContainerBar::show_title(_self, ui, info, color);\n                },\n            };\n\n        Some(FocusedContainerBar {\n            renderer,\n            icon_size: Vec2::splat(12.5 * 1.4),\n        })\n    }\n\n    /// Draws window icon(s) at configured size; adds hover text if HOVEL is true.\n    fn show_icon<const HOVEL: bool>(&self, ctx: &Context, ui: &mut Ui, info: &WindowInfo) {\n        let inner_response = Frame::NONE\n            .inner_margin(Margin::same(ui.style().spacing.button_padding.y as i8))\n            .show(ui, |ui| {\n                for icon in info.icon.iter() {\n                    let img = Image::from(&icon.texture(ctx)).maintain_aspect_ratio(true);\n                    ui.add(img.fit_to_exact_size(self.icon_size));\n                }\n            });\n        if HOVEL && let Some(title) = &info.title {\n            inner_response.response.on_hover_text(title);\n        }\n    }\n\n    /// Renders the window title label in color, truncating if needed.\n    fn show_title(&self, ui: &mut Ui, info: &WindowInfo, color: Color32) {\n        if let Some(title) = &info.title {\n            let text = Label::new(RichText::new(title).color(color)).selectable(false);\n\n            let x = MAX_LABEL_WIDTH.load(Ordering::SeqCst) as f32;\n            let y = ui.available_height();\n            CustomUi(ui).add_sized_left_to_right(Vec2::new(x, y), text.truncate());\n        }\n    }\n}\n\n/// Workspace layer bar with a pre-selected render strategy for efficient\n/// layer display\n#[derive(Clone, Debug)]\npub struct WorkspaceLayerBar {\n    /// Show the widget even if the layer is Tiling\n    show_when_tiling: bool,\n    /// Chosen rendering function for this widget\n    renderer: fn(&Context, &mut Ui, &WorkspaceLayer, Vec2),\n}\n\nimpl WorkspaceLayerBar {\n    /// Creates a `WorkspaceLayerBar` from the given configuration.\n    ///\n    /// Selects a render strategy based on the display format.\n    /// Returns `None` if the widget is disabled.\n    fn try_from(value: KomorebiWorkspaceLayerConfig) -> Option<Self> {\n        if !value.enable {\n            return None;\n        }\n        let display_format = value.display.unwrap_or(Text);\n\n        // Select renderer strategy based on display format for better performance\n        let renderer: fn(&Context, &mut Ui, &WorkspaceLayer, Vec2) = match display_format {\n            Icon => Self::draw_layer_icon,\n            Text => |_ctx, ui, layer, _size| {\n                ui.add(Label::new(layer.to_string()).selectable(false));\n            },\n            _ => |ctx, ui, layer, size| {\n                Self::draw_layer_icon(ctx, ui, layer, size);\n                ui.add(Label::new(layer.to_string()).selectable(false));\n            },\n        };\n\n        Some(Self {\n            show_when_tiling: value.show_when_tiling.unwrap_or_default(),\n            renderer,\n        })\n    }\n\n    /// Draws an icon representing the current workspace layer.\n    fn draw_layer_icon(ctx: &Context, ui: &mut Ui, layer: &WorkspaceLayer, size: Vec2) {\n        if matches!(layer, WorkspaceLayer::Tiling) {\n            let (response, painter) = ui.allocate_painter(size, Sense::hover());\n            let color = ctx.style().visuals.selection.stroke.color;\n            let stroke = Stroke::new(1.0, color);\n            let mut rect = response.rect;\n            let corner = CornerRadius::same((rect.width() * 0.1) as u8);\n            rect = rect.shrink(stroke.width);\n\n            // tiling\n            let mut rect_left = response.rect;\n            rect_left.set_width(rect.width() * 0.48);\n            rect_left.set_height(rect.height() * 0.98);\n            let mut rect_right = rect_left;\n            rect_left = rect_left.translate(Vec2::new(\n                rect.width() * 0.01 + stroke.width,\n                rect.width() * 0.01 + stroke.width,\n            ));\n            rect_right = rect_right.translate(Vec2::new(\n                rect.width() * 0.51 + stroke.width,\n                rect.width() * 0.01 + stroke.width,\n            ));\n            painter.rect_filled(rect_left, corner, color);\n            painter.rect_stroke(rect_right, corner, stroke, StrokeKind::Outside);\n        } else {\n            let (response, painter) = ui.allocate_painter(size, Sense::hover());\n            let color = ctx.style().visuals.selection.stroke.color;\n            let stroke = Stroke::new(1.0, color);\n            let mut rect = response.rect;\n            let corner = CornerRadius::same((rect.width() * 0.1) as u8);\n            rect = rect.shrink(stroke.width);\n\n            // floating\n            let mut rect_left = response.rect;\n            rect_left.set_width(rect.width() * 0.65);\n            rect_left.set_height(rect.height() * 0.65);\n            let mut rect_right = rect_left;\n            rect_left = rect_left.translate(Vec2::new(\n                rect.width() * 0.01 + stroke.width,\n                rect.width() * 0.01 + stroke.width,\n            ));\n            rect_right = rect_right.translate(Vec2::new(\n                rect.width() * 0.34 + stroke.width,\n                rect.width() * 0.34 + stroke.width,\n            ));\n            painter.rect_filled(rect_left, corner, color);\n            painter.rect_stroke(rect_right, corner, stroke, StrokeKind::Outside);\n        }\n    }\n}\n\n/// Locked container bar for displaying and interacting with container lock state\n#[derive(Clone, Debug)]\npub struct LockedContainerBar {\n    /// Show the widget even when unlocked\n    show_when_unlocked: bool,\n    /// Chosen rendering function for this widget\n    renderer: fn(&Context, &mut Ui, bool, FontId, FontId),\n}\n\nimpl LockedContainerBar {\n    /// Creates a `LockedContainerBar` from the given configuration.\n    ///\n    /// Selects a render strategy based on the display format.\n    /// Returns `None` if the widget is disabled.\n    fn try_from(value: KomorebiLockedContainerConfig) -> Option<Self> {\n        if !value.enable {\n            return None;\n        }\n        let display_format = value.display.unwrap_or(DisplayFormat::Text);\n\n        // Select renderer strategy based on display format for better performance\n        let renderer: fn(&Context, &mut Ui, bool, FontId, FontId) = match display_format {\n            DisplayFormat::Icon => |ctx, ui, is_locked, icon_font, _txt_font| {\n                let layout_job = Self::icon_layout(ctx, is_locked, icon_font);\n                ui.add(Label::new(layout_job).selectable(false));\n            },\n            DisplayFormat::Text => |ctx, ui, is_locked, _icon_font, txt_font| {\n                let mut layout_job = LayoutJob::default();\n                Self::append_text(&mut layout_job, ctx, is_locked, txt_font);\n                ui.add(Label::new(layout_job).selectable(false));\n            },\n            _ => |ctx, ui: &mut Ui, is_locked, icon_font, txt_font| {\n                let mut layout_job = Self::icon_layout(ctx, is_locked, icon_font);\n                Self::append_text(&mut layout_job, ctx, is_locked, txt_font);\n                ui.add(Label::new(layout_job).selectable(false));\n            },\n        };\n\n        Some(Self {\n            show_when_unlocked: value.show_when_unlocked.unwrap_or_default(),\n            renderer,\n        })\n    }\n\n    /// Builds a layout job for the lock/unlock icon,\n    /// using the provided font and current lock state.\n    fn icon_layout(ctx: &Context, is_locked: bool, icon_font: FontId) -> LayoutJob {\n        LayoutJob::simple(\n            if is_locked {\n                egui_phosphor::regular::LOCK_KEY.to_string()\n            } else {\n                egui_phosphor::regular::LOCK_SIMPLE_OPEN.to_string()\n            },\n            icon_font,\n            ctx.style().visuals.selection.stroke.color,\n            100.0,\n        )\n    }\n\n    /// Appends lock/unlock status text to the given layout job,\n    /// using the specified font and current lock state.\n    fn append_text(job: &mut LayoutJob, ctx: &Context, is_locked: bool, txt_font: FontId) {\n        job.append(\n            if is_locked { \"Locked\" } else { \"Unlocked\" },\n            10.0,\n            TextFormat {\n                font_id: txt_font,\n                color: ctx.style().visuals.text_color(),\n                valign: Align::Center,\n                ..Default::default()\n            },\n        )\n    }\n}\n\n/// Represents the full state of a single monitor for the Komorebi bar/UI.\n///\n/// Includes all workspaces, containers, windows, layout, focus,\n/// display options, and monitor indices. Used to render the current monitor\n/// state in UI widgets.\n///\n/// Updated whenever Komorebi state changes.\n#[derive(Clone, Debug)]\npub struct MonitorInfo {\n    pub workspaces: Vec<WorkspaceInfo>,\n    pub layout: KomorebiLayout,\n    pub mouse_follows_focus: bool,\n    pub work_area_offset: Option<Rect>,\n    pub monitor_index: usize,\n    pub monitor_usr_idx_map: HashMap<usize, usize>,\n    pub focused_workspace_idx: Option<usize>,\n    pub show_all_icons: bool,\n    pub hide_empty_workspaces: bool,\n}\n\nimpl Default for MonitorInfo {\n    fn default() -> Self {\n        Self {\n            workspaces: Vec::new(),\n            layout: KomorebiLayout::Default(komorebi_client::DefaultLayout::BSP),\n            mouse_follows_focus: true,\n            work_area_offset: None,\n            monitor_index: MONITOR_INDEX.load(Ordering::SeqCst),\n            monitor_usr_idx_map: HashMap::new(),\n            focused_workspace_idx: None,\n            show_all_icons: false,\n            hide_empty_workspaces: false,\n        }\n    }\n}\n\nimpl MonitorInfo {\n    /// Returns a reference to the currently focused workspace, if any.\n    pub fn focused_workspace(&self) -> Option<&WorkspaceInfo> {\n        self.workspaces.get(self.focused_workspace_idx?)\n    }\n\n    /// Returns the layer of the focused workspace, if available.\n    pub fn focused_workspace_layer(&self) -> Option<WorkspaceLayer> {\n        self.focused_workspace().map(|ws| ws.layer)\n    }\n\n    /// Returns the focused container within the focused workspace, if any.\n    pub fn focused_container(&self) -> Option<&ContainerInfo> {\n        self.focused_workspace()\n            .and_then(WorkspaceInfo::focused_container)\n    }\n}\n\nimpl MonitorInfo {\n    pub fn update_from_self(&mut self, config: &Self) {\n        self.hide_empty_workspaces = config.hide_empty_workspaces;\n    }\n\n    /// Updates monitor state from the given State, setting all fields based on the selected\n    /// monitor and its workspaces\n    pub fn update(&mut self, monitor_index: Option<usize>, state: State, show_all_icons: bool) {\n        self.show_all_icons = show_all_icons;\n        self.monitor_usr_idx_map = state.monitor_usr_idx_map;\n\n        match monitor_index {\n            Some(idx) if idx < state.monitors.elements().len() => self.monitor_index = idx,\n            // The bar's monitor is diconnected, so the bar is disabled no need to check anything\n            // any further otherwise we'll get `OutOfBounds` panics.\n            _ => return,\n        };\n        self.mouse_follows_focus = state.mouse_follows_focus;\n\n        let monitor = &state.monitors.elements()[self.monitor_index];\n        self.work_area_offset = monitor.work_area_offset;\n        self.focused_workspace_idx = Some(monitor.focused_workspace_idx());\n\n        // Layout\n        let focused_ws = &monitor.workspaces()[monitor.focused_workspace_idx()];\n        self.layout = Self::resolve_layout(focused_ws, state.is_paused);\n\n        self.workspaces.clear();\n        self.workspaces.extend(Self::workspaces(\n            self.show_all_icons,\n            self.hide_empty_workspaces,\n            self.focused_workspace_idx,\n            monitor.workspaces().iter().enumerate(),\n        ));\n    }\n\n    /// Builds an iterator of WorkspaceInfo for the monitor.\n    fn workspaces<'a, I>(\n        show_all_icons: bool,\n        hide_empty_ws: bool,\n        focused_ws_idx: Option<usize>,\n        iter: I,\n    ) -> impl Iterator<Item = WorkspaceInfo> + 'a\n    where\n        I: Iterator<Item = (usize, &'a Workspace)> + 'a,\n    {\n        let fn_containers_from = if show_all_icons {\n            |ws| ContainerInfo::from_all_containers(ws)\n        } else {\n            |ws| {\n                ContainerInfo::from_focused_container(ws)\n                    .into_iter()\n                    .collect()\n            }\n        };\n        iter.map(move |(index, ws)| {\n            let containers = fn_containers_from(ws);\n            WorkspaceInfo {\n                name: ws\n                    .name\n                    .to_owned()\n                    .unwrap_or_else(|| format!(\"{}\", index + 1)),\n                focused_container_idx: containers.iter().position(|c| c.is_focused),\n                has_icons: containers\n                    .iter()\n                    .any(|container| container.windows.iter().any(|window| window.icon.is_some())),\n                containers,\n                layer: ws.layer,\n                should_show: !hide_empty_ws || focused_ws_idx == Some(index) || !ws.is_empty(),\n                is_selected: focused_ws_idx == Some(index),\n            }\n        })\n    }\n\n    /// Determines the current layout of the focused workspace\n    fn resolve_layout(focused_ws: &Workspace, is_paused: bool) -> KomorebiLayout {\n        if focused_ws.monocle_container.is_some() {\n            KomorebiLayout::Monocle\n        } else if !focused_ws.tile {\n            KomorebiLayout::Floating\n        } else if is_paused {\n            KomorebiLayout::Paused\n        } else {\n            match focused_ws.layout {\n                komorebi_client::Layout::Default(layout) => KomorebiLayout::Default(layout),\n                komorebi_client::Layout::Custom(_) => KomorebiLayout::Custom,\n            }\n        }\n    }\n}\n\n/// Describes the state of a single workspace on a monitor.\n///\n/// Contains the workspace name, its containers (tiled, floating,\n/// monocle), focus state, layer (tiling/floating), and display\n/// flags for UI rendering.\n#[derive(Clone, Debug)]\npub struct WorkspaceInfo {\n    pub name: String,\n    pub containers: Vec<ContainerInfo>,\n    pub focused_container_idx: Option<usize>,\n    pub layer: WorkspaceLayer,\n    pub should_show: bool,\n    pub is_selected: bool,\n    pub has_icons: bool,\n}\n\nimpl WorkspaceInfo {\n    /// Returns a reference to the focused container in this workspace, if any.\n    pub fn focused_container(&self) -> Option<&ContainerInfo> {\n        self.containers.get(self.focused_container_idx?)\n    }\n}\n\n/// Holds information about a window container (tiled, floating, or monocle)\n/// within a workspace.\n///\n/// Includes a list of windows, the focused window index, and flags for focus\n/// and lock status.\n#[derive(Clone, Debug)]\npub struct ContainerInfo {\n    pub windows: Vec<WindowInfo>,\n    pub focused_window_idx: usize,\n    pub is_focused: bool,\n    pub is_locked: bool,\n}\n\nimpl ContainerInfo {\n    /// Returns all containers for the given workspace in the following order:\n    ///\n    /// 1. The monocle container (if present) is included first.\n    /// 2. All tiled containers are included next.\n    /// 3. All floating windows are added last, each as a separate container.\n    ///\n    /// Function ensures only one container is marked as focused, prioritizing\n    /// floating → monocle → tiled.\n    pub fn from_all_containers(ws: &Workspace) -> Vec<Self> {\n        let has_focused_float = ws.floating_windows().iter().any(|w| w.is_focused());\n\n        // Monocle container first if present\n        let monocle = ws\n            .monocle_container\n            .as_ref()\n            .map(|c| Self::from_container(c, !has_focused_float));\n\n        // All tiled containers, focus only if there's no monocle/focused float\n        let has_focused_monocle_or_float = has_focused_float || monocle.is_some();\n        let tiled = ws.containers().iter().enumerate().map(|(i, c)| {\n            let is_focused = !has_focused_monocle_or_float && i == ws.focused_container_idx();\n            Self::from_container(c, is_focused)\n        });\n\n        // All floating windows\n        let floats = ws.floating_windows().iter().map(Self::from_window);\n        // All windows\n        monocle.into_iter().chain(tiled).chain(floats).collect()\n    }\n\n    /// Creates a `ContainerInfo` for the currently focused item in the workspace.\n    ///\n    /// The function checks focus in the following order:\n    /// 1. Focused floating window\n    /// 2. Monocle container\n    /// 3. Focused tiled container\n    pub fn from_focused_container(ws: &Workspace) -> Option<Self> {\n        if let Some(window) = ws.floating_windows().iter().find(|w| w.is_focused()) {\n            return Some(Self::from_window(window));\n        }\n        if let Some(container) = &ws.monocle_container {\n            Some(Self::from_container(container, true))\n        } else {\n            ws.focused_container()\n                .map(|container| Self::from_container(container, true))\n        }\n    }\n\n    /// Creates a `ContainerInfo` from a given container.\n    pub fn from_container(container: &Container, is_focused: bool) -> Self {\n        Self {\n            windows: container.windows().iter().map(WindowInfo::from).collect(),\n            focused_window_idx: container.focused_window_idx(),\n            is_focused,\n            is_locked: container.locked,\n        }\n    }\n\n    /// Creates a `ContainerInfo` from a single floating window.\n    /// The window becomes the only entry in `windows`, is marked as focused\n    /// if applicable, and `is_locked` is set to false.\n    pub fn from_window(window: &Window) -> Self {\n        Self {\n            windows: vec![window.into()],\n            focused_window_idx: 0,\n            is_focused: window.is_focused(),\n            is_locked: false, // locked is only container feature\n        }\n    }\n}\n\n/// Stores basic information about a single window in a container.\n/// Contains the window's title and its icon, if available.\n#[derive(Clone, Debug)]\npub struct WindowInfo {\n    pub title: Option<String>,\n    pub icon: Option<ImageIcon>,\n}\n\nimpl From<&Window> for WindowInfo {\n    fn from(value: &Window) -> Self {\n        Self {\n            title: value.title().ok(),\n            icon: ImageIcon::try_load(value.hwnd, || {\n                windows_icons::get_icon_by_hwnd(value.hwnd)\n                    .or_else(|| windows_icons_fallback::get_icon_by_process_id(value.process_id()))\n            }),\n        }\n    }\n}\n"
  },
  {
    "path": "komorebi-bar/src/widgets/komorebi_layout.rs",
    "content": "use crate::config::DisplayFormat;\nuse crate::render::RenderConfig;\nuse crate::selected_frame::SelectableFrame;\nuse crate::widgets::komorebi::KomorebiLayoutConfig;\nuse color_eyre::eyre;\nuse eframe::egui::Context;\nuse eframe::egui::CornerRadius;\nuse eframe::egui::FontId;\nuse eframe::egui::Frame;\nuse eframe::egui::Label;\nuse eframe::egui::Sense;\nuse eframe::egui::Stroke;\nuse eframe::egui::StrokeKind;\nuse eframe::egui::Ui;\nuse eframe::egui::Vec2;\nuse eframe::egui::vec2;\nuse komorebi_client::SocketMessage;\nuse serde::Deserialize;\nuse serde::Deserializer;\nuse serde::Serialize;\nuse serde::de::Error;\nuse serde_json::from_str;\nuse std::fmt::Display;\nuse std::fmt::Formatter;\n\n#[derive(Copy, Clone, Debug, Serialize, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n#[serde(untagged)]\n/// Komorebi layout kind\npub enum KomorebiLayout {\n    /// Predefined layout\n    #[cfg_attr(feature = \"schemars\", schemars(title = \"Default\"))]\n    Default(komorebi_client::DefaultLayout),\n    /// Monocle mode\n    Monocle,\n    /// Floating layer\n    Floating,\n    /// Paused\n    Paused,\n    /// Custom layout\n    Custom,\n}\n\nimpl<'de> Deserialize<'de> for KomorebiLayout {\n    fn deserialize<D>(deserializer: D) -> eyre::Result<Self, D::Error>\n    where\n        D: Deserializer<'de>,\n    {\n        let s: String = String::deserialize(deserializer)?;\n\n        // Attempt to deserialize the string as a DefaultLayout\n        if let Ok(default_layout) = from_str::<komorebi_client::DefaultLayout>(&format!(\"\\\"{s}\\\"\"))\n        {\n            return Ok(KomorebiLayout::Default(default_layout));\n        }\n\n        // Handle other cases\n        match s.as_str() {\n            \"Monocle\" => Ok(KomorebiLayout::Monocle),\n            \"Floating\" => Ok(KomorebiLayout::Floating),\n            \"Paused\" => Ok(KomorebiLayout::Paused),\n            \"Custom\" => Ok(KomorebiLayout::Custom),\n            _ => Err(Error::custom(format!(\"Invalid layout: {s}\"))),\n        }\n    }\n}\n\nimpl Display for KomorebiLayout {\n    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {\n        match self {\n            KomorebiLayout::Default(layout) => write!(f, \"{layout}\"),\n            KomorebiLayout::Monocle => write!(f, \"Monocle\"),\n            KomorebiLayout::Floating => write!(f, \"Floating\"),\n            KomorebiLayout::Paused => write!(f, \"Paused\"),\n            KomorebiLayout::Custom => write!(f, \"Custom\"),\n        }\n    }\n}\n\nimpl KomorebiLayout {\n    fn is_default(&mut self) -> bool {\n        matches!(self, KomorebiLayout::Default(_))\n    }\n\n    fn on_click(\n        &mut self,\n        show_options: &bool,\n        monitor_idx: usize,\n        workspace_idx: Option<usize>,\n    ) -> bool {\n        if self.is_default() {\n            !show_options\n        } else {\n            self.on_click_option(monitor_idx, workspace_idx);\n            false\n        }\n    }\n\n    fn on_click_option(&mut self, monitor_idx: usize, workspace_idx: Option<usize>) {\n        match self {\n            KomorebiLayout::Default(option) => {\n                if let Some(ws_idx) = workspace_idx\n                    && komorebi_client::send_message(&SocketMessage::WorkspaceLayout(\n                        monitor_idx,\n                        ws_idx,\n                        *option,\n                    ))\n                    .is_err()\n                {\n                    tracing::error!(\"could not send message to komorebi: WorkspaceLayout\");\n                }\n            }\n            KomorebiLayout::Monocle => {\n                if komorebi_client::send_batch([\n                    SocketMessage::FocusMonitorAtCursor,\n                    SocketMessage::ToggleMonocle,\n                ])\n                .is_err()\n                {\n                    tracing::error!(\"could not send message to komorebi: ToggleMonocle\");\n                }\n            }\n            KomorebiLayout::Floating => {\n                if komorebi_client::send_batch([\n                    SocketMessage::FocusMonitorAtCursor,\n                    SocketMessage::ToggleTiling,\n                ])\n                .is_err()\n                {\n                    tracing::error!(\"could not send message to komorebi: ToggleTiling\");\n                }\n            }\n            KomorebiLayout::Paused => {\n                if komorebi_client::send_message(&SocketMessage::TogglePause).is_err() {\n                    tracing::error!(\"could not send message to komorebi: TogglePause\");\n                }\n            }\n            KomorebiLayout::Custom => {}\n        }\n    }\n\n    fn show_icon(&mut self, is_selected: bool, font_id: FontId, ctx: &Context, ui: &mut Ui) {\n        // paint custom icons for the layout\n        let size = Vec2::splat(font_id.size);\n        let (response, painter) = ui.allocate_painter(size, Sense::hover());\n        let color = if is_selected {\n            ctx.style().visuals.selection.stroke.color\n        } else {\n            ui.style().visuals.text_color()\n        };\n        let stroke = Stroke::new(1.0, color);\n        let mut rect = response.rect;\n        let rounding = CornerRadius::same((rect.width() * 0.1) as u8);\n        rect = rect.shrink(stroke.width);\n        let c = rect.center();\n        let r = rect.width() / 2.0;\n        painter.rect_stroke(rect, rounding, stroke, StrokeKind::Outside);\n\n        match self {\n            KomorebiLayout::Default(layout) => match layout {\n                komorebi_client::DefaultLayout::BSP => {\n                    painter.line_segment([c - vec2(0.0, r), c + vec2(0.0, r)], stroke);\n                    painter.line_segment([c, c + vec2(r, 0.0)], stroke);\n                    painter.line_segment([c + vec2(r / 2.0, 0.0), c + vec2(r / 2.0, r)], stroke);\n                }\n                komorebi_client::DefaultLayout::Columns => {\n                    painter.line_segment([c - vec2(r / 2.0, r), c + vec2(-r / 2.0, r)], stroke);\n                    painter.line_segment([c - vec2(0.0, r), c + vec2(0.0, r)], stroke);\n                    painter.line_segment([c - vec2(-r / 2.0, r), c + vec2(r / 2.0, r)], stroke);\n                }\n                komorebi_client::DefaultLayout::Rows => {\n                    painter.line_segment([c - vec2(r, r / 2.0), c + vec2(r, -r / 2.0)], stroke);\n                    painter.line_segment([c - vec2(r, 0.0), c + vec2(r, 0.0)], stroke);\n                    painter.line_segment([c - vec2(r, -r / 2.0), c + vec2(r, r / 2.0)], stroke);\n                }\n                komorebi_client::DefaultLayout::VerticalStack => {\n                    painter.line_segment([c - vec2(0.0, r), c + vec2(0.0, r)], stroke);\n                    painter.line_segment([c, c + vec2(r, 0.0)], stroke);\n                }\n                komorebi_client::DefaultLayout::RightMainVerticalStack => {\n                    painter.line_segment([c - vec2(0.0, r), c + vec2(0.0, r)], stroke);\n                    painter.line_segment([c - vec2(r, 0.0), c], stroke);\n                }\n                komorebi_client::DefaultLayout::HorizontalStack => {\n                    painter.line_segment([c - vec2(r, 0.0), c + vec2(r, 0.0)], stroke);\n                    painter.line_segment([c, c + vec2(0.0, r)], stroke);\n                }\n                komorebi_client::DefaultLayout::UltrawideVerticalStack => {\n                    painter.line_segment([c - vec2(r / 2.0, r), c + vec2(-r / 2.0, r)], stroke);\n                    painter.line_segment([c + vec2(r / 2.0, 0.0), c + vec2(r, 0.0)], stroke);\n                    painter.line_segment([c - vec2(-r / 2.0, r), c + vec2(r / 2.0, r)], stroke);\n                }\n                komorebi_client::DefaultLayout::Grid => {\n                    painter.line_segment([c - vec2(r, 0.0), c + vec2(r, 0.0)], stroke);\n                    painter.line_segment([c - vec2(0.0, r), c + vec2(0.0, r)], stroke);\n                }\n                // TODO: @CtByte can you think of a nice icon to draw here?\n                komorebi_client::DefaultLayout::Scrolling => {\n                    painter.line_segment([c - vec2(r / 2.0, r), c + vec2(-r / 2.0, r)], stroke);\n                    painter.line_segment([c - vec2(0.0, r), c + vec2(0.0, r)], stroke);\n                    painter.line_segment([c - vec2(-r / 2.0, r), c + vec2(r / 2.0, r)], stroke);\n                }\n            },\n            KomorebiLayout::Monocle => {}\n            KomorebiLayout::Floating => {\n                let mut rect_left = response.rect;\n                rect_left.set_width(rect.width() * 0.5);\n                rect_left.set_height(rect.height() * 0.5);\n                let mut rect_right = rect_left;\n                rect_left = rect_left.translate(Vec2::new(\n                    rect.width() * 0.1 + stroke.width,\n                    rect.width() * 0.1 + stroke.width,\n                ));\n                rect_right = rect_right.translate(Vec2::new(\n                    rect.width() * 0.35 + stroke.width,\n                    rect.width() * 0.35 + stroke.width,\n                ));\n                painter.rect_filled(rect_left, rounding, color);\n                painter.rect_stroke(rect_right, rounding, stroke, StrokeKind::Outside);\n            }\n            KomorebiLayout::Paused => {\n                let mut rect_left = response.rect;\n                rect_left.set_width(rect.width() * 0.25);\n                rect_left.set_height(rect.height() * 0.8);\n                let mut rect_right = rect_left;\n                rect_left = rect_left.translate(Vec2::new(\n                    rect.width() * 0.2 + stroke.width,\n                    rect.width() * 0.1 + stroke.width,\n                ));\n                rect_right = rect_right.translate(Vec2::new(\n                    rect.width() * 0.55 + stroke.width,\n                    rect.width() * 0.1 + stroke.width,\n                ));\n                painter.rect_filled(rect_left, rounding, color);\n                painter.rect_filled(rect_right, rounding, color);\n            }\n            KomorebiLayout::Custom => {\n                painter.line_segment([c - vec2(0.0, r), c + vec2(0.0, r)], stroke);\n                painter.line_segment([c + vec2(0.0, r / 2.0), c + vec2(r, r / 2.0)], stroke);\n                painter.line_segment([c - vec2(0.0, r / 3.0), c - vec2(r, r / 3.0)], stroke);\n            }\n        }\n    }\n\n    pub fn show(\n        &mut self,\n        ctx: &Context,\n        ui: &mut Ui,\n        render_config: &mut RenderConfig,\n        layout_config: &KomorebiLayoutConfig,\n        workspace_idx: Option<usize>,\n    ) {\n        let monitor_idx = render_config.monitor_idx;\n        let font_id = render_config.icon_font_id.clone();\n        let mut show_options = RenderConfig::load_show_komorebi_layout_options();\n        let format = layout_config.display.unwrap_or(DisplayFormat::IconAndText);\n\n        if !self.is_default() {\n            show_options = false;\n        }\n\n        render_config.apply_on_widget(false, ui, |ui| {\n            let layout_frame = SelectableFrame::new(false)\n                .show(ui, |ui| {\n                    if let DisplayFormat::Icon | DisplayFormat::IconAndText = format {\n                        self.show_icon(true, font_id.clone(), ctx, ui);\n                    }\n\n                    if let DisplayFormat::Text | DisplayFormat::IconAndText = format {\n                        ui.add(Label::new(self.to_string()).selectable(false));\n                    }\n                })\n                .on_hover_text(self.to_string());\n\n            if layout_frame.clicked() {\n                show_options = self.on_click(&show_options, monitor_idx, workspace_idx);\n            }\n\n            if show_options && let Some(workspace_idx) = workspace_idx {\n                Frame::NONE.show(ui, |ui| {\n                    ui.add(\n                        Label::new(egui_phosphor::regular::ARROW_FAT_LINES_RIGHT.to_string())\n                            .selectable(false),\n                    );\n\n                    let mut layout_options = layout_config.options.clone().unwrap_or(vec![\n                        KomorebiLayout::Default(komorebi_client::DefaultLayout::BSP),\n                        KomorebiLayout::Default(komorebi_client::DefaultLayout::Columns),\n                        KomorebiLayout::Default(komorebi_client::DefaultLayout::Rows),\n                        KomorebiLayout::Default(komorebi_client::DefaultLayout::VerticalStack),\n                        KomorebiLayout::Default(\n                            komorebi_client::DefaultLayout::RightMainVerticalStack,\n                        ),\n                        KomorebiLayout::Default(komorebi_client::DefaultLayout::HorizontalStack),\n                        KomorebiLayout::Default(\n                            komorebi_client::DefaultLayout::UltrawideVerticalStack,\n                        ),\n                        KomorebiLayout::Default(komorebi_client::DefaultLayout::Grid),\n                        //KomorebiLayout::Custom,\n                        KomorebiLayout::Monocle,\n                        KomorebiLayout::Floating,\n                        KomorebiLayout::Paused,\n                    ]);\n\n                    for layout_option in &mut layout_options {\n                        let is_selected = self == layout_option;\n\n                        if SelectableFrame::new(is_selected)\n                            .show(ui, |ui| {\n                                layout_option.show_icon(is_selected, font_id.clone(), ctx, ui)\n                            })\n                            .on_hover_text(match layout_option {\n                                KomorebiLayout::Default(layout) => layout.to_string(),\n                                KomorebiLayout::Monocle => \"Toggle monocle\".to_string(),\n                                KomorebiLayout::Floating => \"Toggle tiling\".to_string(),\n                                KomorebiLayout::Paused => \"Toggle pause\".to_string(),\n                                KomorebiLayout::Custom => \"Custom\".to_string(),\n                            })\n                            .clicked()\n                        {\n                            layout_option.on_click_option(monitor_idx, Some(workspace_idx));\n                            show_options = false;\n                        };\n                    }\n                });\n            }\n        });\n\n        RenderConfig::store_show_komorebi_layout_options(show_options);\n    }\n}\n"
  },
  {
    "path": "komorebi-bar/src/widgets/media.rs",
    "content": "use crate::MAX_LABEL_WIDTH;\nuse crate::bar::Alignment;\nuse crate::config::MediaDisplayFormat;\nuse crate::render::RenderConfig;\nuse crate::selected_frame::SelectableFrame;\nuse crate::ui::CustomUi;\nuse crate::widgets::widget::BarWidget;\nuse eframe::egui::Align;\nuse eframe::egui::Context;\nuse eframe::egui::Label;\nuse eframe::egui::TextFormat;\nuse eframe::egui::Ui;\nuse eframe::egui::Vec2;\nuse eframe::egui::text::LayoutJob;\nuse serde::Deserialize;\nuse serde::Serialize;\nuse std::sync::atomic::Ordering;\nuse windows::Media::Control::GlobalSystemMediaTransportControlsSessionManager;\nuse windows::Media::Control::GlobalSystemMediaTransportControlsSessionPlaybackStatus;\n\n#[derive(Copy, Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Media widget configuration\npub struct MediaConfig {\n    /// Enable the Media widget\n    pub enable: bool,\n    /// Display format of the media widget (defaults to IconAndText)\n    pub display: Option<MediaDisplayFormat>,\n}\n\nimpl From<MediaConfig> for Media {\n    fn from(value: MediaConfig) -> Self {\n        Self::new(\n            value.enable,\n            value.display.unwrap_or(MediaDisplayFormat::IconAndText),\n        )\n    }\n}\n\n#[derive(Clone, Debug)]\npub struct Media {\n    pub enable: bool,\n    pub display: MediaDisplayFormat,\n    pub session_manager: GlobalSystemMediaTransportControlsSessionManager,\n}\n\nimpl Media {\n    pub fn new(enable: bool, display: MediaDisplayFormat) -> Self {\n        Self {\n            enable,\n            display,\n            session_manager: GlobalSystemMediaTransportControlsSessionManager::RequestAsync()\n                .unwrap()\n                .join()\n                .unwrap(),\n        }\n    }\n\n    pub fn toggle(&self) {\n        if let Ok(session) = self.session_manager.GetCurrentSession()\n            && let Ok(op) = session.TryTogglePlayPauseAsync()\n        {\n            op.join().unwrap_or_default();\n        }\n    }\n\n    pub fn previous(&self) {\n        if let Ok(session) = self.session_manager.GetCurrentSession()\n            && let Ok(op) = session.TrySkipPreviousAsync()\n        {\n            op.join().unwrap_or_default();\n        }\n    }\n\n    pub fn next(&self) {\n        if let Ok(session) = self.session_manager.GetCurrentSession()\n            && let Ok(op) = session.TrySkipNextAsync()\n        {\n            op.join().unwrap_or_default();\n        }\n    }\n\n    fn is_playing(&self) -> bool {\n        if let Ok(session) = self.session_manager.GetCurrentSession()\n            && let Ok(info) = session.GetPlaybackInfo()\n            && let Ok(status) = info.PlaybackStatus()\n        {\n            return status == GlobalSystemMediaTransportControlsSessionPlaybackStatus::Playing;\n        }\n        false\n    }\n\n    fn is_previous_enabled(&self) -> bool {\n        if let Ok(session) = self.session_manager.GetCurrentSession()\n            && let Ok(info) = session.GetPlaybackInfo()\n            && let Ok(controls) = info.Controls()\n            && let Ok(enabled) = controls.IsPreviousEnabled()\n        {\n            return enabled;\n        }\n        false\n    }\n\n    fn is_next_enabled(&self) -> bool {\n        if let Ok(session) = self.session_manager.GetCurrentSession()\n            && let Ok(info) = session.GetPlaybackInfo()\n            && let Ok(controls) = info.Controls()\n            && let Ok(enabled) = controls.IsNextEnabled()\n        {\n            return enabled;\n        }\n        false\n    }\n\n    fn has_session(&self) -> bool {\n        self.session_manager.GetCurrentSession().is_ok()\n    }\n\n    fn output(&mut self) -> String {\n        if let Ok(session) = self.session_manager.GetCurrentSession()\n            && let Ok(operation) = session.TryGetMediaPropertiesAsync()\n            && let Ok(properties) = operation.join()\n            && let (Ok(artist), Ok(title)) = (properties.Artist(), properties.Title())\n        {\n            if artist.is_empty() {\n                return format!(\"{title}\");\n            }\n\n            if title.is_empty() {\n                return format!(\"{artist}\");\n            }\n\n            return format!(\"{artist} - {title}\");\n        }\n\n        String::new()\n    }\n}\n\nimpl BarWidget for Media {\n    fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {\n        if self.enable {\n            // Don't render if there's no active media session\n            if !self.has_session() {\n                return;\n            }\n\n            let output = self.output();\n\n            let show_icon = matches!(\n                self.display,\n                MediaDisplayFormat::Icon\n                    | MediaDisplayFormat::IconAndText\n                    | MediaDisplayFormat::IconAndControls\n                    | MediaDisplayFormat::Full\n            );\n            let show_text = matches!(\n                self.display,\n                MediaDisplayFormat::Text\n                    | MediaDisplayFormat::IconAndText\n                    | MediaDisplayFormat::TextAndControls\n                    | MediaDisplayFormat::Full\n            );\n            let show_controls = matches!(\n                self.display,\n                MediaDisplayFormat::ControlsOnly\n                    | MediaDisplayFormat::IconAndControls\n                    | MediaDisplayFormat::TextAndControls\n                    | MediaDisplayFormat::Full\n            );\n\n            // Don't render if there's no media info and we're not showing controls-only\n            if output.is_empty() && !show_controls {\n                return;\n            }\n\n            let icon_font_id = config.icon_font_id.clone();\n            let text_font_id = config.text_font_id.clone();\n            let icon_color = ctx.style().visuals.selection.stroke.color;\n            let text_color = ctx.style().visuals.text_color();\n\n            let mut layout_job = LayoutJob::default();\n\n            if show_icon {\n                layout_job = LayoutJob::simple(\n                    egui_phosphor::regular::HEADPHONES.to_string(),\n                    icon_font_id.clone(),\n                    icon_color,\n                    100.0,\n                );\n            }\n\n            if show_text {\n                layout_job.append(\n                    &output,\n                    if show_icon { 10.0 } else { 0.0 },\n                    TextFormat {\n                        font_id: text_font_id,\n                        color: text_color,\n                        valign: Align::Center,\n                        ..Default::default()\n                    },\n                );\n            }\n\n            let is_playing = self.is_playing();\n            let is_previous_enabled = self.is_previous_enabled();\n            let is_next_enabled = self.is_next_enabled();\n            let disabled_color = text_color.gamma_multiply(0.5);\n            let is_reversed = matches!(config.alignment, Some(Alignment::Right));\n\n            let prev_color = if is_previous_enabled {\n                text_color\n            } else {\n                disabled_color\n            };\n\n            let next_color = if is_next_enabled {\n                text_color\n            } else {\n                disabled_color\n            };\n\n            let play_pause_icon = if is_playing {\n                egui_phosphor::regular::PAUSE\n            } else {\n                egui_phosphor::regular::PLAY\n            };\n\n            let show_label = |ui: &mut Ui| {\n                if (show_icon || show_text)\n                    && SelectableFrame::new(false)\n                        .show(ui, |ui| {\n                            let available_height = ui.available_height();\n                            let mut custom_ui = CustomUi(ui);\n\n                            custom_ui.add_sized_left_to_right(\n                                Vec2::new(\n                                    MAX_LABEL_WIDTH.load(Ordering::SeqCst) as f32,\n                                    available_height,\n                                ),\n                                Label::new(layout_job.clone()).selectable(false).truncate(),\n                            )\n                        })\n                        .on_hover_text(&output)\n                        .clicked()\n                {\n                    self.toggle();\n                }\n            };\n\n            let show_previous = |ui: &mut Ui| {\n                if SelectableFrame::new(false)\n                    .show(ui, |ui| {\n                        ui.add(\n                            Label::new(LayoutJob::simple(\n                                egui_phosphor::regular::SKIP_BACK.to_string(),\n                                icon_font_id.clone(),\n                                prev_color,\n                                100.0,\n                            ))\n                            .selectable(false),\n                        )\n                    })\n                    .clicked()\n                    && is_previous_enabled\n                {\n                    self.previous();\n                }\n            };\n\n            let show_play_pause = |ui: &mut Ui| {\n                if SelectableFrame::new(false)\n                    .show(ui, |ui| {\n                        ui.add(\n                            Label::new(LayoutJob::simple(\n                                play_pause_icon.to_string(),\n                                icon_font_id.clone(),\n                                text_color,\n                                100.0,\n                            ))\n                            .selectable(false),\n                        )\n                    })\n                    .on_hover_text(&output)\n                    .clicked()\n                {\n                    self.toggle();\n                }\n            };\n\n            let show_next = |ui: &mut Ui| {\n                if SelectableFrame::new(false)\n                    .show(ui, |ui| {\n                        ui.add(\n                            Label::new(LayoutJob::simple(\n                                egui_phosphor::regular::SKIP_FORWARD.to_string(),\n                                icon_font_id.clone(),\n                                next_color,\n                                100.0,\n                            ))\n                            .selectable(false),\n                        )\n                    })\n                    .clicked()\n                    && is_next_enabled\n                {\n                    self.next();\n                }\n            };\n\n            config.apply_on_widget(false, ui, |ui| {\n                if is_reversed {\n                    // Right panel renders right-to-left, so reverse order\n                    if show_controls {\n                        show_next(ui);\n                        show_play_pause(ui);\n                        show_previous(ui);\n                    }\n                    show_label(ui);\n                } else {\n                    // Left/center panel renders left-to-right, normal order\n                    show_label(ui);\n                    if show_controls {\n                        show_previous(ui);\n                        show_play_pause(ui);\n                        show_next(ui);\n                    }\n                }\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "komorebi-bar/src/widgets/memory.rs",
    "content": "use crate::config::LabelPrefix;\nuse crate::render::RenderConfig;\nuse crate::selected_frame::SelectableFrame;\nuse crate::widgets::widget::BarWidget;\nuse eframe::egui::Align;\nuse eframe::egui::Context;\nuse eframe::egui::Label;\nuse eframe::egui::TextFormat;\nuse eframe::egui::Ui;\nuse eframe::egui::text::LayoutJob;\nuse serde::Deserialize;\nuse serde::Serialize;\nuse std::process::Command;\nuse std::time::Duration;\nuse std::time::Instant;\nuse sysinfo::RefreshKind;\nuse sysinfo::System;\n\n#[derive(Copy, Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Memory widget configuration\npub struct MemoryConfig {\n    /// Enable the Memory widget\n    pub enable: bool,\n    /// Data refresh interval in seconds\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = 10)))]\n    pub data_refresh_interval: Option<u64>,\n    /// Display label prefix\n    pub label_prefix: Option<LabelPrefix>,\n    /// Select when the current percentage is over this value [[1-100]]\n    pub auto_select_over: Option<u8>,\n}\n\nimpl From<MemoryConfig> for Memory {\n    fn from(value: MemoryConfig) -> Self {\n        let data_refresh_interval = value.data_refresh_interval.unwrap_or(10);\n\n        Self {\n            enable: value.enable,\n            system: System::new_with_specifics(\n                RefreshKind::default().without_cpu().without_processes(),\n            ),\n            data_refresh_interval,\n            label_prefix: value.label_prefix.unwrap_or(LabelPrefix::IconAndText),\n            auto_select_over: value.auto_select_over.map(|o| o.clamp(1, 100)),\n            last_updated: Instant::now()\n                .checked_sub(Duration::from_secs(data_refresh_interval))\n                .unwrap(),\n        }\n    }\n}\n\n#[derive(Clone, Debug)]\nstruct MemoryOutput {\n    label: String,\n    selected: bool,\n}\n\npub struct Memory {\n    pub enable: bool,\n    system: System,\n    data_refresh_interval: u64,\n    label_prefix: LabelPrefix,\n    auto_select_over: Option<u8>,\n    last_updated: Instant,\n}\n\nimpl Memory {\n    fn output(&mut self) -> MemoryOutput {\n        let now = Instant::now();\n        if now.duration_since(self.last_updated) > Duration::from_secs(self.data_refresh_interval) {\n            self.system.refresh_memory();\n            self.last_updated = now;\n        }\n\n        let used = self.system.used_memory();\n        let total = self.system.total_memory();\n        let usage = ((used * 100) / total) as u8;\n        let selected = self.auto_select_over.is_some_and(|o| usage >= o);\n\n        MemoryOutput {\n            label: match self.label_prefix {\n                LabelPrefix::Text | LabelPrefix::IconAndText => {\n                    format!(\"RAM: {usage}%\")\n                }\n                LabelPrefix::None | LabelPrefix::Icon => format!(\"{usage}%\"),\n            },\n            selected,\n        }\n    }\n}\n\nimpl BarWidget for Memory {\n    fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {\n        if self.enable {\n            let output = self.output();\n            if !output.label.is_empty() {\n                let auto_text_color = config.auto_select_text.filter(|_| output.selected);\n\n                let mut layout_job = LayoutJob::simple(\n                    match self.label_prefix {\n                        LabelPrefix::Icon | LabelPrefix::IconAndText => {\n                            egui_phosphor::regular::MEMORY.to_string()\n                        }\n                        LabelPrefix::None | LabelPrefix::Text => String::new(),\n                    },\n                    config.icon_font_id.clone(),\n                    auto_text_color.unwrap_or(ctx.style().visuals.selection.stroke.color),\n                    100.0,\n                );\n\n                layout_job.append(\n                    &output.label,\n                    10.0,\n                    TextFormat {\n                        font_id: config.text_font_id.clone(),\n                        color: auto_text_color.unwrap_or(ctx.style().visuals.text_color()),\n                        valign: Align::Center,\n                        ..Default::default()\n                    },\n                );\n\n                let auto_focus_fill = config.auto_select_fill;\n\n                config.apply_on_widget(false, ui, |ui| {\n                    if SelectableFrame::new_auto(output.selected, auto_focus_fill)\n                        .show(ui, |ui| ui.add(Label::new(layout_job).selectable(false)))\n                        .clicked()\n                        && let Err(error) =\n                            Command::new(\"cmd.exe\").args([\"/C\", \"taskmgr.exe\"]).spawn()\n                    {\n                        eprintln!(\"{error}\")\n                    }\n                });\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "komorebi-bar/src/widgets/mod.rs",
    "content": "use eframe::egui::ColorImage;\nuse eframe::egui::Context;\nuse eframe::egui::TextureHandle;\nuse eframe::egui::TextureOptions;\nuse image::RgbaImage;\nuse std::collections::HashMap;\nuse std::path::Path;\nuse std::sync::Arc;\nuse std::sync::LazyLock;\nuse std::sync::RwLock;\n\npub mod applications;\npub mod battery;\npub mod cpu;\npub mod date;\npub mod keyboard;\npub mod komorebi;\nmod komorebi_layout;\npub mod media;\npub mod memory;\npub mod network;\npub mod storage;\npub mod time;\npub mod update;\npub mod widget;\n\n/// Global cache for icon images and their associated GPU textures.\npub static ICONS_CACHE: IconsCache = IconsCache::new();\n\n/// In-memory cache for icon images and their associated GPU textures.\n///\n/// Stores raw [`ColorImage`]s and [`TextureHandle`]s keyed by [`ImageIconId`].\n/// Texture entries are context-dependent and automatically invalidated when the [`Context`] changes.\n#[allow(clippy::type_complexity)]\npub struct IconsCache {\n    textures: LazyLock<RwLock<(Option<Context>, HashMap<ImageIconId, TextureHandle>)>>,\n    images: LazyLock<RwLock<HashMap<ImageIconId, Arc<ColorImage>>>>,\n}\n\nimpl IconsCache {\n    /// Creates a new empty IconsCache instance.\n    #[inline]\n    pub const fn new() -> Self {\n        Self {\n            textures: LazyLock::new(|| RwLock::new((None, HashMap::new()))),\n            images: LazyLock::new(|| RwLock::new(HashMap::new())),\n        }\n    }\n\n    /// Retrieves or creates a texture handle for the given icon ID and image.\n    ///\n    /// If a texture for the given ID already exists for the current [`Context`], it is reused.\n    /// Otherwise, a new texture is created, inserted into the cache, and returned.\n    /// The cache is reset if the [`Context`] has changed.\n    #[inline]\n    pub fn texture(&self, ctx: &Context, id: &ImageIconId, img: &Arc<ColorImage>) -> TextureHandle {\n        if let Some(texture) = self.get_texture(ctx, id) {\n            return texture;\n        }\n        let texture_handle = ctx.load_texture(\"icon\", img.clone(), TextureOptions::default());\n        self.insert_texture(ctx, id.clone(), texture_handle.clone());\n        texture_handle\n    }\n\n    /// Returns the cached texture for the given icon ID if it exists and matches the current [`Context`].\n    pub fn get_texture(&self, ctx: &Context, id: &ImageIconId) -> Option<TextureHandle> {\n        let textures_lock = self.textures.read().unwrap();\n        if textures_lock.0.as_ref() == Some(ctx) {\n            return textures_lock.1.get(id).cloned();\n        }\n        None\n    }\n\n    /// Inserts a texture handle, resetting the cache if the [`Context`] has changed.\n    pub fn insert_texture(&self, ctx: &Context, id: ImageIconId, texture: TextureHandle) {\n        let mut textures_lock = self.textures.write().unwrap();\n\n        if textures_lock.0.as_ref() != Some(ctx) {\n            textures_lock.0 = Some(ctx.clone());\n            textures_lock.1.clear();\n        }\n\n        textures_lock.1.insert(id, texture);\n    }\n\n    /// Returns the cached image for the given icon ID, if available.\n    pub fn get_image(&self, id: &ImageIconId) -> Option<Arc<ColorImage>> {\n        self.images.read().unwrap().get(id).cloned()\n    }\n\n    /// Caches a raw [`ColorImage`] associated with the given icon ID.\n    pub fn insert_image(&self, id: ImageIconId, image: Arc<ColorImage>) {\n        self.images.write().unwrap().insert(id, image);\n    }\n}\n\n#[inline]\nfn rgba_to_color_image(rgba_image: &RgbaImage) -> ColorImage {\n    let size = [rgba_image.width() as usize, rgba_image.height() as usize];\n    let pixels = rgba_image.as_flat_samples();\n    ColorImage::from_rgba_unmultiplied(size, pixels.as_slice())\n}\n\n/// Represents an image-based icon with a unique ID and pixel data.\n#[derive(Clone, Debug)]\npub struct ImageIcon {\n    /// Unique identifier for the image icon, used for texture caching.\n    pub id: ImageIconId,\n    /// Shared pixel data of the icon in `ColorImage` format.\n    pub image: Arc<ColorImage>,\n}\n\nimpl ImageIcon {\n    /// Creates a new [`ImageIcon`] from the given ID and image data.\n    #[inline]\n    pub fn new(id: ImageIconId, image: Arc<ColorImage>) -> Self {\n        Self { id, image }\n    }\n\n    /// Loads an [`ImageIcon`] from [`ICONS_CACHE`] or calls `loader` if not cached.\n    /// The loaded image is converted to a [`ColorImage`], cached, and returned.\n    #[inline]\n    pub fn try_load<F, I>(id: impl Into<ImageIconId>, loader: F) -> Option<Self>\n    where\n        F: FnOnce() -> Option<I>,\n        I: Into<RgbaImage>,\n    {\n        let id = id.into();\n        let image = ICONS_CACHE.get_image(&id).or_else(|| {\n            let img = loader()?;\n            let img = Arc::new(rgba_to_color_image(&img.into()));\n            ICONS_CACHE.insert_image(id.clone(), img.clone());\n            Some(img)\n        })?;\n\n        Some(ImageIcon::new(id, image))\n    }\n\n    /// Returns a texture handle for the icon, using the given [`Context`].\n    ///\n    /// If the texture is already cached in [`ICONS_CACHE`], it is reused.\n    /// Otherwise, a new texture is created from the [`ColorImage`] and cached.\n    #[inline]\n    pub fn texture(&self, ctx: &Context) -> TextureHandle {\n        ICONS_CACHE.texture(ctx, &self.id, &self.image)\n    }\n}\n\n/// Unique identifier for an image-based icon.\n///\n/// Used to distinguish cached images and textures by either a file path\n/// or a Windows window handle.\n#[derive(Clone, Debug, Eq, Hash, PartialEq)]\npub enum ImageIconId {\n    /// Identifier based on a file system path.\n    Path(Arc<Path>),\n    /// Windows HWND handle.\n    Hwnd(isize),\n}\n\nimpl From<&Path> for ImageIconId {\n    #[inline]\n    fn from(value: &Path) -> Self {\n        Self::Path(value.into())\n    }\n}\n\nimpl From<isize> for ImageIconId {\n    #[inline]\n    fn from(value: isize) -> Self {\n        Self::Hwnd(value)\n    }\n}\n"
  },
  {
    "path": "komorebi-bar/src/widgets/network.rs",
    "content": "use crate::bar::Alignment;\nuse crate::config::LabelPrefix;\nuse crate::render::RenderConfig;\nuse crate::selected_frame::SelectableFrame;\nuse crate::widgets::widget::BarWidget;\nuse eframe::egui::Align;\nuse eframe::egui::Color32;\nuse eframe::egui::Context;\nuse eframe::egui::Label;\nuse eframe::egui::TextFormat;\nuse eframe::egui::Ui;\nuse eframe::egui::text::LayoutJob;\nuse num_derive::FromPrimitive;\nuse serde::Deserialize;\nuse serde::Serialize;\nuse std::fmt;\nuse std::process::Command;\nuse std::sync::Arc;\nuse std::sync::Mutex;\nuse std::sync::atomic::AtomicU64;\nuse std::sync::atomic::Ordering;\nuse std::thread;\nuse std::time::Duration;\nuse std::time::Instant;\nuse sysinfo::Networks;\n\n#[derive(Copy, Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Network widget configuration\npub struct NetworkConfig {\n    /// Enable the Network widget\n    pub enable: bool,\n    /// Show total received and transmitted activity\n    #[serde(alias = \"show_total_data_transmitted\")]\n    pub show_total_activity: bool,\n    /// Show received and transmitted activity\n    #[serde(alias = \"show_network_activity\")]\n    pub show_activity: bool,\n    /// Show default interface\n    pub show_default_interface: Option<bool>,\n    /// Characters to reserve for received and transmitted activity\n    #[serde(alias = \"network_activity_fill_characters\")]\n    pub activity_left_padding: Option<usize>,\n    /// Data refresh interval in seconds\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = 10)))]\n    pub data_refresh_interval: Option<u64>,\n    /// Display label prefix\n    pub label_prefix: Option<LabelPrefix>,\n    /// Select when the value is over a limit (1MiB is 1048576 bytes (1024*1024))\n    pub auto_select: Option<NetworkSelectConfig>,\n}\n\n#[derive(Copy, Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Network select configuration\npub struct NetworkSelectConfig {\n    /// Select the total received data when it's over this value\n    pub total_received_over: Option<u64>,\n    /// Select the total transmitted data when it's over this value\n    pub total_transmitted_over: Option<u64>,\n    /// Select the received data when it's over this value\n    pub received_over: Option<u64>,\n    /// Select the transmitted data when it's over this value\n    pub transmitted_over: Option<u64>,\n}\n\nimpl From<NetworkConfig> for Network {\n    fn from(value: NetworkConfig) -> Self {\n        let default_refresh_interval = 10;\n        let data_refresh_interval = value\n            .data_refresh_interval\n            .unwrap_or(default_refresh_interval);\n\n        Self {\n            enable: value.enable,\n            show_total_activity: value.show_total_activity,\n            show_activity: value.show_activity,\n            show_default_interface: value.show_default_interface.unwrap_or(true),\n            networks_network_activity: Arc::new(Mutex::new(Networks::new_with_refreshed_list())),\n            default_interface: Arc::new(Mutex::new(String::new())),\n            interface_generation: Arc::new(AtomicU64::new(0)),\n            default_refresh_interval,\n            data_refresh_interval,\n            label_prefix: value.label_prefix.unwrap_or(LabelPrefix::Icon),\n            auto_select: value.auto_select,\n            activity_left_padding: value.activity_left_padding.unwrap_or_default(),\n            last_update_request_default_interface: Instant::now()\n                .checked_sub(Duration::from_secs(default_refresh_interval))\n                .unwrap(),\n            last_state_total_activity: Arc::new(Mutex::new(vec![])),\n            last_state_activity: Arc::new(Mutex::new(vec![])),\n            last_update_request_network_activity: Arc::new(Mutex::new(\n                Instant::now()\n                    .checked_sub(Duration::from_secs(data_refresh_interval))\n                    .unwrap(),\n            )),\n            activity_generation: Arc::new(AtomicU64::new(0)),\n        }\n    }\n}\n\npub struct Network {\n    pub enable: bool,\n    pub show_total_activity: bool,\n    pub show_activity: bool,\n    pub show_default_interface: bool,\n    networks_network_activity: Arc<Mutex<Networks>>,\n    default_refresh_interval: u64,\n    data_refresh_interval: u64,\n    label_prefix: LabelPrefix,\n    auto_select: Option<NetworkSelectConfig>,\n    default_interface: Arc<Mutex<String>>,\n    interface_generation: Arc<AtomicU64>,\n    last_update_request_default_interface: Instant,\n    activity_generation: Arc<AtomicU64>,\n    last_state_activity: Arc<Mutex<Vec<NetworkReading>>>,\n    last_state_total_activity: Arc<Mutex<Vec<NetworkReading>>>,\n    last_update_request_network_activity: Arc<Mutex<Instant>>,\n    activity_left_padding: usize,\n}\n\nimpl Network {\n    fn update_default_interface_async(&mut self) {\n        let gen_ = self.interface_generation.fetch_add(1, Ordering::SeqCst) + 1;\n        let gen_arc = Arc::clone(&self.interface_generation);\n        let iface_arc = Arc::clone(&self.default_interface);\n\n        thread::spawn(move || {\n            if let Ok(interface) = netdev::get_default_interface()\n                && let Some(friendly_name) = &interface.friendly_name\n            {\n                // Only update if this is the latest request\n                if gen_ == gen_arc.load(Ordering::SeqCst)\n                    && let Ok(mut iface) = iface_arc.lock()\n                {\n                    *iface = friendly_name.clone();\n                }\n            }\n        });\n    }\n\n    fn default_interface(&mut self) -> String {\n        let current = self.default_interface.lock().unwrap().clone();\n        let now = Instant::now();\n\n        if now.duration_since(self.last_update_request_default_interface)\n            > Duration::from_secs(self.default_refresh_interval)\n        {\n            self.last_update_request_default_interface = now;\n            self.update_default_interface_async();\n        }\n\n        current\n    }\n\n    fn update_network_activity_async(&mut self) {\n        let gen_ = self.activity_generation.fetch_add(1, Ordering::SeqCst) + 1;\n        let gen_arc = Arc::clone(&self.activity_generation);\n        let activity_arc = Arc::clone(&self.last_state_activity);\n        let total_activity_arc = Arc::clone(&self.last_state_total_activity);\n        let data_refresh_interval = self.data_refresh_interval;\n        let show_activity = self.show_activity;\n        let show_total_activity = self.show_total_activity;\n        let networks_network_activity_arc = Arc::clone(&self.networks_network_activity);\n\n        thread::spawn(move || {\n            let mut activity = Vec::new();\n            let mut total_activity = Vec::new();\n\n            if let Ok(interface) = netdev::get_default_interface()\n                && let Some(friendly_name) = &interface.friendly_name\n                && let Ok(mut networks) = networks_network_activity_arc.lock()\n            {\n                networks.refresh(true);\n\n                for (interface_name, data) in &*networks {\n                    if friendly_name.eq(interface_name) {\n                        if show_activity {\n                            let received =\n                                Network::to_pretty_bytes(data.received(), data_refresh_interval);\n                            let transmitted =\n                                Network::to_pretty_bytes(data.transmitted(), data_refresh_interval);\n\n                            activity.push(NetworkReading::new(\n                                NetworkReadingFormat::Speed,\n                                ReadingValue::from(received),\n                                ReadingValue::from(transmitted),\n                            ));\n                        }\n\n                        if show_total_activity {\n                            let total_received = Network::to_pretty_bytes(data.total_received(), 1);\n                            let total_transmitted =\n                                Network::to_pretty_bytes(data.total_transmitted(), 1);\n\n                            total_activity.push(NetworkReading::new(\n                                NetworkReadingFormat::Total,\n                                ReadingValue::from(total_received),\n                                ReadingValue::from(total_transmitted),\n                            ));\n                        }\n                    }\n                }\n            }\n\n            // Only update if this is the latest request\n            if gen_ == gen_arc.load(Ordering::SeqCst) {\n                if let Ok(mut act) = activity_arc.lock() {\n                    *act = activity;\n                }\n                if let Ok(mut tot) = total_activity_arc.lock() {\n                    *tot = total_activity;\n                }\n            }\n        });\n    }\n\n    fn network_activity(&mut self) -> (Vec<NetworkReading>, Vec<NetworkReading>) {\n        let now = Instant::now();\n        let should_update = {\n            let last_update_request = self.last_update_request_network_activity.lock().unwrap();\n            now.duration_since(*last_update_request)\n                > Duration::from_secs(self.data_refresh_interval)\n        };\n\n        if should_update {\n            {\n                let mut last_updated = self.last_update_request_network_activity.lock().unwrap();\n                *last_updated = now;\n            }\n            self.update_network_activity_async();\n        }\n\n        self.get_network_activity()\n    }\n\n    fn get_network_activity(&self) -> (Vec<NetworkReading>, Vec<NetworkReading>) {\n        let activity = self.last_state_activity.lock().unwrap().clone();\n        let total_activity = self.last_state_total_activity.lock().unwrap().clone();\n        (activity, total_activity)\n    }\n\n    fn reading_to_labels(\n        &self,\n        select_received: bool,\n        select_transmitted: bool,\n        ctx: &Context,\n        reading: &NetworkReading,\n        config: RenderConfig,\n    ) -> (Label, Label) {\n        let (text_down, text_up) = match self.label_prefix {\n            LabelPrefix::None | LabelPrefix::Icon => match reading.format {\n                NetworkReadingFormat::Speed => (\n                    format!(\n                        \"{: >width$}/s \",\n                        reading.received.pretty,\n                        width = self.activity_left_padding\n                    ),\n                    format!(\n                        \"{: >width$}/s\",\n                        reading.transmitted.pretty,\n                        width = self.activity_left_padding\n                    ),\n                ),\n                NetworkReadingFormat::Total => (\n                    format!(\"{} \", reading.received.pretty),\n                    reading.transmitted.pretty.clone(),\n                ),\n            },\n            LabelPrefix::Text | LabelPrefix::IconAndText => match reading.format {\n                NetworkReadingFormat::Speed => (\n                    format!(\n                        \"DOWN: {: >width$}/s \",\n                        reading.received.pretty,\n                        width = self.activity_left_padding\n                    ),\n                    format!(\n                        \"UP: {: >width$}/s\",\n                        reading.transmitted.pretty,\n                        width = self.activity_left_padding\n                    ),\n                ),\n                NetworkReadingFormat::Total => (\n                    format!(\"\\u{2211}DOWN: {}/s \", reading.received.pretty),\n                    format!(\"\\u{2211}UP: {}/s\", reading.transmitted.pretty),\n                ),\n            },\n        };\n\n        let auto_text_color_received = config.auto_select_text.filter(|_| select_received);\n        let auto_text_color_transmitted = config.auto_select_text.filter(|_| select_transmitted);\n\n        // icon\n        let mut layout_job_down = LayoutJob::simple(\n            match self.label_prefix {\n                LabelPrefix::Icon | LabelPrefix::IconAndText => {\n                    if select_received {\n                        egui_phosphor::regular::ARROW_FAT_LINES_DOWN.to_string()\n                    } else {\n                        egui_phosphor::regular::ARROW_FAT_DOWN.to_string()\n                    }\n                }\n                LabelPrefix::None | LabelPrefix::Text => String::new(),\n            },\n            config.icon_font_id.clone(),\n            auto_text_color_received.unwrap_or(ctx.style().visuals.selection.stroke.color),\n            100.0,\n        );\n\n        // text\n        layout_job_down.append(\n            &text_down,\n            ctx.style().spacing.item_spacing.x,\n            TextFormat {\n                font_id: config.text_font_id.clone(),\n                color: auto_text_color_received.unwrap_or(ctx.style().visuals.text_color()),\n                valign: Align::Center,\n                ..Default::default()\n            },\n        );\n\n        // icon\n        let mut layout_job_up = LayoutJob::simple(\n            match self.label_prefix {\n                LabelPrefix::Icon | LabelPrefix::IconAndText => {\n                    if select_transmitted {\n                        egui_phosphor::regular::ARROW_FAT_LINES_UP.to_string()\n                    } else {\n                        egui_phosphor::regular::ARROW_FAT_UP.to_string()\n                    }\n                }\n                LabelPrefix::None | LabelPrefix::Text => String::new(),\n            },\n            config.icon_font_id.clone(),\n            auto_text_color_transmitted.unwrap_or(ctx.style().visuals.selection.stroke.color),\n            100.0,\n        );\n\n        // text\n        layout_job_up.append(\n            &text_up,\n            ctx.style().spacing.item_spacing.x,\n            TextFormat {\n                font_id: config.text_font_id.clone(),\n                color: auto_text_color_transmitted.unwrap_or(ctx.style().visuals.text_color()),\n                valign: Align::Center,\n                ..Default::default()\n            },\n        );\n\n        (\n            Label::new(layout_job_down).selectable(false),\n            Label::new(layout_job_up).selectable(false),\n        )\n    }\n\n    fn to_pretty_bytes(input_in_bytes: u64, timespan_in_s: u64) -> (u64, String) {\n        let input = input_in_bytes as f32 / timespan_in_s as f32;\n        let mut magnitude = input.log(1024f32) as u32;\n\n        // let the base unit be KiB\n        if magnitude < 1 {\n            magnitude = 1;\n        }\n\n        let base: Option<DataUnit> = num::FromPrimitive::from_u32(magnitude);\n        let result = input / ((1u64) << (magnitude * 10)) as f32;\n\n        (\n            input as u64,\n            match base {\n                Some(DataUnit::B) => format!(\"{result:.1} B\"),\n                Some(unit) => format!(\"{result:.1} {unit}iB\"),\n                None => String::from(\"Unknown data unit\"),\n            },\n        )\n    }\n\n    fn show_frame<R>(\n        &self,\n        selected: bool,\n        auto_focus_fill: Option<Color32>,\n        ui: &mut Ui,\n        add_contents: impl FnOnce(&mut Ui) -> R,\n    ) {\n        if SelectableFrame::new_auto(selected, auto_focus_fill)\n            .show(ui, add_contents)\n            .clicked()\n            && let Err(error) = Command::new(\"cmd.exe\").args([\"/C\", \"ncpa\"]).spawn()\n        {\n            eprintln!(\"{error}\");\n        }\n    }\n}\n\nimpl BarWidget for Network {\n    fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {\n        if self.enable {\n            let is_reversed = matches!(config.alignment, Some(Alignment::Right));\n\n            // widget spacing: make sure to use the same config to call the apply_on_widget function\n            let mut render_config = config.clone();\n\n            if self.show_total_activity || self.show_activity {\n                let (activity, total_activity) = self.network_activity();\n\n                if self.show_total_activity {\n                    for reading in &total_activity {\n                        render_config.apply_on_widget(false, ui, |ui| {\n                            let select_received = self.auto_select.is_some_and(|f| {\n                                f.total_received_over\n                                    .is_some_and(|o| reading.received.value > o)\n                            });\n                            let select_transmitted = self.auto_select.is_some_and(|f| {\n                                f.total_transmitted_over\n                                    .is_some_and(|o| reading.transmitted.value > o)\n                            });\n\n                            let labels = self.reading_to_labels(\n                                select_received,\n                                select_transmitted,\n                                ctx,\n                                reading,\n                                config.clone(),\n                            );\n\n                            if is_reversed {\n                                self.show_frame(\n                                    select_transmitted,\n                                    config.auto_select_fill,\n                                    ui,\n                                    |ui| ui.add(labels.1),\n                                );\n                                self.show_frame(\n                                    select_received,\n                                    config.auto_select_fill,\n                                    ui,\n                                    |ui| ui.add(labels.0),\n                                );\n                            } else {\n                                self.show_frame(\n                                    select_received,\n                                    config.auto_select_fill,\n                                    ui,\n                                    |ui| ui.add(labels.0),\n                                );\n                                self.show_frame(\n                                    select_transmitted,\n                                    config.auto_select_fill,\n                                    ui,\n                                    |ui| ui.add(labels.1),\n                                );\n                            }\n                        });\n                    }\n                }\n\n                if self.show_activity {\n                    for reading in &activity {\n                        render_config.apply_on_widget(false, ui, |ui| {\n                            let select_received = self.auto_select.is_some_and(|f| {\n                                f.received_over.is_some_and(|o| reading.received.value > o)\n                            });\n                            let select_transmitted = self.auto_select.is_some_and(|f| {\n                                f.transmitted_over\n                                    .is_some_and(|o| reading.transmitted.value > o)\n                            });\n\n                            let labels = self.reading_to_labels(\n                                select_received,\n                                select_transmitted,\n                                ctx,\n                                reading,\n                                config.clone(),\n                            );\n\n                            if is_reversed {\n                                self.show_frame(\n                                    select_transmitted,\n                                    config.auto_select_fill,\n                                    ui,\n                                    |ui| ui.add(labels.1),\n                                );\n                                self.show_frame(\n                                    select_received,\n                                    config.auto_select_fill,\n                                    ui,\n                                    |ui| ui.add(labels.0),\n                                );\n                            } else {\n                                self.show_frame(\n                                    select_received,\n                                    config.auto_select_fill,\n                                    ui,\n                                    |ui| ui.add(labels.0),\n                                );\n                                self.show_frame(\n                                    select_transmitted,\n                                    config.auto_select_fill,\n                                    ui,\n                                    |ui| ui.add(labels.1),\n                                );\n                            }\n                        });\n                    }\n                }\n            }\n\n            if self.show_default_interface {\n                let mut self_default_interface = self.default_interface();\n\n                if !self_default_interface.is_empty() {\n                    let mut layout_job = LayoutJob::simple(\n                        match self.label_prefix {\n                            LabelPrefix::Icon | LabelPrefix::IconAndText => {\n                                egui_phosphor::regular::WIFI_HIGH.to_string()\n                            }\n                            LabelPrefix::None | LabelPrefix::Text => String::new(),\n                        },\n                        config.icon_font_id.clone(),\n                        ctx.style().visuals.selection.stroke.color,\n                        100.0,\n                    );\n\n                    if let LabelPrefix::Text | LabelPrefix::IconAndText = self.label_prefix {\n                        self_default_interface.insert_str(0, \"NET: \");\n                    }\n\n                    layout_job.append(\n                        &self_default_interface,\n                        10.0,\n                        TextFormat {\n                            font_id: config.text_font_id.clone(),\n                            color: ctx.style().visuals.text_color(),\n                            valign: Align::Center,\n                            ..Default::default()\n                        },\n                    );\n\n                    render_config.apply_on_widget(false, ui, |ui| {\n                        self.show_frame(false, None, ui, |ui| {\n                            ui.add(Label::new(layout_job).selectable(false))\n                        });\n                    });\n                }\n            }\n\n            // widget spacing: pass on the config that was use for calling the apply_on_widget function\n            *config = render_config.clone();\n        }\n    }\n}\n\n#[derive(Clone)]\nenum NetworkReadingFormat {\n    Speed = 0,\n    Total = 1,\n}\n\n#[derive(Clone)]\nstruct ReadingValue {\n    value: u64,\n    pretty: String,\n}\n\nimpl From<(u64, String)> for ReadingValue {\n    fn from(value: (u64, String)) -> Self {\n        Self {\n            value: value.0,\n            pretty: value.1,\n        }\n    }\n}\n\n#[derive(Clone)]\nstruct NetworkReading {\n    format: NetworkReadingFormat,\n    received: ReadingValue,\n    transmitted: ReadingValue,\n}\n\nimpl NetworkReading {\n    fn new(\n        format: NetworkReadingFormat,\n        received: ReadingValue,\n        transmitted: ReadingValue,\n    ) -> Self {\n        Self {\n            format,\n            received,\n            transmitted,\n        }\n    }\n}\n\n#[derive(Debug, FromPrimitive)]\nenum DataUnit {\n    B = 0,\n    K = 1,\n    M = 2,\n    G = 3,\n    T = 4,\n    P = 5,\n    E = 6,\n    Z = 7,\n    Y = 8,\n}\n\nimpl fmt::Display for DataUnit {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        write!(f, \"{self:?}\")\n    }\n}\n"
  },
  {
    "path": "komorebi-bar/src/widgets/storage.rs",
    "content": "use crate::bar::Alignment;\nuse crate::config::LabelPrefix;\nuse crate::render::RenderConfig;\nuse crate::selected_frame::SelectableFrame;\nuse crate::widgets::widget::BarWidget;\nuse eframe::egui::Align;\nuse eframe::egui::Context;\nuse eframe::egui::Label;\nuse eframe::egui::TextFormat;\nuse eframe::egui::Ui;\nuse eframe::egui::text::LayoutJob;\nuse serde::Deserialize;\nuse serde::Serialize;\nuse std::process::Command;\nuse std::time::Duration;\nuse std::time::Instant;\nuse sysinfo::Disks;\n\n#[derive(Copy, Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Storage widget configuration\npub struct StorageConfig {\n    /// Enable the Storage widget\n    pub enable: bool,\n    /// Data refresh interval in seconds\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = 10)))]\n    pub data_refresh_interval: Option<u64>,\n    /// Display label prefix\n    pub label_prefix: Option<LabelPrefix>,\n    /// Show disks that are read only\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = false)))]\n    pub show_read_only_disks: Option<bool>,\n    /// Show removable disks\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = true)))]\n    pub show_removable_disks: Option<bool>,\n    /// Select when the current percentage is over this value [[1-100]]\n    pub auto_select_over: Option<u8>,\n    /// Hide when the current percentage is under this value [[1-100]]\n    pub auto_hide_under: Option<u8>,\n}\n\nimpl From<StorageConfig> for Storage {\n    fn from(value: StorageConfig) -> Self {\n        Self {\n            enable: value.enable,\n            disks: Disks::new_with_refreshed_list(),\n            data_refresh_interval: value.data_refresh_interval.unwrap_or(10),\n            label_prefix: value.label_prefix.unwrap_or(LabelPrefix::IconAndText),\n            show_read_only_disks: value.show_read_only_disks.unwrap_or(false),\n            show_removable_disks: value.show_removable_disks.unwrap_or(true),\n            auto_select_over: value.auto_select_over.map(|o| o.clamp(1, 100)),\n            auto_hide_under: value.auto_hide_under.map(|o| o.clamp(1, 100)),\n            last_updated: Instant::now(),\n        }\n    }\n}\n\nstruct StorageDisk {\n    label: String,\n    selected: bool,\n}\n\npub struct Storage {\n    pub enable: bool,\n    disks: Disks,\n    data_refresh_interval: u64,\n    label_prefix: LabelPrefix,\n    show_read_only_disks: bool,\n    show_removable_disks: bool,\n    auto_select_over: Option<u8>,\n    auto_hide_under: Option<u8>,\n    last_updated: Instant,\n}\n\nimpl Storage {\n    fn output(&mut self) -> Vec<StorageDisk> {\n        let now = Instant::now();\n        if now.duration_since(self.last_updated) > Duration::from_secs(self.data_refresh_interval) {\n            self.disks.refresh(true);\n            self.last_updated = now;\n        }\n\n        let mut disks = vec![];\n\n        for disk in &self.disks {\n            if disk.is_read_only() && !self.show_read_only_disks {\n                continue;\n            }\n            if disk.is_removable() && !self.show_removable_disks {\n                continue;\n            }\n            let mount = disk.mount_point();\n            let total = disk.total_space();\n            let available = disk.available_space();\n            let used = total - available;\n            let percentage = ((used * 100) / total) as u8;\n\n            let hide = self.auto_hide_under.is_some_and(|u| percentage <= u);\n\n            if !hide {\n                let selected = self.auto_select_over.is_some_and(|o| percentage >= o);\n\n                disks.push(StorageDisk {\n                    label: match self.label_prefix {\n                        LabelPrefix::Text | LabelPrefix::IconAndText => {\n                            format!(\"{} {}%\", mount.to_string_lossy(), percentage)\n                        }\n                        LabelPrefix::None | LabelPrefix::Icon => format!(\"{percentage}%\"),\n                    },\n                    selected,\n                })\n            }\n        }\n\n        disks.sort_by(|a, b| a.label.cmp(&b.label));\n\n        disks\n    }\n}\n\nimpl BarWidget for Storage {\n    fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {\n        if self.enable {\n            let mut output = self.output();\n            let is_reversed = matches!(config.alignment, Some(Alignment::Right));\n\n            if is_reversed {\n                output.reverse();\n            }\n\n            for output in output {\n                let auto_text_color = config.auto_select_text.filter(|_| output.selected);\n\n                let mut layout_job = LayoutJob::simple(\n                    match self.label_prefix {\n                        LabelPrefix::Icon | LabelPrefix::IconAndText => {\n                            egui_phosphor::regular::HARD_DRIVES.to_string()\n                        }\n                        LabelPrefix::None | LabelPrefix::Text => String::new(),\n                    },\n                    config.icon_font_id.clone(),\n                    auto_text_color.unwrap_or(ctx.style().visuals.selection.stroke.color),\n                    100.0,\n                );\n\n                layout_job.append(\n                    &output.label,\n                    10.0,\n                    TextFormat {\n                        font_id: config.text_font_id.clone(),\n                        color: auto_text_color.unwrap_or(ctx.style().visuals.text_color()),\n                        valign: Align::Center,\n                        ..Default::default()\n                    },\n                );\n\n                let auto_focus_fill = config.auto_select_fill;\n\n                config.apply_on_widget(false, ui, |ui| {\n                    if SelectableFrame::new_auto(output.selected, auto_focus_fill)\n                        .show(ui, |ui| ui.add(Label::new(layout_job).selectable(false)))\n                        .clicked()\n                        && let Err(error) = Command::new(\"cmd.exe\")\n                            .args([\n                                \"/C\",\n                                \"explorer.exe\",\n                                output.label.split(' ').collect::<Vec<&str>>()[0],\n                            ])\n                            .spawn()\n                    {\n                        eprintln!(\"{error}\")\n                    }\n                });\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "komorebi-bar/src/widgets/time.rs",
    "content": "use crate::bar::Alignment;\nuse crate::config::LabelPrefix;\nuse crate::render::RenderConfig;\nuse crate::selected_frame::SelectableFrame;\nuse crate::widgets::widget::BarWidget;\nuse chrono::Local;\nuse chrono::NaiveTime;\nuse chrono_tz::Tz;\nuse eframe::egui::Align;\nuse eframe::egui::Context;\nuse eframe::egui::CornerRadius;\nuse eframe::egui::Label;\nuse eframe::egui::Sense;\nuse eframe::egui::Stroke;\nuse eframe::egui::TextFormat;\nuse eframe::egui::Ui;\nuse eframe::egui::Vec2;\nuse eframe::egui::text::LayoutJob;\nuse eframe::epaint::StrokeKind;\nuse lazy_static::lazy_static;\nuse serde::Deserialize;\nuse serde::Serialize;\nuse std::time::Duration;\nuse std::time::Instant;\n\nlazy_static! {\n    static ref TIME_RANGES: Vec<(&'static str, NaiveTime)> = {\n        vec![\n            (\n                egui_phosphor::regular::MOON,\n                NaiveTime::from_hms_opt(0, 0, 0).expect(\"invalid\"),\n            ),\n            (\n                egui_phosphor::regular::ALARM,\n                NaiveTime::from_hms_opt(6, 0, 0).expect(\"invalid\"),\n            ),\n            (\n                egui_phosphor::regular::BREAD,\n                NaiveTime::from_hms_opt(6, 1, 0).expect(\"invalid\"),\n            ),\n            (\n                egui_phosphor::regular::BARBELL,\n                NaiveTime::from_hms_opt(6, 30, 0).expect(\"invalid\"),\n            ),\n            (\n                egui_phosphor::regular::COFFEE,\n                NaiveTime::from_hms_opt(8, 0, 0).expect(\"invalid\"),\n            ),\n            (\n                egui_phosphor::regular::CLOCK,\n                NaiveTime::from_hms_opt(8, 30, 0).expect(\"invalid\"),\n            ),\n            (\n                egui_phosphor::regular::HAMBURGER,\n                NaiveTime::from_hms_opt(12, 0, 0).expect(\"invalid\"),\n            ),\n            (\n                egui_phosphor::regular::CLOCK_AFTERNOON,\n                NaiveTime::from_hms_opt(12, 30, 0).expect(\"invalid\"),\n            ),\n            (\n                egui_phosphor::regular::FORK_KNIFE,\n                NaiveTime::from_hms_opt(18, 0, 0).expect(\"invalid\"),\n            ),\n            (\n                egui_phosphor::regular::MOON_STARS,\n                NaiveTime::from_hms_opt(18, 30, 0).expect(\"invalid\"),\n            ),\n        ]\n    };\n}\n\n#[derive(Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Time widget configuration\npub struct TimeConfig {\n    /// Enable the Time widget\n    pub enable: bool,\n    /// Set the Time format\n    pub format: TimeFormat,\n    /// Display label prefix\n    pub label_prefix: Option<LabelPrefix>,\n    /// TimeZone (https://docs.rs/chrono-tz/latest/chrono_tz/enum.Tz.html)\n    ///\n    /// Use a custom format to display additional information, i.e.:\n    /// ```json\n    /// {\n    ///     \"Time\": {\n    ///         \"enable\": true,\n    ///         \"format\": { \"Custom\": \"%T %Z (Tokyo)\" },\n    ///         \"timezone\": \"Asia/Tokyo\"\n    ///      }\n    ///}\n    /// ```\n    pub timezone: Option<String>,\n    /// Change the icon depending on the time. The default icon is used between 8:30 and 12:00\n    pub changing_icon: Option<bool>,\n}\n\nimpl From<TimeConfig> for Time {\n    fn from(value: TimeConfig) -> Self {\n        // using 1 second made the widget look \"less accurate\" and lagging (especially having multiple with seconds).\n        // This is still better than getting an update every frame\n        let data_refresh_interval = 500;\n\n        Self {\n            enable: value.enable,\n            format: value.format,\n            label_prefix: value.label_prefix.unwrap_or(LabelPrefix::Icon),\n            timezone: value.timezone,\n            changing_icon: value.changing_icon.unwrap_or_default(),\n            data_refresh_interval_millis: data_refresh_interval,\n            last_state: TimeOutput::new(),\n            last_updated: Instant::now()\n                .checked_sub(Duration::from_millis(data_refresh_interval))\n                .unwrap(),\n        }\n    }\n}\n\n#[derive(Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Time format\npub enum TimeFormat {\n    /// Twelve-hour format (with seconds)\n    TwelveHour,\n    /// Twelve-hour format (without seconds)\n    TwelveHourWithoutSeconds,\n    /// Twenty-four-hour format (with seconds)\n    TwentyFourHour,\n    /// Twenty-four-hour format (without seconds)\n    TwentyFourHourWithoutSeconds,\n    /// Twenty-four-hour format displayed as a binary clock with circles (with seconds) (https://en.wikipedia.org/wiki/Binary_clock)\n    BinaryCircle,\n    /// Twenty-four-hour format displayed as a binary clock with rectangles (with seconds) (https://en.wikipedia.org/wiki/Binary_clock)\n    BinaryRectangle,\n    /// Custom format (https://docs.rs/chrono/latest/chrono/format/strftime/index.html)\n    #[cfg_attr(feature = \"schemars\", schemars(title = \"Custom\"))]\n    Custom(String),\n}\n\nimpl TimeFormat {\n    pub fn toggle(&mut self) {\n        match self {\n            TimeFormat::TwelveHour => *self = TimeFormat::TwelveHourWithoutSeconds,\n            TimeFormat::TwelveHourWithoutSeconds => *self = TimeFormat::TwentyFourHour,\n            TimeFormat::TwentyFourHour => *self = TimeFormat::TwentyFourHourWithoutSeconds,\n            TimeFormat::TwentyFourHourWithoutSeconds => *self = TimeFormat::BinaryCircle,\n            TimeFormat::BinaryCircle => *self = TimeFormat::BinaryRectangle,\n            TimeFormat::BinaryRectangle => *self = TimeFormat::TwelveHour,\n            _ => {}\n        };\n    }\n\n    fn fmt_string(&self) -> String {\n        match self {\n            TimeFormat::TwelveHour => String::from(\"%l:%M:%S %p\"),\n            TimeFormat::TwelveHourWithoutSeconds => String::from(\"%l:%M %p\"),\n            TimeFormat::TwentyFourHour => String::from(\"%T\"),\n            TimeFormat::TwentyFourHourWithoutSeconds => String::from(\"%H:%M\"),\n            TimeFormat::BinaryCircle => String::from(\"c%T\"),\n            TimeFormat::BinaryRectangle => String::from(\"r%T\"),\n            TimeFormat::Custom(format) => format.to_string(),\n        }\n    }\n}\n\n#[derive(Clone, Debug)]\nstruct TimeOutput {\n    label: String,\n    icon: String,\n}\n\nimpl TimeOutput {\n    fn new() -> Self {\n        Self {\n            label: String::new(),\n            icon: String::new(),\n        }\n    }\n}\n\n#[derive(Clone, Debug)]\npub struct Time {\n    pub enable: bool,\n    pub format: TimeFormat,\n    label_prefix: LabelPrefix,\n    timezone: Option<String>,\n    changing_icon: bool,\n    data_refresh_interval_millis: u64,\n    last_state: TimeOutput,\n    last_updated: Instant,\n}\n\nimpl Time {\n    fn output(&mut self) -> TimeOutput {\n        let mut output = self.last_state.clone();\n        let now = Instant::now();\n\n        if now.duration_since(self.last_updated)\n            > Duration::from_millis(self.data_refresh_interval_millis)\n        {\n            let (formatted, current_time) = match &self.timezone {\n                Some(timezone) => match timezone.parse::<Tz>() {\n                    Ok(tz) => {\n                        let dt = Local::now().with_timezone(&tz);\n                        (\n                            dt.format(&self.format.fmt_string())\n                                .to_string()\n                                .trim()\n                                .to_string(),\n                            Some(dt.time()),\n                        )\n                    }\n                    Err(_) => (format!(\"Invalid timezone: {timezone:?}\"), None),\n                },\n                None => {\n                    let dt = Local::now();\n                    (\n                        dt.format(&self.format.fmt_string())\n                            .to_string()\n                            .trim()\n                            .to_string(),\n                        Some(dt.time()),\n                    )\n                }\n            };\n\n            if current_time.is_none() {\n                return TimeOutput {\n                    label: formatted,\n                    icon: egui_phosphor::regular::WARNING_CIRCLE.to_string(),\n                };\n            }\n\n            let current_range = match &self.changing_icon {\n                true => TIME_RANGES\n                    .iter()\n                    .rev()\n                    .find(|&(_, start)| current_time.unwrap() > *start)\n                    .cloned(),\n                false => None,\n            }\n            .unwrap_or((egui_phosphor::regular::CLOCK, NaiveTime::default()));\n\n            output = TimeOutput {\n                label: formatted,\n                icon: current_range.0.to_string(),\n            };\n\n            self.last_state.clone_from(&output);\n            self.last_updated = now;\n        }\n\n        output\n    }\n\n    fn paint_binary_circle(\n        &mut self,\n        size: f32,\n        number: u32,\n        max_power: usize,\n        ctx: &Context,\n        ui: &mut Ui,\n    ) {\n        let full_height = size;\n        let height = full_height / 4.0;\n        let width = height;\n        let offset = height / 2.0 - height / 8.0;\n\n        let (response, painter) =\n            ui.allocate_painter(Vec2::new(width, full_height + offset * 2.0), Sense::hover());\n        let color = ctx.style().visuals.text_color();\n\n        let c = response.rect.center();\n        let r = height / 2.0 - 0.5;\n\n        if number == 1 || number == 3 || number == 5 || number == 7 || number == 9 {\n            painter.circle_filled(c + Vec2::new(0.0, height * 1.50 + offset), r, color);\n        } else {\n            painter.circle_filled(c + Vec2::new(0.0, height * 1.50 + offset), r / 2.5, color);\n        }\n\n        if number == 2 || number == 3 || number == 6 || number == 7 {\n            painter.circle_filled(c + Vec2::new(0.0, height * 0.50 + offset), r, color);\n        } else {\n            painter.circle_filled(c + Vec2::new(0.0, height * 0.50 + offset), r / 2.5, color);\n        }\n\n        if number == 4 || number == 5 || number == 6 || number == 7 {\n            painter.circle_filled(c + Vec2::new(0.0, -height * 0.50 + offset), r, color);\n        } else if max_power > 2 {\n            painter.circle_filled(c + Vec2::new(0.0, -height * 0.50 + offset), r / 2.5, color);\n        }\n\n        if number == 8 || number == 9 {\n            painter.circle_filled(c + Vec2::new(0.0, -height * 1.50 + offset), r, color);\n        } else if max_power > 3 {\n            painter.circle_filled(c + Vec2::new(0.0, -height * 1.50 + offset), r / 2.5, color);\n        }\n    }\n\n    fn paint_binary_rect(\n        &mut self,\n        size: f32,\n        number: u32,\n        max_power: usize,\n        ctx: &Context,\n        ui: &mut Ui,\n    ) {\n        let full_height = size;\n        let height = full_height / 4.0;\n        let width = height * 1.5;\n        let offset = height / 2.0 - height / 8.0;\n\n        let (response, painter) =\n            ui.allocate_painter(Vec2::new(width, full_height + offset * 2.0), Sense::hover());\n        let color = ctx.style().visuals.text_color();\n        let stroke = Stroke::new(1.0, color);\n\n        let round_all = CornerRadius::same((response.rect.width() * 0.1) as u8);\n        let round_top = CornerRadius {\n            nw: round_all.nw,\n            ne: round_all.ne,\n            ..Default::default()\n        };\n        let round_none = CornerRadius::ZERO;\n        let round_bottom = CornerRadius {\n            sw: round_all.nw,\n            se: round_all.ne,\n            ..Default::default()\n        };\n\n        if max_power == 2 {\n            let mut rect = response\n                .rect\n                .shrink2(Vec2::new(stroke.width, stroke.width + offset));\n            rect.set_height(rect.height() - height * 2.0);\n            rect = rect.translate(Vec2::new(0.0, height * 2.0 + offset));\n            painter.rect_stroke(rect, round_all, stroke, StrokeKind::Outside);\n        } else if max_power == 3 {\n            let mut rect = response\n                .rect\n                .shrink2(Vec2::new(stroke.width, stroke.width + offset));\n            rect.set_height(rect.height() - height);\n            rect = rect.translate(Vec2::new(0.0, height + offset));\n            painter.rect_stroke(rect, round_all, stroke, StrokeKind::Outside);\n        } else {\n            let mut rect = response\n                .rect\n                .shrink2(Vec2::new(stroke.width, stroke.width + offset));\n            rect = rect.translate(Vec2::new(0.0, 0.0 + offset));\n            painter.rect_stroke(rect, round_all, stroke, StrokeKind::Outside);\n        }\n\n        let mut rect_bin = response.rect;\n        rect_bin.set_width(width);\n\n        if number == 1 || number == 5 || number == 9 {\n            rect_bin.set_height(height);\n            painter.rect_filled(\n                rect_bin.translate(Vec2::new(stroke.width, height * 3.0 + offset * 2.0)),\n                round_bottom,\n                color,\n            );\n        }\n        if number == 2 {\n            rect_bin.set_height(height);\n            painter.rect_filled(\n                rect_bin.translate(Vec2::new(stroke.width, height * 2.0 + offset * 2.0)),\n                if max_power == 2 {\n                    round_top\n                } else {\n                    round_none\n                },\n                color,\n            );\n        }\n        if number == 3 {\n            rect_bin.set_height(height * 2.0);\n            painter.rect_filled(\n                rect_bin.translate(Vec2::new(stroke.width, height * 2.0 + offset * 2.0)),\n                round_bottom,\n                color,\n            );\n        }\n        if number == 4 || number == 5 {\n            rect_bin.set_height(height);\n            painter.rect_filled(\n                rect_bin.translate(Vec2::new(stroke.width, height * 1.0 + offset * 2.0)),\n                if max_power == 3 {\n                    round_top\n                } else {\n                    round_none\n                },\n                color,\n            );\n        }\n        if number == 6 {\n            rect_bin.set_height(height * 2.0);\n            painter.rect_filled(\n                rect_bin.translate(Vec2::new(stroke.width, height * 1.0 + offset * 2.0)),\n                if max_power == 3 {\n                    round_top\n                } else {\n                    round_none\n                },\n                color,\n            );\n        }\n        if number == 7 {\n            rect_bin.set_height(height * 3.0);\n            painter.rect_filled(\n                rect_bin.translate(Vec2::new(stroke.width, height + offset * 2.0)),\n                if max_power == 3 {\n                    round_all\n                } else {\n                    round_bottom\n                },\n                color,\n            );\n        }\n        if number == 8 || number == 9 {\n            rect_bin.set_height(height);\n            painter.rect_filled(\n                rect_bin.translate(Vec2::new(stroke.width, 0.0 + offset * 2.0)),\n                round_top,\n                color,\n            );\n        }\n    }\n}\n\nimpl BarWidget for Time {\n    fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {\n        if self.enable {\n            let mut output = self.output();\n            if !output.label.is_empty() {\n                let use_binary_circle = output.label.starts_with('c');\n                let use_binary_rectangle = output.label.starts_with('r');\n\n                let mut layout_job = LayoutJob::simple(\n                    match self.label_prefix {\n                        LabelPrefix::Icon | LabelPrefix::IconAndText => output.icon,\n                        LabelPrefix::None | LabelPrefix::Text => String::new(),\n                    },\n                    config.icon_font_id.clone(),\n                    ctx.style().visuals.selection.stroke.color,\n                    100.0,\n                );\n\n                if let LabelPrefix::Text | LabelPrefix::IconAndText = self.label_prefix {\n                    output.label.insert_str(0, \"TIME: \");\n                }\n\n                if !use_binary_circle && !use_binary_rectangle {\n                    layout_job.append(\n                        &output.label,\n                        10.0,\n                        TextFormat {\n                            font_id: config.text_font_id.clone(),\n                            color: ctx.style().visuals.text_color(),\n                            valign: Align::Center,\n                            ..Default::default()\n                        },\n                    );\n                }\n\n                let font_id = config.icon_font_id.clone();\n                let is_reversed = matches!(config.alignment, Some(Alignment::Right));\n\n                config.apply_on_widget(false, ui, |ui| {\n                    if SelectableFrame::new(false)\n                        .show(ui, |ui| {\n                            if !is_reversed {\n                                ui.add(Label::new(layout_job.clone()).selectable(false));\n                            }\n\n                            if use_binary_circle || use_binary_rectangle {\n                                let ordered_output = if is_reversed {\n                                    output.label.chars().rev().collect()\n                                } else {\n                                    output.label\n                                };\n\n                                for (section_index, section) in\n                                    ordered_output.split(':').enumerate()\n                                {\n                                    ui.scope(|ui| {\n                                        ui.spacing_mut().item_spacing = Vec2::splat(2.0);\n                                        for (number_index, number_char) in\n                                            section.chars().enumerate()\n                                        {\n                                            if let Some(number) = number_char.to_digit(10) {\n                                                // the hour is the second char in the first section (in reverse, it's in the last section)\n                                                let max_power = match (\n                                                    is_reversed,\n                                                    section_index,\n                                                    number_index,\n                                                ) {\n                                                    (true, 2, 1) | (false, 0, 1) => 2,\n                                                    (true, _, 1) | (false, _, 0) => 3,\n                                                    _ => 4,\n                                                };\n\n                                                if use_binary_circle {\n                                                    self.paint_binary_circle(\n                                                        font_id.size,\n                                                        number,\n                                                        max_power,\n                                                        ctx,\n                                                        ui,\n                                                    );\n                                                } else if use_binary_rectangle {\n                                                    self.paint_binary_rect(\n                                                        font_id.size,\n                                                        number,\n                                                        max_power,\n                                                        ctx,\n                                                        ui,\n                                                    );\n                                                }\n                                            }\n                                        }\n                                    });\n                                }\n                            }\n\n                            if is_reversed {\n                                ui.add(Label::new(layout_job.clone()).selectable(false));\n                            }\n                        })\n                        .clicked()\n                    {\n                        self.format.toggle()\n                    }\n                });\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "komorebi-bar/src/widgets/update.rs",
    "content": "use crate::config::LabelPrefix;\nuse crate::render::RenderConfig;\nuse crate::selected_frame::SelectableFrame;\nuse crate::widgets::widget::BarWidget;\nuse eframe::egui::Align;\nuse eframe::egui::Context;\nuse eframe::egui::Label;\nuse eframe::egui::TextFormat;\nuse eframe::egui::Ui;\nuse eframe::egui::text::LayoutJob;\nuse serde::Deserialize;\nuse serde::Serialize;\nuse std::process::Command;\nuse std::time::Duration;\nuse std::time::Instant;\n\n#[derive(Copy, Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Update widget configuration\npub struct UpdateConfig {\n    /// Enable the Update widget\n    pub enable: bool,\n    /// Data refresh interval in hours\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = 12)))]\n    pub data_refresh_interval: Option<u64>,\n    /// Display label prefix\n    pub label_prefix: Option<LabelPrefix>,\n}\n\nimpl From<UpdateConfig> for Update {\n    fn from(value: UpdateConfig) -> Self {\n        let data_refresh_interval = value.data_refresh_interval.unwrap_or(12);\n\n        let mut latest_version = String::new();\n\n        let client = reqwest::blocking::Client::new();\n        if let Ok(response) = client\n            .get(\"https://api.github.com/repos/LGUG2Z/komorebi/releases/latest\")\n            .header(\"User-Agent\", \"komorebi-bar-version-checker\")\n            .send()\n        {\n            #[derive(Deserialize)]\n            struct Release {\n                tag_name: String,\n            }\n\n            if let Ok(release) =\n                serde_json::from_str::<Release>(&response.text().unwrap_or_default())\n            {\n                let trimmed = release.tag_name.trim_start_matches(\"v\");\n                latest_version = trimmed.to_string();\n            }\n        }\n\n        Self {\n            enable: value.enable,\n            data_refresh_interval,\n            installed_version: env!(\"CARGO_PKG_VERSION\").to_string(),\n            latest_version,\n            label_prefix: value.label_prefix.unwrap_or(LabelPrefix::IconAndText),\n            last_updated: Instant::now()\n                .checked_sub(Duration::from_secs(data_refresh_interval))\n                .unwrap(),\n        }\n    }\n}\n\npub struct Update {\n    pub enable: bool,\n    data_refresh_interval: u64,\n    installed_version: String,\n    latest_version: String,\n    label_prefix: LabelPrefix,\n    last_updated: Instant,\n}\n\nimpl Update {\n    fn output(&mut self) -> String {\n        let now = Instant::now();\n        if now.duration_since(self.last_updated)\n            > Duration::from_secs((self.data_refresh_interval * 60) * 60)\n        {\n            let client = reqwest::blocking::Client::new();\n            if let Ok(response) = client\n                .get(\"https://api.github.com/repos/LGUG2Z/komorebi/releases/latest\")\n                .header(\"User-Agent\", \"komorebi-bar-version-checker\")\n                .send()\n            {\n                #[derive(Deserialize)]\n                struct Release {\n                    tag_name: String,\n                }\n\n                if let Ok(release) =\n                    serde_json::from_str::<Release>(&response.text().unwrap_or_default())\n                {\n                    let trimmed = release.tag_name.trim_start_matches(\"v\");\n                    self.latest_version = trimmed.to_string();\n                }\n            }\n\n            self.last_updated = now;\n        }\n\n        if self.latest_version > self.installed_version {\n            format!(\"Update available! v{}\", self.latest_version)\n        } else {\n            String::new()\n        }\n    }\n}\n\nimpl BarWidget for Update {\n    fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {\n        if self.enable {\n            let output = self.output();\n            if !output.is_empty() {\n                let mut layout_job = LayoutJob::simple(\n                    match self.label_prefix {\n                        LabelPrefix::Icon | LabelPrefix::IconAndText => {\n                            egui_phosphor::regular::ROCKET_LAUNCH.to_string()\n                        }\n                        LabelPrefix::None | LabelPrefix::Text => String::new(),\n                    },\n                    config.icon_font_id.clone(),\n                    ctx.style().visuals.selection.stroke.color,\n                    100.0,\n                );\n\n                layout_job.append(\n                    &output,\n                    10.0,\n                    TextFormat {\n                        font_id: config.text_font_id.clone(),\n                        color: ctx.style().visuals.text_color(),\n                        valign: Align::Center,\n                        ..Default::default()\n                    },\n                );\n\n                config.apply_on_widget(false, ui, |ui| {\n                    if SelectableFrame::new(false)\n                        .show(ui, |ui| ui.add(Label::new(layout_job).selectable(false)))\n                        .clicked()\n                        && let Err(error) = Command::new(\"explorer.exe\")\n                            .args([format!(\n                                \"https://github.com/LGUG2Z/komorebi/releases/v{}\",\n                                self.latest_version\n                            )])\n                            .spawn()\n                    {\n                        eprintln!(\"{error}\")\n                    }\n                });\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "komorebi-bar/src/widgets/widget.rs",
    "content": "use crate::render::RenderConfig;\nuse crate::widgets::applications::Applications;\nuse crate::widgets::applications::ApplicationsConfig;\nuse crate::widgets::battery::Battery;\nuse crate::widgets::battery::BatteryConfig;\nuse crate::widgets::cpu::Cpu;\nuse crate::widgets::cpu::CpuConfig;\nuse crate::widgets::date::Date;\nuse crate::widgets::date::DateConfig;\nuse crate::widgets::keyboard::Keyboard;\nuse crate::widgets::keyboard::KeyboardConfig;\nuse crate::widgets::komorebi::Komorebi;\nuse crate::widgets::komorebi::KomorebiConfig;\nuse crate::widgets::media::Media;\nuse crate::widgets::media::MediaConfig;\nuse crate::widgets::memory::Memory;\nuse crate::widgets::memory::MemoryConfig;\nuse crate::widgets::network::Network;\nuse crate::widgets::network::NetworkConfig;\nuse crate::widgets::storage::Storage;\nuse crate::widgets::storage::StorageConfig;\nuse crate::widgets::time::Time;\nuse crate::widgets::time::TimeConfig;\nuse crate::widgets::update::Update;\nuse crate::widgets::update::UpdateConfig;\nuse eframe::egui::Context;\nuse eframe::egui::Ui;\nuse serde::Deserialize;\nuse serde::Serialize;\n\npub trait BarWidget {\n    fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig);\n}\n\n#[derive(Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Widget configuration\npub enum WidgetConfig {\n    /// Applications widget configuration\n    #[cfg_attr(feature = \"schemars\", schemars(title = \"Applications\"))]\n    Applications(ApplicationsConfig),\n    /// Battery widget configuration\n    #[cfg_attr(feature = \"schemars\", schemars(title = \"Battery\"))]\n    Battery(BatteryConfig),\n    /// CPU widget configuration\n    #[cfg_attr(feature = \"schemars\", schemars(title = \"Cpu\"))]\n    Cpu(CpuConfig),\n    /// Date widget configuration\n    #[cfg_attr(feature = \"schemars\", schemars(title = \"Date\"))]\n    Date(DateConfig),\n    /// Keyboard widget configuration\n    #[cfg_attr(feature = \"schemars\", schemars(title = \"Keyboard\"))]\n    Keyboard(KeyboardConfig),\n    /// Komorebi widget configuration\n    #[cfg_attr(feature = \"schemars\", schemars(title = \"Komorebi\"))]\n    Komorebi(KomorebiConfig),\n    /// Media widget configuration\n    #[cfg_attr(feature = \"schemars\", schemars(title = \"Media\"))]\n    Media(MediaConfig),\n    /// Memory widget configuration\n    #[cfg_attr(feature = \"schemars\", schemars(title = \"Memory\"))]\n    Memory(MemoryConfig),\n    /// Network widget configuration\n    #[cfg_attr(feature = \"schemars\", schemars(title = \"Network\"))]\n    Network(NetworkConfig),\n    /// Storage widget configuration\n    #[cfg_attr(feature = \"schemars\", schemars(title = \"Storage\"))]\n    Storage(StorageConfig),\n    /// Time widget configuration\n    #[cfg_attr(feature = \"schemars\", schemars(title = \"Time\"))]\n    Time(TimeConfig),\n    /// Update widget configuration\n    #[cfg_attr(feature = \"schemars\", schemars(title = \"Update\"))]\n    Update(UpdateConfig),\n}\n\nimpl WidgetConfig {\n    pub fn as_boxed_bar_widget(&self) -> Box<dyn BarWidget> {\n        match self {\n            WidgetConfig::Applications(config) => Box::new(Applications::from(config)),\n            WidgetConfig::Battery(config) => Box::new(Battery::from(*config)),\n            WidgetConfig::Cpu(config) => Box::new(Cpu::from(*config)),\n            WidgetConfig::Date(config) => Box::new(Date::from(config.clone())),\n            WidgetConfig::Keyboard(config) => Box::new(Keyboard::from(*config)),\n            WidgetConfig::Komorebi(config) => Box::new(Komorebi::from(config)),\n            WidgetConfig::Media(config) => Box::new(Media::from(*config)),\n            WidgetConfig::Memory(config) => Box::new(Memory::from(*config)),\n            WidgetConfig::Network(config) => Box::new(Network::from(*config)),\n            WidgetConfig::Storage(config) => Box::new(Storage::from(*config)),\n            WidgetConfig::Time(config) => Box::new(Time::from(config.clone())),\n            WidgetConfig::Update(config) => Box::new(Update::from(*config)),\n        }\n    }\n\n    pub fn enabled(&self) -> bool {\n        match self {\n            WidgetConfig::Applications(config) => config.enable,\n            WidgetConfig::Battery(config) => config.enable,\n            WidgetConfig::Cpu(config) => config.enable,\n            WidgetConfig::Date(config) => config.enable,\n            WidgetConfig::Keyboard(config) => config.enable,\n            WidgetConfig::Komorebi(config) => {\n                config.workspaces.as_ref().is_some_and(|w| w.enable)\n                    || config.layout.as_ref().is_some_and(|w| w.enable)\n                    || config.focused_container.as_ref().is_some_and(|w| w.enable)\n                    || config\n                        .configuration_switcher\n                        .as_ref()\n                        .is_some_and(|w| w.enable)\n            }\n            WidgetConfig::Media(config) => config.enable,\n            WidgetConfig::Memory(config) => config.enable,\n            WidgetConfig::Network(config) => config.enable,\n            WidgetConfig::Storage(config) => config.enable,\n            WidgetConfig::Time(config) => config.enable,\n            WidgetConfig::Update(config) => config.enable,\n        }\n    }\n}\n"
  },
  {
    "path": "komorebi-client/Cargo.toml",
    "content": "[package]\nname = \"komorebi-client\"\nversion = \"0.1.41\"\nedition = \"2024\"\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\nkomorebi = { path = \"../komorebi\", default-features = false }\n\nuds_windows = { workspace = true }\nserde_json = { workspace = true }\n\n[features]\ndefault = [\"schemars\"]\nschemars = [\"komorebi/default\"]\n"
  },
  {
    "path": "komorebi-client/src/lib.rs",
    "content": "#![warn(clippy::all)]\n#![allow(clippy::missing_errors_doc)]\n\npub use komorebi::AnimationsConfig;\npub use komorebi::AppSpecificConfigurationPath;\npub use komorebi::AspectRatio;\npub use komorebi::BorderColours;\npub use komorebi::Colour;\npub use komorebi::CrossBoundaryBehaviour;\npub use komorebi::GridLayoutOptions;\npub use komorebi::KomorebiTheme;\npub use komorebi::LayoutOptions;\npub use komorebi::MonitorConfig;\npub use komorebi::Notification;\npub use komorebi::NotificationEvent;\npub use komorebi::Placement;\npub use komorebi::PredefinedAspectRatio;\npub use komorebi::Rgb;\npub use komorebi::RuleDebug;\npub use komorebi::ScrollingLayoutOptions;\npub use komorebi::StackbarConfig;\npub use komorebi::StaticConfig;\npub use komorebi::SubscribeOptions;\npub use komorebi::TabsConfig;\npub use komorebi::ThemeOptions;\npub use komorebi::VirtualDesktopNotification;\npub use komorebi::Wallpaper;\npub use komorebi::WindowContainerBehaviour;\npub use komorebi::WindowHandlingBehaviour;\npub use komorebi::WindowsApi;\npub use komorebi::WorkspaceConfig;\npub use komorebi::animation::PerAnimationPrefixConfig;\npub use komorebi::animation::prefix::AnimationPrefix;\npub use komorebi::asc::ApplicationSpecificConfiguration;\npub use komorebi::border_manager::BorderInfo;\npub use komorebi::config_generation::ApplicationConfiguration;\npub use komorebi::config_generation::IdWithIdentifier;\npub use komorebi::config_generation::IdWithIdentifierAndComment;\npub use komorebi::config_generation::MatchingRule;\npub use komorebi::config_generation::MatchingStrategy;\npub use komorebi::container::Container;\npub use komorebi::core::AnimationStyle;\npub use komorebi::core::ApplicationIdentifier;\npub use komorebi::core::Arrangement;\npub use komorebi::core::Axis;\npub use komorebi::core::BorderImplementation;\npub use komorebi::core::BorderStyle;\npub use komorebi::core::Column;\npub use komorebi::core::ColumnSplit;\npub use komorebi::core::ColumnSplitWithCapacity;\npub use komorebi::core::ColumnWidth;\npub use komorebi::core::CustomLayout;\npub use komorebi::core::CycleDirection;\npub use komorebi::core::DefaultLayout;\npub use komorebi::core::Direction;\npub use komorebi::core::FloatingLayerBehaviour;\npub use komorebi::core::FocusFollowsMouseImplementation;\npub use komorebi::core::HidingBehaviour;\npub use komorebi::core::Layout;\npub use komorebi::core::MoveBehaviour;\npub use komorebi::core::OperationBehaviour;\npub use komorebi::core::OperationDirection;\npub use komorebi::core::PathExt;\npub use komorebi::core::Rect;\npub use komorebi::core::Sizing;\npub use komorebi::core::SocketMessage;\npub use komorebi::core::StackbarLabel;\npub use komorebi::core::StackbarMode;\npub use komorebi::core::StateQuery;\npub use komorebi::core::WindowKind;\npub use komorebi::core::config_generation::ApplicationConfigurationGenerator;\npub use komorebi::core::replace_env_in_path;\npub use komorebi::monitor::Monitor;\npub use komorebi::monitor_reconciliator::MonitorNotification;\npub use komorebi::ring::Ring;\npub use komorebi::splash;\npub use komorebi::state::GlobalState;\npub use komorebi::state::State;\npub use komorebi::win32_display_data;\npub use komorebi::window::Window;\npub use komorebi::window_manager_event::WindowManagerEvent;\npub use komorebi::workspace::Workspace;\npub use komorebi::workspace::WorkspaceGlobals;\npub use komorebi::workspace::WorkspaceLayer;\n\nuse komorebi::DATA_DIR;\n\nuse std::borrow::Borrow;\nuse std::io::BufReader;\nuse std::io::Read;\nuse std::io::Write;\nuse std::net::Shutdown;\nuse std::time::Duration;\npub use uds_windows::UnixListener;\nuse uds_windows::UnixStream;\n\nconst KOMOREBI: &str = \"komorebi.sock\";\n\npub fn send_message(message: &SocketMessage) -> std::io::Result<()> {\n    let socket = DATA_DIR.join(KOMOREBI);\n    let mut stream = UnixStream::connect(socket)?;\n    stream.set_write_timeout(Some(Duration::from_secs(1)))?;\n    stream.write_all(serde_json::to_string(message)?.as_bytes())\n}\n\npub fn send_batch<Q>(messages: impl IntoIterator<Item = Q>) -> std::io::Result<()>\nwhere\n    Q: Borrow<SocketMessage>,\n{\n    let socket = DATA_DIR.join(KOMOREBI);\n    let mut stream = UnixStream::connect(socket)?;\n    stream.set_write_timeout(Some(Duration::from_secs(1)))?;\n    let msgs = messages.into_iter().fold(String::new(), |mut s, m| {\n        if let Ok(m_str) = serde_json::to_string(m.borrow()) {\n            s.push_str(&m_str);\n            s.push('\\n');\n        }\n        s\n    });\n    stream.write_all(msgs.as_bytes())\n}\n\npub fn send_query(message: &SocketMessage) -> std::io::Result<String> {\n    let socket = DATA_DIR.join(KOMOREBI);\n\n    let mut stream = UnixStream::connect(socket)?;\n    stream.set_read_timeout(Some(Duration::from_secs(1)))?;\n    stream.set_write_timeout(Some(Duration::from_secs(1)))?;\n    stream.write_all(serde_json::to_string(message)?.as_bytes())?;\n    stream.shutdown(Shutdown::Write)?;\n\n    let mut reader = BufReader::new(stream);\n    let mut response = String::new();\n    reader.read_to_string(&mut response)?;\n\n    Ok(response)\n}\n\npub fn subscribe(name: &str) -> std::io::Result<UnixListener> {\n    let socket = DATA_DIR.join(name);\n\n    match std::fs::remove_file(&socket) {\n        Ok(()) => {}\n        Err(error) => match error.kind() {\n            std::io::ErrorKind::NotFound => {}\n            _ => {\n                return Err(error);\n            }\n        },\n    };\n\n    let listener = UnixListener::bind(&socket)?;\n\n    send_message(&SocketMessage::AddSubscriberSocket(name.to_string()))?;\n\n    Ok(listener)\n}\n\npub fn subscribe_with_options(\n    name: &str,\n    options: SubscribeOptions,\n) -> std::io::Result<UnixListener> {\n    let socket = DATA_DIR.join(name);\n\n    match std::fs::remove_file(&socket) {\n        Ok(()) => {}\n        Err(error) => match error.kind() {\n            std::io::ErrorKind::NotFound => {}\n            _ => {\n                return Err(error);\n            }\n        },\n    };\n\n    let listener = UnixListener::bind(&socket)?;\n\n    send_message(&SocketMessage::AddSubscriberSocketWithOptions(\n        name.to_string(),\n        options,\n    ))?;\n\n    Ok(listener)\n}\n"
  },
  {
    "path": "komorebi-gui/Cargo.toml",
    "content": "[package]\nname = \"komorebi-gui\"\nversion = \"0.1.41\"\nedition = \"2024\"\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\nkomorebi-client = { path = \"../komorebi-client\", default-features = false }\n\neframe = { workspace = true }\negui_extras = { workspace = true }\nrandom_word = { version = \"0.5\", features = [\"en\"] }\nserde_json = { workspace = true }\nwindows-core = { workspace = true }\nwindows = { workspace = true }\n"
  },
  {
    "path": "komorebi-gui/src/main.rs",
    "content": "#![warn(clippy::all)]\n\nuse eframe::egui;\nuse eframe::egui::Color32;\nuse eframe::egui::ViewportBuilder;\nuse eframe::egui::color_picker::Alpha;\nuse komorebi_client::BorderStyle;\nuse komorebi_client::Colour;\nuse komorebi_client::DefaultLayout;\nuse komorebi_client::GlobalState;\nuse komorebi_client::Layout;\nuse komorebi_client::Rect;\nuse komorebi_client::Rgb;\nuse komorebi_client::RuleDebug;\nuse komorebi_client::SocketMessage;\nuse komorebi_client::StackbarLabel;\nuse komorebi_client::StackbarMode;\nuse komorebi_client::State;\nuse komorebi_client::Window;\nuse komorebi_client::WindowKind;\nuse std::collections::HashMap;\nuse std::time::Duration;\nuse windows::Win32::UI::WindowsAndMessaging::EnumWindows;\n\nfn main() {\n    let native_options = eframe::NativeOptions {\n        viewport: ViewportBuilder::default()\n            .with_always_on_top()\n            .with_inner_size([320.0, 500.0]),\n        ..Default::default()\n    };\n\n    let _ = eframe::run_native(\n        \"komorebi-gui\",\n        native_options,\n        Box::new(|cc| Ok(Box::new(KomorebiGui::new(cc)))),\n    );\n}\n\nstruct BorderColours {\n    single: Color32,\n    stack: Color32,\n    monocle: Color32,\n    floating: Color32,\n    unfocused: Color32,\n    unfocused_locked: Color32,\n}\n\nstruct BorderConfig {\n    border_enabled: bool,\n    border_colours: BorderColours,\n    border_style: BorderStyle,\n    border_offset: i32,\n    border_width: i32,\n}\n\nstruct StackbarConfig {\n    mode: StackbarMode,\n    label: StackbarLabel,\n    height: i32,\n    width: i32,\n    focused_text_colour: Color32,\n    unfocused_text_colour: Color32,\n    background_colour: Color32,\n}\n\nstruct MonitorConfig {\n    size: Rect,\n    work_area_offset: Rect,\n    workspaces: Vec<WorkspaceConfig>,\n}\n\nimpl From<&komorebi_client::Monitor> for MonitorConfig {\n    fn from(value: &komorebi_client::Monitor) -> Self {\n        let mut workspaces = vec![];\n        for ws in value.workspaces() {\n            workspaces.push(WorkspaceConfig::from(ws));\n        }\n\n        Self {\n            size: value.size,\n            work_area_offset: value.work_area_offset.unwrap_or_default(),\n            workspaces,\n        }\n    }\n}\n\nstruct WorkspaceConfig {\n    name: String,\n    tile: bool,\n    layout: DefaultLayout,\n    container_padding: i32,\n    workspace_padding: i32,\n}\n\nimpl From<&komorebi_client::Workspace> for WorkspaceConfig {\n    fn from(value: &komorebi_client::Workspace) -> Self {\n        let layout = match value.layout {\n            Layout::Default(layout) => layout,\n            Layout::Custom(_) => DefaultLayout::BSP,\n        };\n\n        let name = value\n            .name\n            .to_owned()\n            .unwrap_or_else(|| random_word::get(random_word::Lang::En).to_string());\n\n        Self {\n            layout,\n            name,\n            tile: value.tile,\n            workspace_padding: value.workspace_padding.unwrap_or(20),\n            container_padding: value.container_padding.unwrap_or(20),\n        }\n    }\n}\n\nstruct KomorebiGui {\n    border_config: BorderConfig,\n    stackbar_config: StackbarConfig,\n    mouse_follows_focus: bool,\n    monitors: Vec<MonitorConfig>,\n    workspace_names: HashMap<usize, Vec<String>>,\n    debug_hwnd: isize,\n    debug_windows: Vec<Window>,\n    debug_rule: Option<RuleDebug>,\n}\n\nfn colour32(colour: Option<Colour>) -> Color32 {\n    match colour {\n        Some(Colour::Rgb(rgb)) => Color32::from_rgb(rgb.r as u8, rgb.g as u8, rgb.b as u8),\n        Some(Colour::Hex(hex)) => {\n            let rgb = Rgb::from(hex);\n            Color32::from_rgb(rgb.r as u8, rgb.g as u8, rgb.b as u8)\n        }\n        None => Color32::from_rgb(0, 0, 0),\n    }\n}\n\nimpl KomorebiGui {\n    fn new(_cc: &eframe::CreationContext<'_>) -> Self {\n        // Customize egui here with cc.egui_ctx.set_fonts and cc.egui_ctx.set_visuals.\n        // Restore app state using cc.storage (requires the \"persistence\" feature).\n        // Use the cc.gl (a glow::Context) to create graphics shaders and buffers that you can use\n        // for e.g. egui::PaintCallback.\n        let global_state: GlobalState = serde_json::from_str(\n            &komorebi_client::send_query(&SocketMessage::GlobalState).unwrap(),\n        )\n        .unwrap();\n\n        let state: State =\n            serde_json::from_str(&komorebi_client::send_query(&SocketMessage::State).unwrap())\n                .unwrap();\n\n        let border_colours = BorderColours {\n            single: colour32(global_state.border_colours.single),\n            stack: colour32(global_state.border_colours.stack),\n            monocle: colour32(global_state.border_colours.monocle),\n            floating: colour32(global_state.border_colours.floating),\n            unfocused: colour32(global_state.border_colours.unfocused),\n            unfocused_locked: colour32(global_state.border_colours.unfocused_locked),\n        };\n\n        let border_config = BorderConfig {\n            border_enabled: global_state.border_enabled,\n            border_colours,\n            border_style: global_state.border_style,\n            border_offset: global_state.border_offset,\n            border_width: global_state.border_width,\n        };\n\n        let mut monitors = vec![];\n        for m in state.monitors.elements() {\n            monitors.push(MonitorConfig::from(m));\n        }\n\n        let mut workspace_names = HashMap::new();\n\n        for (monitor_idx, m) in monitors.iter().enumerate() {\n            for ws in &m.workspaces {\n                let names = workspace_names.entry(monitor_idx).or_insert_with(Vec::new);\n                names.push(ws.name.clone());\n            }\n        }\n\n        let stackbar_config = StackbarConfig {\n            mode: global_state.stackbar_mode,\n            height: global_state.stackbar_height,\n            width: global_state.stackbar_tab_width,\n            label: global_state.stackbar_label,\n            focused_text_colour: colour32(Some(global_state.stackbar_focused_text_colour)),\n            unfocused_text_colour: colour32(Some(global_state.stackbar_unfocused_text_colour)),\n            background_colour: colour32(Some(global_state.stackbar_tab_background_colour)),\n        };\n\n        let mut debug_windows = vec![];\n\n        unsafe {\n            EnumWindows(\n                Some(enum_window),\n                windows::Win32::Foundation::LPARAM(&mut debug_windows as *mut Vec<Window> as isize),\n            )\n            .unwrap();\n        };\n\n        Self {\n            border_config,\n            mouse_follows_focus: state.mouse_follows_focus,\n            monitors,\n            workspace_names,\n            debug_hwnd: 0,\n            debug_windows,\n            stackbar_config,\n            debug_rule: None,\n        }\n    }\n}\n\nextern \"system\" fn enum_window(\n    hwnd: windows::Win32::Foundation::HWND,\n    lparam: windows::Win32::Foundation::LPARAM,\n) -> windows_core::BOOL {\n    let windows = unsafe { &mut *(lparam.0 as *mut Vec<Window>) };\n    let window = Window::from(hwnd.0 as isize);\n\n    if window.is_window()\n        && !window.is_miminized()\n        && window.is_visible()\n        && window.title().is_ok()\n        && window.exe().is_ok()\n    {\n        windows.push(window);\n    }\n\n    true.into()\n}\n\nfn json_view_ui(ui: &mut egui::Ui, code: &str) {\n    let language = \"json\";\n    let theme =\n        egui_extras::syntax_highlighting::CodeTheme::from_memory(ui.ctx(), &ui.ctx().style());\n    egui_extras::syntax_highlighting::code_view_ui(ui, &theme, code, language);\n}\n\nimpl eframe::App for KomorebiGui {\n    fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {\n        egui::CentralPanel::default().show(ctx, |ui| {\n            ctx.set_pixels_per_point(2.0);\n            egui::ScrollArea::vertical().show(ui, |ui| {\n                ui.set_width(ctx.content_rect().width());\n                ui.collapsing(\"Debugging\", |ui| {\n                    ui.collapsing(\"Window Rules\", |ui| {\n                        let window = Window::from(self.debug_hwnd);\n\n                        let label = if let (Ok(title), Ok(exe)) = (window.title(), window.exe()) {\n                            format!(\"{title} ({exe})\")\n                        } else {\n                            String::from(\"Select a Window\")\n                        };\n\n                        if ui.button(\"Refresh Windows\").clicked() {\n                            let mut debug_windows = vec![];\n\n                            unsafe {\n                                EnumWindows(\n                                    Some(enum_window),\n                                    windows::Win32::Foundation::LPARAM(\n                                        &mut debug_windows as *mut Vec<Window> as isize,\n                                    ),\n                                )\n                                .unwrap();\n                            };\n\n                            self.debug_windows = debug_windows;\n                        }\n\n                        egui::ComboBox::from_label(\"Select a Window\")\n                            .selected_text(label)\n                            .show_ui(ui, |ui| {\n                                for w in &self.debug_windows {\n                                    if ui\n                                        .selectable_value(\n                                            &mut self.debug_hwnd,\n                                            w.hwnd,\n                                            format!(\n                                                \"{} ({})\",\n                                                w.title().unwrap(),\n                                                w.exe().unwrap()\n                                            ),\n                                        )\n                                        .changed()\n                                    {\n                                        let debug_rule: RuleDebug = serde_json::from_str(\n                                            &komorebi_client::send_query(\n                                                &SocketMessage::DebugWindow(self.debug_hwnd),\n                                            )\n                                            .unwrap(),\n                                        )\n                                        .unwrap();\n\n                                        self.debug_rule = Some(debug_rule)\n                                    }\n                                }\n                            });\n\n                        if let Some(debug_rule) = &self.debug_rule {\n                            json_view_ui(ui, &serde_json::to_string_pretty(debug_rule).unwrap())\n                        }\n                    });\n                });\n\n                ui.collapsing(\"Mouse\", |ui| {\n                    if ui\n                        .toggle_value(&mut self.mouse_follows_focus, \"Mouse Follows Focus\")\n                        .changed()\n                    {\n                        komorebi_client::send_message(&SocketMessage::MouseFollowsFocus(\n                            self.mouse_follows_focus,\n                        ))\n                        .unwrap();\n                    }\n                });\n\n                ui.collapsing(\"Border\", |ui| {\n                    if ui\n                        .toggle_value(&mut self.border_config.border_enabled, \"Border\")\n                        .changed()\n                    {\n                        komorebi_client::send_message(&SocketMessage::Border(\n                            self.border_config.border_enabled,\n                        ))\n                        .unwrap();\n                    }\n\n                    ui.collapsing(\"Colours\", |ui| {\n                        ui.collapsing(\"Single\", |ui| {\n                            if egui::color_picker::color_picker_color32(\n                                ui,\n                                &mut self.border_config.border_colours.single,\n                                Alpha::Opaque,\n                            ) {\n                                komorebi_client::send_message(&SocketMessage::BorderColour(\n                                    WindowKind::Single,\n                                    self.border_config.border_colours.single.r() as u32,\n                                    self.border_config.border_colours.single.g() as u32,\n                                    self.border_config.border_colours.single.b() as u32,\n                                ))\n                                .unwrap();\n                            }\n                        });\n\n                        ui.collapsing(\"Stack\", |ui| {\n                            if egui::color_picker::color_picker_color32(\n                                ui,\n                                &mut self.border_config.border_colours.stack,\n                                Alpha::Opaque,\n                            ) {\n                                komorebi_client::send_message(&SocketMessage::BorderColour(\n                                    WindowKind::Stack,\n                                    self.border_config.border_colours.stack.r() as u32,\n                                    self.border_config.border_colours.stack.g() as u32,\n                                    self.border_config.border_colours.stack.b() as u32,\n                                ))\n                                .unwrap();\n                            }\n                        });\n\n                        ui.collapsing(\"Monocle\", |ui| {\n                            if egui::color_picker::color_picker_color32(\n                                ui,\n                                &mut self.border_config.border_colours.monocle,\n                                Alpha::Opaque,\n                            ) {\n                                komorebi_client::send_message(&SocketMessage::BorderColour(\n                                    WindowKind::Monocle,\n                                    self.border_config.border_colours.monocle.r() as u32,\n                                    self.border_config.border_colours.monocle.g() as u32,\n                                    self.border_config.border_colours.monocle.b() as u32,\n                                ))\n                                .unwrap();\n                            }\n                        });\n\n                        ui.collapsing(\"Floating\", |ui| {\n                            if egui::color_picker::color_picker_color32(\n                                ui,\n                                &mut self.border_config.border_colours.floating,\n                                Alpha::Opaque,\n                            ) {\n                                komorebi_client::send_message(&SocketMessage::BorderColour(\n                                    WindowKind::Floating,\n                                    self.border_config.border_colours.floating.r() as u32,\n                                    self.border_config.border_colours.floating.g() as u32,\n                                    self.border_config.border_colours.floating.b() as u32,\n                                ))\n                                .unwrap();\n                            }\n                        });\n\n                        ui.collapsing(\"Unfocused\", |ui| {\n                            if egui::color_picker::color_picker_color32(\n                                ui,\n                                &mut self.border_config.border_colours.unfocused,\n                                Alpha::Opaque,\n                            ) {\n                                komorebi_client::send_message(&SocketMessage::BorderColour(\n                                    WindowKind::Unfocused,\n                                    self.border_config.border_colours.unfocused.r() as u32,\n                                    self.border_config.border_colours.unfocused.g() as u32,\n                                    self.border_config.border_colours.unfocused.b() as u32,\n                                ))\n                                .unwrap();\n                            }\n                        });\n\n                        ui.collapsing(\"Unfocused Locked\", |ui| {\n                            if egui::color_picker::color_picker_color32(\n                                ui,\n                                &mut self.border_config.border_colours.unfocused_locked,\n                                Alpha::Opaque,\n                            ) {\n                                komorebi_client::send_message(&SocketMessage::BorderColour(\n                                    WindowKind::UnfocusedLocked,\n                                    self.border_config.border_colours.unfocused_locked.r() as u32,\n                                    self.border_config.border_colours.unfocused_locked.g() as u32,\n                                    self.border_config.border_colours.unfocused_locked.b() as u32,\n                                ))\n                                .unwrap();\n                            }\n                        })\n                    });\n\n                    ui.collapsing(\"Style\", |ui| {\n                        for option in [\n                            BorderStyle::System,\n                            BorderStyle::Rounded,\n                            BorderStyle::Square,\n                        ] {\n                            if ui\n                                .add(egui::Button::selectable(\n                                    self.border_config.border_style == option,\n                                    option.to_string(),\n                                ))\n                                .clicked()\n                            {\n                                self.border_config.border_style = option;\n                                komorebi_client::send_message(&SocketMessage::BorderStyle(\n                                    self.border_config.border_style,\n                                ))\n                                .unwrap();\n\n                                std::thread::sleep(Duration::from_secs(1));\n\n                                komorebi_client::send_message(&SocketMessage::Retile).unwrap();\n                            }\n                        }\n                    });\n\n                    ui.collapsing(\"Width\", |ui| {\n                        if ui\n                            .add(egui::Slider::new(\n                                &mut self.border_config.border_width,\n                                -50..=50,\n                            ))\n                            .changed()\n                        {\n                            komorebi_client::send_message(&SocketMessage::BorderWidth(\n                                self.border_config.border_width,\n                            ))\n                            .unwrap();\n                        };\n                    });\n\n                    ui.collapsing(\"Offset\", |ui| {\n                        if ui\n                            .add(egui::Slider::new(\n                                &mut self.border_config.border_offset,\n                                -50..=50,\n                            ))\n                            .changed()\n                        {\n                            komorebi_client::send_message(&SocketMessage::BorderOffset(\n                                self.border_config.border_offset,\n                            ))\n                            .unwrap();\n                        };\n                    });\n                });\n\n                ui.collapsing(\"Stackbar\", |ui| {\n                    for option in [\n                        StackbarMode::Never,\n                        StackbarMode::OnStack,\n                        StackbarMode::Always,\n                    ] {\n                        if ui\n                            .add(egui::Button::selectable(\n                                self.stackbar_config.mode == option,\n                                option.to_string(),\n                            ))\n                            .clicked()\n                        {\n                            self.stackbar_config.mode = option;\n                            komorebi_client::send_message(&SocketMessage::StackbarMode(\n                                self.stackbar_config.mode,\n                            ))\n                            .unwrap();\n\n                            komorebi_client::send_message(&SocketMessage::Retile).unwrap()\n                        }\n                    }\n\n                    ui.collapsing(\"Label\", |ui| {\n                        for option in [StackbarLabel::Process, StackbarLabel::Title] {\n                            if ui\n                                .add(egui::Button::selectable(\n                                    self.stackbar_config.label == option,\n                                    option.to_string(),\n                                ))\n                                .clicked()\n                            {\n                                self.stackbar_config.label = option;\n                                komorebi_client::send_message(&SocketMessage::StackbarLabel(\n                                    self.stackbar_config.label,\n                                ))\n                                .unwrap();\n                            }\n                        }\n                    });\n\n                    ui.collapsing(\"Colours\", |ui| {\n                        ui.collapsing(\"Focused Text\", |ui| {\n                            if egui::color_picker::color_picker_color32(\n                                ui,\n                                &mut self.stackbar_config.focused_text_colour,\n                                Alpha::Opaque,\n                            ) {\n                                komorebi_client::send_message(\n                                    &SocketMessage::StackbarFocusedTextColour(\n                                        self.stackbar_config.focused_text_colour.r() as u32,\n                                        self.stackbar_config.focused_text_colour.g() as u32,\n                                        self.stackbar_config.focused_text_colour.b() as u32,\n                                    ),\n                                )\n                                .unwrap();\n                            }\n                        });\n\n                        ui.collapsing(\"Unfocused Text\", |ui| {\n                            if egui::color_picker::color_picker_color32(\n                                ui,\n                                &mut self.stackbar_config.unfocused_text_colour,\n                                Alpha::Opaque,\n                            ) {\n                                komorebi_client::send_message(\n                                    &SocketMessage::StackbarUnfocusedTextColour(\n                                        self.stackbar_config.unfocused_text_colour.r() as u32,\n                                        self.stackbar_config.unfocused_text_colour.g() as u32,\n                                        self.stackbar_config.unfocused_text_colour.b() as u32,\n                                    ),\n                                )\n                                .unwrap();\n                            }\n                        });\n\n                        ui.collapsing(\"Background\", |ui| {\n                            if egui::color_picker::color_picker_color32(\n                                ui,\n                                &mut self.stackbar_config.background_colour,\n                                Alpha::Opaque,\n                            ) {\n                                komorebi_client::send_message(\n                                    &SocketMessage::StackbarBackgroundColour(\n                                        self.stackbar_config.background_colour.r() as u32,\n                                        self.stackbar_config.background_colour.g() as u32,\n                                        self.stackbar_config.background_colour.b() as u32,\n                                    ),\n                                )\n                                .unwrap();\n                            }\n                        })\n                    });\n\n                    ui.collapsing(\"Width\", |ui| {\n                        if ui\n                            .add(egui::Slider::new(&mut self.stackbar_config.width, 0..=500))\n                            .drag_stopped()\n                        {\n                            komorebi_client::send_message(&SocketMessage::StackbarTabWidth(\n                                self.stackbar_config.width,\n                            ))\n                            .unwrap();\n\n                            komorebi_client::send_message(&SocketMessage::Retile).unwrap()\n                        };\n                    });\n\n                    ui.collapsing(\"Height\", |ui| {\n                        if ui\n                            .add(egui::Slider::new(&mut self.stackbar_config.height, 0..=100))\n                            .drag_stopped()\n                        {\n                            komorebi_client::send_message(&SocketMessage::StackbarHeight(\n                                self.stackbar_config.height,\n                            ))\n                            .unwrap();\n\n                            komorebi_client::send_message(&SocketMessage::Retile).unwrap()\n                        };\n                    });\n                });\n\n                for (monitor_idx, monitor) in self.monitors.iter_mut().enumerate() {\n                    ui.collapsing(\n                        format!(\n                            \"Monitor {monitor_idx} ({}x{})\",\n                            monitor.size.right, monitor.size.bottom\n                        ),\n                        |ui| {\n                            ui.collapsing(\"Work Area Offset\", |ui| {\n                                if ui\n                                    .add(\n                                        egui::Slider::new(\n                                            &mut monitor.work_area_offset.left,\n                                            0..=500,\n                                        )\n                                        .text(\"Left\"),\n                                    )\n                                    .drag_stopped()\n                                {\n                                    komorebi_client::send_message(\n                                        &SocketMessage::MonitorWorkAreaOffset(\n                                            monitor_idx,\n                                            monitor.work_area_offset,\n                                        ),\n                                    )\n                                    .unwrap();\n                                };\n\n                                if ui\n                                    .add(\n                                        egui::Slider::new(\n                                            &mut monitor.work_area_offset.top,\n                                            0..=500,\n                                        )\n                                        .text(\"Top\"),\n                                    )\n                                    .drag_stopped()\n                                {\n                                    komorebi_client::send_message(\n                                        &SocketMessage::MonitorWorkAreaOffset(\n                                            monitor_idx,\n                                            monitor.work_area_offset,\n                                        ),\n                                    )\n                                    .unwrap();\n                                };\n\n                                if ui\n                                    .add(\n                                        egui::Slider::new(\n                                            &mut monitor.work_area_offset.right,\n                                            0..=500,\n                                        )\n                                        .text(\"Right\"),\n                                    )\n                                    .drag_stopped()\n                                {\n                                    komorebi_client::send_message(\n                                        &SocketMessage::MonitorWorkAreaOffset(\n                                            monitor_idx,\n                                            monitor.work_area_offset,\n                                        ),\n                                    )\n                                    .unwrap();\n                                };\n\n                                if ui\n                                    .add(\n                                        egui::Slider::new(\n                                            &mut monitor.work_area_offset.bottom,\n                                            0..=500,\n                                        )\n                                        .text(\"Bottom\"),\n                                    )\n                                    .drag_stopped()\n                                {\n                                    komorebi_client::send_message(\n                                        &SocketMessage::MonitorWorkAreaOffset(\n                                            monitor_idx,\n                                            monitor.work_area_offset,\n                                        ),\n                                    )\n                                    .unwrap();\n                                };\n                            });\n\n                            ui.collapsing(\"Workspaces\", |ui| {\n                                for (workspace_idx, workspace) in\n                                    monitor.workspaces.iter_mut().enumerate()\n                                {\n                                    ui.collapsing(\n                                        format!(\"Workspace {workspace_idx} ({})\", workspace.name),\n                                        |ui| {\n                                            if ui.button(\"Focus\").clicked() {\n                                                komorebi_client::send_message(\n                                                    &SocketMessage::MouseFollowsFocus(false),\n                                                )\n                                                .unwrap();\n\n                                                komorebi_client::send_message(\n                                                    &SocketMessage::FocusMonitorWorkspaceNumber(\n                                                        monitor_idx,\n                                                        workspace_idx,\n                                                    ),\n                                                )\n                                                .unwrap();\n\n                                                komorebi_client::send_message(\n                                                    &SocketMessage::MouseFollowsFocus(\n                                                        self.mouse_follows_focus,\n                                                    ),\n                                                )\n                                                .unwrap();\n                                            }\n\n                                            if ui\n                                                .toggle_value(&mut workspace.tile, \"Tiling\")\n                                                .changed()\n                                            {\n                                                komorebi_client::send_message(\n                                                    &SocketMessage::WorkspaceTiling(\n                                                        monitor_idx,\n                                                        workspace_idx,\n                                                        workspace.tile,\n                                                    ),\n                                                )\n                                                .unwrap();\n                                            }\n\n                                            ui.collapsing(\"Name\", |ui| {\n                                                let monitor_workspaces = self\n                                                    .workspace_names\n                                                    .get_mut(&monitor_idx)\n                                                    .unwrap();\n                                                let workspace_name =\n                                                    &mut monitor_workspaces[workspace_idx];\n                                                if ui\n                                                    .text_edit_singleline(workspace_name)\n                                                    .lost_focus()\n                                                {\n                                                    workspace.name.clone_from(workspace_name);\n                                                    komorebi_client::send_message(\n                                                        &SocketMessage::WorkspaceName(\n                                                            monitor_idx,\n                                                            workspace_idx,\n                                                            workspace.name.clone(),\n                                                        ),\n                                                    )\n                                                    .unwrap();\n                                                }\n                                            });\n\n                                            ui.collapsing(\"Layout\", |ui| {\n                                                for option in [\n                                                    DefaultLayout::BSP,\n                                                    DefaultLayout::Columns,\n                                                    DefaultLayout::Rows,\n                                                    DefaultLayout::VerticalStack,\n                                                    DefaultLayout::HorizontalStack,\n                                                    DefaultLayout::UltrawideVerticalStack,\n                                                    DefaultLayout::Grid,\n                                                ] {\n                                                    if ui\n                                                        .add(egui::Button::selectable(\n                                                            workspace.layout == option,\n                                                            option.to_string(),\n                                                        ))\n                                                        .clicked()\n                                                    {\n                                                        workspace.layout = option;\n                                                        komorebi_client::send_message(\n                                                            &SocketMessage::WorkspaceLayout(\n                                                                monitor_idx,\n                                                                workspace_idx,\n                                                                workspace.layout,\n                                                            ),\n                                                        )\n                                                        .unwrap();\n                                                    }\n                                                }\n                                            });\n\n                                            ui.collapsing(\"Container Padding\", |ui| {\n                                                if ui\n                                                    .add(egui::Slider::new(\n                                                        &mut workspace.container_padding,\n                                                        0..=100,\n                                                    ))\n                                                    .drag_stopped()\n                                                {\n                                                    komorebi_client::send_message(\n                                                        &SocketMessage::ContainerPadding(\n                                                            monitor_idx,\n                                                            workspace_idx,\n                                                            workspace.container_padding,\n                                                        ),\n                                                    )\n                                                    .unwrap();\n                                                };\n                                            });\n\n                                            ui.collapsing(\"Workspace Padding\", |ui| {\n                                                if ui\n                                                    .add(egui::Slider::new(\n                                                        &mut workspace.workspace_padding,\n                                                        0..=100,\n                                                    ))\n                                                    .drag_stopped()\n                                                {\n                                                    komorebi_client::send_message(\n                                                        &SocketMessage::WorkspacePadding(\n                                                            monitor_idx,\n                                                            workspace_idx,\n                                                            workspace.workspace_padding,\n                                                        ),\n                                                    )\n                                                    .unwrap();\n                                                };\n                                            });\n                                        },\n                                    );\n                                }\n                            });\n                        },\n                    );\n                }\n            });\n        });\n    }\n}\n"
  },
  {
    "path": "komorebi-layouts/Cargo.toml",
    "content": "[package]\nname = \"komorebi-layouts\"\nversion = \"0.1.41\"\nedition = \"2024\"\n\n[dependencies]\nclap = { workspace = true }\ncolor-eyre = { workspace = true }\nserde = { workspace = true }\nserde_json = { workspace = true }\nserde_yaml = { workspace = true }\nstrum = { workspace = true }\ntracing = { workspace = true }\n\n# Optional dependencies\nschemars = { workspace = true, optional = true }\nwindows = { workspace = true, optional = true }\nobjc2-core-foundation = { version = \"0.3\", default-features = false, features = [\n  \"std\",\n  \"CFCGTypes\",\n], optional = true }\n\n[features]\nschemars = [\"dep:schemars\"]\nwin32 = [\"dep:windows\"]\ndarwin = [\"dep:objc2-core-foundation\"]\n"
  },
  {
    "path": "komorebi-layouts/src/arrangement.rs",
    "content": "use std::num::NonZeroUsize;\n\nuse clap::ValueEnum;\nuse serde::Deserialize;\nuse serde::Serialize;\nuse strum::Display;\nuse strum::EnumString;\n\n#[cfg(feature = \"win32\")]\nuse super::CustomLayout;\nuse super::DefaultLayout;\nuse super::Rect;\n#[cfg(feature = \"win32\")]\nuse super::custom_layout::Column;\n#[cfg(feature = \"win32\")]\nuse super::custom_layout::ColumnSplit;\n#[cfg(feature = \"win32\")]\nuse super::custom_layout::ColumnSplitWithCapacity;\nuse crate::default_layout::DEFAULT_RATIO;\nuse crate::default_layout::DEFAULT_SECONDARY_RATIO;\nuse crate::default_layout::LayoutOptions;\nuse crate::default_layout::MAX_RATIO;\nuse crate::default_layout::MAX_RATIOS;\nuse crate::default_layout::MIN_RATIO;\n\npub trait Arrangement {\n    #[allow(clippy::too_many_arguments)]\n    fn calculate(\n        &self,\n        area: &Rect,\n        len: NonZeroUsize,\n        container_padding: Option<i32>,\n        layout_flip: Option<Axis>,\n        resize_dimensions: &[Option<Rect>],\n        focused_idx: usize,\n        layout_options: Option<LayoutOptions>,\n        latest_layout: &[Rect],\n    ) -> Vec<Rect>;\n}\n\nimpl Arrangement for DefaultLayout {\n    #[allow(clippy::too_many_lines, clippy::cognitive_complexity)]\n    fn calculate(\n        &self,\n        area: &Rect,\n        len: NonZeroUsize,\n        container_padding: Option<i32>,\n        layout_flip: Option<Axis>,\n        resize_dimensions: &[Option<Rect>],\n        focused_idx: usize,\n        layout_options: Option<LayoutOptions>,\n        latest_layout: &[Rect],\n    ) -> Vec<Rect> {\n        // Trace layout_options for debugging\n        if let Some(ref opts) = layout_options {\n            tracing::debug!(\n                \"Layout {:?} - layout_options received: column_ratios={:?}, row_ratios={:?}\",\n                self,\n                opts.column_ratios,\n                opts.row_ratios\n            );\n        } else {\n            tracing::debug!(\"Layout {:?} - no layout_options provided\", self);\n        }\n\n        let len = usize::from(len);\n        let mut dimensions = match self {\n            Self::Scrolling => {\n                let column_count = layout_options\n                    .as_ref()\n                    .and_then(|o| o.scrolling.map(|s| s.columns))\n                    .unwrap_or(3);\n\n                let column_width = area.right / column_count.min(len) as i32;\n                let mut layouts = Vec::with_capacity(len);\n\n                let visible_columns = area.right / column_width;\n                let keep_centered = layout_options\n                    .as_ref()\n                    .and_then(|o| {\n                        o.scrolling\n                            .map(|s| s.center_focused_column.unwrap_or_default())\n                    })\n                    .unwrap_or(false);\n\n                let first_visible: isize = if focused_idx == 0 {\n                    // if focused idx is 0, we are at the beginning of the scrolling strip\n                    0\n                } else {\n                    let previous_first_visible = if latest_layout.is_empty() {\n                        0\n                    } else {\n                        // previous first_visible based on the left position of the first visible window\n                        let left_edge = area.left;\n                        latest_layout\n                            .iter()\n                            .position(|rect| rect.left >= left_edge)\n                            .unwrap_or(0) as isize\n                    };\n\n                    let focused_idx = focused_idx as isize;\n\n                    // if center_focused_column is enabled, and we have an odd number of visible columns,\n                    // center the focused window column\n                    if keep_centered && visible_columns % 2 == 1 {\n                        let center_offset = visible_columns as isize / 2;\n                        (focused_idx - center_offset).max(0).min(\n                            (len as isize)\n                                .saturating_sub(visible_columns as isize)\n                                .max(0),\n                        )\n                    } else {\n                        if focused_idx < previous_first_visible {\n                            // focused window is off the left edge, we need to scroll left\n                            focused_idx\n                        } else if focused_idx >= previous_first_visible + visible_columns as isize {\n                            // focused window is off the right edge, we need to scroll right\n                            // and make sure it's the last visible window\n                            (focused_idx + 1 - visible_columns as isize).max(0)\n                        } else {\n                            // focused window is already visible, we don't need to scroll\n                            previous_first_visible\n                        }\n                        .min(\n                            (len as isize)\n                                .saturating_sub(visible_columns as isize)\n                                .max(0),\n                        )\n                    }\n                };\n\n                for i in 0..len {\n                    let position = (i as isize) - first_visible;\n                    let left = area.left + (position as i32 * column_width);\n\n                    layouts.push(Rect {\n                        left,\n                        top: area.top,\n                        right: column_width,\n                        bottom: area.bottom,\n                    });\n                }\n\n                // Last visible column absorbs any remainder from integer division\n                // so that visible columns tile the full area width without gaps\n                let width_remainder = area.right - column_width * visible_columns;\n                if width_remainder > 0 {\n                    let last_visible_idx =\n                        (first_visible as usize + visible_columns as usize - 1).min(len - 1);\n                    layouts[last_visible_idx].right += width_remainder;\n                }\n\n                let adjustment = calculate_scrolling_adjustment(resize_dimensions);\n                layouts\n                    .iter_mut()\n                    .zip(adjustment.iter())\n                    .for_each(|(layout, adjustment)| {\n                        layout.top += adjustment.top;\n                        layout.bottom += adjustment.bottom;\n                        layout.left += adjustment.left;\n                        layout.right += adjustment.right;\n                    });\n\n                layouts\n            }\n            Self::BSP => {\n                let column_split_ratio = layout_options\n                    .and_then(|o| o.column_ratios)\n                    .and_then(|r| r[0])\n                    .unwrap_or(DEFAULT_RATIO)\n                    .clamp(MIN_RATIO, MAX_RATIO);\n                let row_split_ratio = layout_options\n                    .and_then(|o| o.row_ratios)\n                    .and_then(|r| r[0])\n                    .unwrap_or(DEFAULT_RATIO)\n                    .clamp(MIN_RATIO, MAX_RATIO);\n                recursive_fibonacci(\n                    0,\n                    len,\n                    area,\n                    layout_flip,\n                    calculate_resize_adjustments(resize_dimensions),\n                    column_split_ratio,\n                    row_split_ratio,\n                )\n            }\n            Self::Columns => {\n                let ratios = layout_options.and_then(|o| o.column_ratios);\n                let mut layouts = columns_with_ratios(area, len, ratios);\n\n                let adjustment = calculate_columns_adjustment(resize_dimensions);\n                layouts\n                    .iter_mut()\n                    .zip(adjustment.iter())\n                    .for_each(|(layout, adjustment)| {\n                        layout.top += adjustment.top;\n                        layout.bottom += adjustment.bottom;\n                        layout.left += adjustment.left;\n                        layout.right += adjustment.right;\n                    });\n\n                if matches!(\n                    layout_flip,\n                    Some(Axis::Horizontal | Axis::HorizontalAndVertical)\n                ) && let 2.. = len\n                {\n                    columns_reverse(&mut layouts);\n                }\n\n                layouts\n            }\n            Self::Rows => {\n                let ratios = layout_options.and_then(|o| o.row_ratios);\n                let mut layouts = rows_with_ratios(area, len, ratios);\n\n                let adjustment = calculate_rows_adjustment(resize_dimensions);\n                layouts\n                    .iter_mut()\n                    .zip(adjustment.iter())\n                    .for_each(|(layout, adjustment)| {\n                        layout.top += adjustment.top;\n                        layout.bottom += adjustment.bottom;\n                        layout.left += adjustment.left;\n                        layout.right += adjustment.right;\n                    });\n\n                if matches!(\n                    layout_flip,\n                    Some(Axis::Vertical | Axis::HorizontalAndVertical)\n                ) && let 2.. = len\n                {\n                    rows_reverse(&mut layouts);\n                }\n\n                layouts\n            }\n            Self::VerticalStack => {\n                let mut layouts: Vec<Rect> = vec![];\n\n                #[allow(clippy::cast_possible_truncation)]\n                let primary_right = match len {\n                    1 => area.right,\n                    _ => {\n                        let ratio = layout_options\n                            .and_then(|o| o.column_ratios)\n                            .and_then(|r| r[0])\n                            .unwrap_or(DEFAULT_RATIO)\n                            .clamp(MIN_RATIO, MAX_RATIO);\n                        (area.right as f32 * ratio) as i32\n                    }\n                };\n\n                let main_left = area.left;\n                let stack_left = area.left + primary_right;\n\n                if len >= 1 {\n                    layouts.push(Rect {\n                        left: main_left,\n                        top: area.top,\n                        right: primary_right,\n                        bottom: area.bottom,\n                    });\n\n                    if len > 1 {\n                        let row_ratios = layout_options.and_then(|o| o.row_ratios);\n                        layouts.append(&mut rows_with_ratios(\n                            &Rect {\n                                left: stack_left,\n                                top: area.top,\n                                right: area.right - primary_right,\n                                bottom: area.bottom,\n                            },\n                            len - 1,\n                            row_ratios,\n                        ));\n                    }\n                }\n\n                let adjustment = calculate_vertical_stack_adjustment(resize_dimensions);\n                layouts\n                    .iter_mut()\n                    .zip(adjustment.iter())\n                    .for_each(|(layout, adjustment)| {\n                        layout.top += adjustment.top;\n                        layout.bottom += adjustment.bottom;\n                        layout.left += adjustment.left;\n                        layout.right += adjustment.right;\n                    });\n\n                if matches!(\n                    layout_flip,\n                    Some(Axis::Horizontal | Axis::HorizontalAndVertical)\n                ) && let 2.. = len\n                {\n                    let (primary, rest) = layouts.split_at_mut(1);\n                    let primary = &mut primary[0];\n\n                    for rect in rest.iter_mut() {\n                        rect.left = primary.left;\n                    }\n                    primary.left = rest[0].left + rest[0].right;\n                }\n\n                if matches!(\n                    layout_flip,\n                    Some(Axis::Vertical | Axis::HorizontalAndVertical)\n                ) && let 3.. = len\n                {\n                    rows_reverse(&mut layouts[1..]);\n                }\n\n                layouts\n            }\n            Self::RightMainVerticalStack => {\n                // Shamelessly borrowed from LeftWM: https://github.com/leftwm/leftwm/commit/f673851745295ae7584a102535566f559d96a941\n                let mut layouts: Vec<Rect> = vec![];\n\n                #[allow(clippy::cast_possible_truncation)]\n                let primary_width = match len {\n                    1 => area.right,\n                    _ => {\n                        let ratio = layout_options\n                            .and_then(|o| o.column_ratios)\n                            .and_then(|r| r[0])\n                            .unwrap_or(DEFAULT_RATIO)\n                            .clamp(MIN_RATIO, MAX_RATIO);\n                        (area.right as f32 * ratio) as i32\n                    }\n                };\n\n                let primary_left = match len {\n                    1 => 0,\n                    _ => area.right - primary_width,\n                };\n\n                if len >= 1 {\n                    layouts.push(Rect {\n                        left: area.left + primary_left,\n                        top: area.top,\n                        right: primary_width,\n                        bottom: area.bottom,\n                    });\n\n                    if len > 1 {\n                        let row_ratios = layout_options.and_then(|o| o.row_ratios);\n                        layouts.append(&mut rows_with_ratios(\n                            &Rect {\n                                left: area.left,\n                                top: area.top,\n                                right: primary_left,\n                                bottom: area.bottom,\n                            },\n                            len - 1,\n                            row_ratios,\n                        ));\n                    }\n                }\n\n                let adjustment = calculate_right_vertical_stack_adjustment(resize_dimensions);\n                layouts\n                    .iter_mut()\n                    .zip(adjustment.iter())\n                    .for_each(|(layout, adjustment)| {\n                        layout.top += adjustment.top;\n                        layout.bottom += adjustment.bottom;\n                        layout.left += adjustment.left;\n                        layout.right += adjustment.right;\n                    });\n\n                if matches!(\n                    layout_flip,\n                    Some(Axis::Horizontal | Axis::HorizontalAndVertical)\n                ) && let 2.. = len\n                {\n                    let (primary, rest) = layouts.split_at_mut(1);\n                    let primary = &mut primary[0];\n\n                    primary.left = rest[0].left;\n                    for rect in rest.iter_mut() {\n                        rect.left = primary.left + primary.right;\n                    }\n                }\n\n                if matches!(\n                    layout_flip,\n                    Some(Axis::Vertical | Axis::HorizontalAndVertical)\n                ) && let 3.. = len\n                {\n                    rows_reverse(&mut layouts[1..]);\n                }\n\n                layouts\n            }\n            Self::HorizontalStack => {\n                let mut layouts: Vec<Rect> = vec![];\n\n                #[allow(clippy::cast_possible_truncation)]\n                let bottom = match len {\n                    1 => area.bottom,\n                    _ => {\n                        let ratio = layout_options\n                            .and_then(|o| o.row_ratios)\n                            .and_then(|r| r[0])\n                            .unwrap_or(DEFAULT_RATIO)\n                            .clamp(MIN_RATIO, MAX_RATIO);\n                        (area.bottom as f32 * ratio) as i32\n                    }\n                };\n\n                let main_top = area.top;\n                let stack_top = area.top + bottom;\n\n                if len >= 1 {\n                    layouts.push(Rect {\n                        left: area.left,\n                        top: main_top,\n                        right: area.right,\n                        bottom,\n                    });\n\n                    if len > 1 {\n                        let col_ratios = layout_options.and_then(|o| o.column_ratios);\n                        layouts.append(&mut columns_with_ratios(\n                            &Rect {\n                                left: area.left,\n                                top: stack_top,\n                                right: area.right,\n                                bottom: area.bottom - bottom,\n                            },\n                            len - 1,\n                            col_ratios,\n                        ));\n                    }\n                }\n\n                let adjustment = calculate_horizontal_stack_adjustment(resize_dimensions);\n                layouts\n                    .iter_mut()\n                    .zip(adjustment.iter())\n                    .for_each(|(layout, adjustment)| {\n                        layout.top += adjustment.top;\n                        layout.bottom += adjustment.bottom;\n                        layout.left += adjustment.left;\n                        layout.right += adjustment.right;\n                    });\n\n                if matches!(\n                    layout_flip,\n                    Some(Axis::Vertical | Axis::HorizontalAndVertical)\n                ) && let 2.. = len\n                {\n                    let (primary, rest) = layouts.split_at_mut(1);\n                    let primary = &mut primary[0];\n\n                    for rect in rest.iter_mut() {\n                        rect.top = primary.top;\n                    }\n                    primary.top = rest[0].top + rest[0].bottom;\n                }\n\n                if matches!(\n                    layout_flip,\n                    Some(Axis::Horizontal | Axis::HorizontalAndVertical)\n                ) && let 3.. = len\n                {\n                    columns_reverse(&mut layouts[1..]);\n                }\n\n                layouts\n            }\n            Self::UltrawideVerticalStack => {\n                let mut layouts: Vec<Rect> = vec![];\n\n                // Get ratios: [0] = primary (center), [1] = secondary (left), remainder = tertiary (right)\n                let ratios = layout_options.and_then(|o| o.column_ratios);\n                let primary_ratio = ratios\n                    .and_then(|r| r[0])\n                    .unwrap_or(DEFAULT_RATIO)\n                    .clamp(MIN_RATIO, MAX_RATIO);\n                let secondary_ratio = ratios\n                    .and_then(|r| r[1])\n                    .unwrap_or(DEFAULT_SECONDARY_RATIO)\n                    .clamp(MIN_RATIO, MAX_RATIO);\n\n                #[allow(clippy::cast_possible_truncation)]\n                let primary_right = match len {\n                    1 => area.right,\n                    _ => (area.right as f32 * primary_ratio) as i32,\n                };\n\n                #[allow(clippy::cast_possible_truncation)]\n                let secondary_right = match len {\n                    1 => 0,\n                    2 => area.right - primary_right,\n                    _ => (area.right as f32 * secondary_ratio) as i32,\n                };\n\n                let (primary_left, secondary_left, stack_left) = match len {\n                    1 => (area.left, 0, 0),\n                    2 => {\n                        let primary = area.left + secondary_right;\n                        let secondary = area.left;\n\n                        (primary, secondary, 0)\n                    }\n                    _ => {\n                        let primary = area.left + secondary_right;\n                        let secondary = area.left;\n                        let stack = area.left + primary_right + secondary_right;\n\n                        (primary, secondary, stack)\n                    }\n                };\n\n                if len >= 1 {\n                    layouts.push(Rect {\n                        left: primary_left,\n                        top: area.top,\n                        right: primary_right,\n                        bottom: area.bottom,\n                    });\n\n                    if len >= 2 {\n                        layouts.push(Rect {\n                            left: secondary_left,\n                            top: area.top,\n                            right: secondary_right,\n                            bottom: area.bottom,\n                        });\n\n                        if len > 2 {\n                            // Tertiary column gets remaining space after primary and secondary\n                            let tertiary_right = area.right - primary_right - secondary_right;\n                            let row_ratios = layout_options.and_then(|o| o.row_ratios);\n                            layouts.append(&mut rows_with_ratios(\n                                &Rect {\n                                    left: stack_left,\n                                    top: area.top,\n                                    right: tertiary_right,\n                                    bottom: area.bottom,\n                                },\n                                len - 2,\n                                row_ratios,\n                            ));\n                        }\n                    }\n                }\n\n                let adjustment = calculate_ultrawide_adjustment(resize_dimensions);\n                layouts\n                    .iter_mut()\n                    .zip(adjustment.iter())\n                    .for_each(|(layout, adjustment)| {\n                        layout.top += adjustment.top;\n                        layout.bottom += adjustment.bottom;\n                        layout.left += adjustment.left;\n                        layout.right += adjustment.right;\n                    });\n\n                if matches!(\n                    layout_flip,\n                    Some(Axis::Horizontal | Axis::HorizontalAndVertical)\n                ) {\n                    match len {\n                        2 => {\n                            let (primary, secondary) = layouts.split_at_mut(1);\n                            let primary = &mut primary[0];\n                            let secondary = &mut secondary[0];\n\n                            primary.left = secondary.left;\n                            secondary.left = primary.left + primary.right;\n                        }\n                        3.. => {\n                            let (primary, rest) = layouts.split_at_mut(1);\n                            let (secondary, tertiary) = rest.split_at_mut(1);\n                            let primary = &mut primary[0];\n                            let secondary = &mut secondary[0];\n\n                            for rect in tertiary.iter_mut() {\n                                rect.left = secondary.left;\n                            }\n                            primary.left = tertiary[0].left + tertiary[0].right;\n                            secondary.left = primary.left + primary.right;\n                        }\n                        _ => {}\n                    }\n                }\n\n                if matches!(\n                    layout_flip,\n                    Some(Axis::Vertical | Axis::HorizontalAndVertical)\n                ) && let 4.. = len\n                {\n                    rows_reverse(&mut layouts[2..]);\n                }\n\n                layouts\n            }\n            #[allow(\n                clippy::cast_precision_loss,\n                clippy::cast_possible_truncation,\n                clippy::cast_possible_wrap\n            )]\n            Self::Grid => {\n                // Shamelessly lifted from LeftWM\n                // https://github.com/leftwm/leftwm/blob/18675067b8450e520ef75db2ebbb0d973aa1199e/leftwm-core/src/layouts/grid_horizontal.rs\n                let mut layouts: Vec<Rect> = vec![];\n                layouts.resize(len, Rect::default());\n\n                let len = len as i32;\n\n                let row_constraint = layout_options.as_ref().and_then(|o| o.grid.map(|g| g.rows));\n                let column_ratios = layout_options.and_then(|o| o.column_ratios);\n\n                // Count defined column ratios (already validated at deserialization to sum < 1.0)\n                let defined_ratios = column_ratios\n                    .as_ref()\n                    .map(|r| r.iter().filter(|x| x.is_some()).count())\n                    .unwrap_or(0);\n\n                let num_cols = if let Some(rows) = row_constraint {\n                    ((len as f32) / (rows as f32)).ceil() as i32\n                } else {\n                    (len as f32).sqrt().ceil() as i32\n                };\n\n                // Pre-calculate column widths and left positions using same logic as columns_with_ratios\n                let mut col_widths: Vec<i32> = Vec::with_capacity(num_cols as usize);\n                let mut col_lefts: Vec<i32> = Vec::with_capacity(num_cols as usize);\n                let mut current_left = area.left;\n\n                for col in 0..num_cols {\n                    let col_idx = col as usize;\n                    let width = if let Some(ref ratios) = column_ratios {\n                        // Only apply ratio if there's at least one more column after this\n                        // The last column always gets the remaining space\n                        let should_apply_ratio =\n                            col_idx < MAX_RATIOS && col_idx < defined_ratios && col < num_cols - 1;\n\n                        if should_apply_ratio {\n                            if let Some(ratio) = ratios[col_idx] {\n                                (area.right as f32 * ratio) as i32\n                            } else {\n                                let used: f32 = (0..col_idx).filter_map(|j| ratios[j]).sum();\n                                let remaining_space =\n                                    area.right - (area.right as f32 * used) as i32;\n                                let remaining_cols = num_cols - col;\n                                remaining_space / remaining_cols\n                            }\n                        } else {\n                            // Beyond defined ratios or last column - split remaining space equally\n                            // Only count ratios that were actually applied (up to defined_ratios, but not beyond num_cols - 1)\n                            let ratios_applied = defined_ratios.min((num_cols - 1) as usize);\n                            let used: f32 = (0..ratios_applied).filter_map(|j| ratios[j]).sum();\n                            let remaining_space = area.right - (area.right as f32 * used) as i32;\n                            let remaining_cols = (num_cols as usize - ratios_applied) as i32;\n                            if remaining_cols > 0 {\n                                remaining_space / remaining_cols\n                            } else {\n                                remaining_space\n                            }\n                        }\n                    } else {\n                        area.right / num_cols\n                    };\n\n                    col_lefts.push(current_left);\n                    col_widths.push(width);\n                    current_left += width;\n                }\n\n                // Last column absorbs any remainder from integer division\n                // so that columns tile the full area width without gaps\n                let total_width: i32 = col_widths.iter().sum();\n                let width_remainder = area.right - total_width;\n                if width_remainder > 0\n                    && let Some(last) = col_widths.last_mut()\n                {\n                    *last += width_remainder;\n                }\n\n                // Pre-calculate flipped column positions: same widths laid out\n                // in reverse order so that the last column sits at area.left\n                let flipped_col_lefts = if matches!(\n                    layout_flip,\n                    Some(Axis::Horizontal | Axis::HorizontalAndVertical)\n                ) {\n                    let n = num_cols as usize;\n                    let mut flipped = vec![0i32; n];\n                    let mut fl = area.left;\n                    for i in (0..n).rev() {\n                        flipped[i] = fl;\n                        fl += col_widths[i];\n                    }\n                    flipped\n                } else {\n                    vec![]\n                };\n\n                let mut iter = layouts.iter_mut().enumerate().peekable();\n\n                for col in 0..num_cols {\n                    let iter_peek = iter.peek().map(|x| x.0).unwrap_or_default() as i32;\n                    let remaining_windows = len - iter_peek;\n                    let remaining_columns = num_cols - col;\n\n                    let num_rows_in_this_col = if let Some(rows) = row_constraint {\n                        (remaining_windows / remaining_columns).min(rows as i32)\n                    } else {\n                        remaining_windows / remaining_columns\n                    };\n\n                    // Rows within each column: base height from integer division,\n                    // last row absorbs any remainder to cover the full area height\n                    let base_height = area.bottom / num_rows_in_this_col;\n                    let height_remainder = area.bottom - base_height * num_rows_in_this_col;\n\n                    let col_idx = col as usize;\n                    let win_width = col_widths[col_idx];\n                    let col_left = col_lefts[col_idx];\n\n                    for row in 0..num_rows_in_this_col {\n                        if let Some((_idx, win)) = iter.next() {\n                            let is_last_row = row == num_rows_in_this_col - 1;\n                            let win_height = if is_last_row {\n                                base_height + height_remainder\n                            } else {\n                                base_height\n                            };\n\n                            let mut left = col_left;\n                            let mut top = area.top + base_height * row;\n\n                            match layout_flip {\n                                Some(Axis::Horizontal) => {\n                                    left = flipped_col_lefts[col_idx];\n                                }\n                                Some(Axis::Vertical) => {\n                                    top = if is_last_row {\n                                        area.top\n                                    } else {\n                                        area.top + area.bottom - base_height * (row + 1)\n                                    };\n                                }\n                                Some(Axis::HorizontalAndVertical) => {\n                                    left = flipped_col_lefts[col_idx];\n                                    top = if is_last_row {\n                                        area.top\n                                    } else {\n                                        area.top + area.bottom - base_height * (row + 1)\n                                    };\n                                }\n                                None => {}\n                            }\n\n                            win.bottom = win_height;\n                            win.right = win_width;\n                            win.left = left;\n                            win.top = top;\n                        }\n                    }\n                }\n\n                layouts\n            }\n        };\n\n        dimensions\n            .iter_mut()\n            .for_each(|l| l.add_padding(container_padding.unwrap_or_default()));\n\n        dimensions\n    }\n}\n\n#[cfg(feature = \"win32\")]\nimpl Arrangement for CustomLayout {\n    fn calculate(\n        &self,\n        area: &Rect,\n        len: NonZeroUsize,\n        container_padding: Option<i32>,\n        _layout_flip: Option<Axis>,\n        _resize_dimensions: &[Option<Rect>],\n        _focused_idx: usize,\n        _layout_options: Option<LayoutOptions>,\n        _latest_layout: &[Rect],\n    ) -> Vec<Rect> {\n        let mut dimensions = vec![];\n        let container_count = len.get();\n\n        if container_count < self.len() {\n            let mut layouts = columns(area, container_count);\n            dimensions.append(&mut layouts);\n        } else {\n            let count_map = self.column_container_counts();\n\n            // If there are not enough windows to trigger the final tertiary\n            // column in the custom layout, use an offset to reduce the number of\n            // columns to calculate each column's area by, so that we don't have\n            // an empty ghost tertiary column and the screen space can be maximised\n            // until there are enough windows to create it\n            let mut tertiary_trigger_threshold = 0;\n\n            // always -1 because we don't insert the tertiary column in the count_map\n            for i in 0..self.len() - 1 {\n                tertiary_trigger_threshold += count_map.get(&i).unwrap();\n            }\n\n            let enable_tertiary_column = len.get() > tertiary_trigger_threshold;\n\n            let offset = if enable_tertiary_column {\n                None\n            } else {\n                Option::from(1)\n            };\n\n            #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]\n            let primary_right = self.primary_width_percentage().map_or_else(\n                || area.right / self.len() as i32,\n                |percentage| (area.right / 100) * percentage as i32,\n            );\n\n            for (idx, column) in self.iter().enumerate() {\n                // If we are offsetting a tertiary column for which the threshold\n                // has not yet been met, this loop should not run for that final\n                // tertiary column\n                if idx < self.len() - offset.unwrap_or(0) {\n                    let column_area = if idx == 0 {\n                        Self::column_area_with_last(self.len(), area, primary_right, None, offset)\n                    } else {\n                        Self::column_area_with_last(\n                            self.len(),\n                            area,\n                            primary_right,\n                            Option::from(dimensions[self.first_container_idx(idx - 1)]),\n                            offset,\n                        )\n                    };\n\n                    match column {\n                        Column::Primary(Some(_)) => {\n                            let main_column_area = if idx == 0 {\n                                Self::main_column_area(area, primary_right, None)\n                            } else {\n                                Self::main_column_area(\n                                    area,\n                                    primary_right,\n                                    Option::from(dimensions[self.first_container_idx(idx - 1)]),\n                                )\n                            };\n\n                            dimensions.push(main_column_area);\n                        }\n                        Column::Primary(None) | Column::Secondary(None) => {\n                            dimensions.push(column_area);\n                        }\n                        Column::Secondary(Some(split)) => match split {\n                            ColumnSplitWithCapacity::Horizontal(capacity) => {\n                                let mut rows = rows(&column_area, *capacity);\n                                dimensions.append(&mut rows);\n                            }\n                            ColumnSplitWithCapacity::Vertical(capacity) => {\n                                let mut columns = columns(&column_area, *capacity);\n                                dimensions.append(&mut columns);\n                            }\n                        },\n                        Column::Tertiary(split) => {\n                            let column_area = Self::column_area_with_last(\n                                self.len(),\n                                area,\n                                primary_right,\n                                Option::from(dimensions[self.first_container_idx(idx - 1)]),\n                                offset,\n                            );\n\n                            let remaining = container_count - tertiary_trigger_threshold;\n\n                            match split {\n                                ColumnSplit::Horizontal => {\n                                    let mut rows = rows(&column_area, remaining);\n                                    dimensions.append(&mut rows);\n                                }\n                                ColumnSplit::Vertical => {\n                                    let mut columns = columns(&column_area, remaining);\n                                    dimensions.append(&mut columns);\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        dimensions\n            .iter_mut()\n            .for_each(|l| l.add_padding(container_padding.unwrap_or_default()));\n\n        dimensions\n    }\n}\n\n#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Axis on which to perform an operation\npub enum Axis {\n    /// Horizontal axis\n    Horizontal,\n    /// Vertical axis\n    Vertical,\n    /// Both horizontal and vertical axes\n    HorizontalAndVertical,\n}\n\n#[cfg(feature = \"win32\")]\n#[must_use]\nfn columns(area: &Rect, len: usize) -> Vec<Rect> {\n    columns_with_ratios(area, len, None)\n}\n\n#[must_use]\nfn columns_with_ratios(\n    area: &Rect,\n    len: usize,\n    ratios: Option<[Option<f32>; MAX_RATIOS]>,\n) -> Vec<Rect> {\n    tracing::debug!(\n        \"columns_with_ratios called: len={}, ratios={:?}\",\n        len,\n        ratios\n    );\n    let mut layouts: Vec<Rect> = vec![];\n    let mut left = 0;\n\n    // Count how many ratios are defined (already validated at deserialization to sum < 1.0)\n    let defined_ratios = ratios\n        .as_ref()\n        .map(|r| r.iter().filter(|x| x.is_some()).count())\n        .unwrap_or(0);\n\n    for i in 0..len {\n        #[allow(clippy::cast_possible_truncation)]\n        let right = if let Some(ref r) = ratios {\n            // Only apply ratio[i] if there's at least one more column after this (i < len - 1)\n            // The last column always gets the remaining space\n            let should_apply_ratio = i < MAX_RATIOS && i < defined_ratios && i < len - 1;\n\n            if should_apply_ratio {\n                if let Some(ratio) = r[i] {\n                    (area.right as f32 * ratio) as i32\n                } else {\n                    let used: f32 = (0..i).filter_map(|j| r[j]).sum();\n                    let remaining_space = area.right - (area.right as f32 * used) as i32;\n                    let remaining_columns = len - i;\n                    remaining_space / remaining_columns as i32\n                }\n            } else {\n                // Last column or beyond defined ratios - split remaining space equally\n                let ratios_applied = i.min(defined_ratios).min(len.saturating_sub(1));\n                let used: f32 = (0..ratios_applied).filter_map(|j| r[j]).sum();\n                let remaining_space = area.right - (area.right as f32 * used) as i32;\n                let remaining_columns = len - ratios_applied;\n                if remaining_columns > 0 {\n                    remaining_space / remaining_columns as i32\n                } else {\n                    remaining_space\n                }\n            }\n        } else {\n            // Equal width columns (original behavior)\n            #[allow(clippy::cast_possible_wrap)]\n            {\n                area.right / len as i32\n            }\n        };\n\n        layouts.push(Rect {\n            left: area.left + left,\n            top: area.top,\n            right,\n            bottom: area.bottom,\n        });\n\n        left += right;\n    }\n\n    // Last column absorbs any remainder from integer division\n    // so that columns tile the full area width without gaps\n    let total_width: i32 = layouts.iter().map(|r| r.right).sum();\n    let remainder = area.right - total_width;\n    if remainder > 0\n        && let Some(last) = layouts.last_mut()\n    {\n        last.right += remainder;\n    }\n\n    layouts\n}\n\n#[cfg(feature = \"win32\")]\n#[must_use]\nfn rows(area: &Rect, len: usize) -> Vec<Rect> {\n    rows_with_ratios(area, len, None)\n}\n\n#[must_use]\nfn rows_with_ratios(\n    area: &Rect,\n    len: usize,\n    ratios: Option<[Option<f32>; MAX_RATIOS]>,\n) -> Vec<Rect> {\n    tracing::debug!(\"rows_with_ratios called: len={}, ratios={:?}\", len, ratios);\n    let mut layouts: Vec<Rect> = vec![];\n    let mut top = 0;\n\n    // Count how many ratios are defined (already validated at deserialization to sum < 1.0)\n    let defined_ratios = ratios\n        .as_ref()\n        .map(|r| r.iter().filter(|x| x.is_some()).count())\n        .unwrap_or(0);\n\n    for i in 0..len {\n        #[allow(clippy::cast_possible_truncation)]\n        let bottom = if let Some(ref r) = ratios {\n            // Only apply ratio[i] if there's at least one more row after this (i < len - 1)\n            // The last row always gets the remaining space\n            let should_apply_ratio = i < MAX_RATIOS && i < defined_ratios && i < len - 1;\n\n            if should_apply_ratio {\n                if let Some(ratio) = r[i] {\n                    (area.bottom as f32 * ratio) as i32\n                } else {\n                    let used: f32 = (0..i).filter_map(|j| r[j]).sum();\n                    let remaining_space = area.bottom - (area.bottom as f32 * used) as i32;\n                    let remaining_rows = len - i;\n                    remaining_space / remaining_rows as i32\n                }\n            } else {\n                // Last row or beyond defined ratios - split remaining space equally\n                let ratios_applied = i.min(defined_ratios).min(len.saturating_sub(1));\n                let used: f32 = (0..ratios_applied).filter_map(|j| r[j]).sum();\n                let remaining_space = area.bottom - (area.bottom as f32 * used) as i32;\n                let remaining_rows = len - ratios_applied;\n                if remaining_rows > 0 {\n                    remaining_space / remaining_rows as i32\n                } else {\n                    remaining_space\n                }\n            }\n        } else {\n            // Equal height rows (original behavior)\n            #[allow(clippy::cast_possible_wrap)]\n            {\n                area.bottom / len as i32\n            }\n        };\n\n        layouts.push(Rect {\n            left: area.left,\n            top: area.top + top,\n            right: area.right,\n            bottom,\n        });\n\n        top += bottom;\n    }\n\n    // Last row absorbs any remainder from integer division\n    // so that rows tile the full area height without gaps\n    let total_height: i32 = layouts.iter().map(|r| r.bottom).sum();\n    let remainder = area.bottom - total_height;\n    if remainder > 0\n        && let Some(last) = layouts.last_mut()\n    {\n        last.bottom += remainder;\n    }\n\n    layouts\n}\n\nfn columns_reverse(columns: &mut [Rect]) {\n    let len = columns.len();\n    columns[len - 1].left = columns[0].left;\n    for i in (0..len - 1).rev() {\n        columns[i].left = columns[i + 1].left + columns[i + 1].right;\n    }\n}\n\nfn rows_reverse(rows: &mut [Rect]) {\n    let len = rows.len();\n    rows[len - 1].top = rows[0].top;\n    for i in (0..len - 1).rev() {\n        rows[i].top = rows[i + 1].top + rows[i + 1].bottom;\n    }\n}\n\nfn calculate_resize_adjustments(resize_dimensions: &[Option<Rect>]) -> Vec<Option<Rect>> {\n    let mut resize_adjustments = resize_dimensions.to_vec();\n\n    // This needs to be aware of layout flips\n    for (i, opt) in resize_dimensions.iter().enumerate() {\n        if let Some(resize_ref) = opt\n            && i > 0\n        {\n            if resize_ref.left != 0 {\n                #[allow(clippy::if_not_else)]\n                let range = if i == 1 {\n                    0..1\n                } else if i & 1 != 0 {\n                    i - 1..i\n                } else {\n                    i - 2..i\n                };\n\n                for n in range {\n                    let should_adjust = n % 2 == 0;\n                    if should_adjust {\n                        if let Some(Some(adjacent_resize)) = resize_adjustments.get_mut(n) {\n                            adjacent_resize.right += resize_ref.left;\n                        } else {\n                            resize_adjustments[n] = Option::from(Rect {\n                                left: 0,\n                                top: 0,\n                                right: resize_ref.left,\n                                bottom: 0,\n                            });\n                        }\n                    }\n                }\n\n                if let Some(rr) = resize_adjustments[i].as_mut() {\n                    rr.left = 0;\n                }\n            }\n\n            if resize_ref.top != 0 {\n                let range = if i == 1 {\n                    0..1\n                } else if i & 1 == 0 {\n                    i - 1..i\n                } else {\n                    i - 2..i\n                };\n\n                for n in range {\n                    let should_adjust = n % 2 != 0;\n                    if should_adjust {\n                        if let Some(Some(adjacent_resize)) = resize_adjustments.get_mut(n) {\n                            adjacent_resize.bottom += resize_ref.top;\n                        } else {\n                            resize_adjustments[n] = Option::from(Rect {\n                                left: 0,\n                                top: 0,\n                                right: 0,\n                                bottom: resize_ref.top,\n                            });\n                        }\n                    }\n                }\n\n                if let Some(Some(resize)) = resize_adjustments.get_mut(i) {\n                    resize.top = 0;\n                }\n            }\n        }\n    }\n\n    let cleaned_resize_adjustments: Vec<_> = resize_adjustments\n        .iter()\n        .map(|adjustment| match adjustment {\n            None => None,\n            Some(rect) if rect.eq(&Rect::default()) => None,\n            Some(_) => *adjustment,\n        })\n        .collect();\n\n    cleaned_resize_adjustments\n}\n\n#[allow(clippy::only_used_in_recursion)]\nfn recursive_fibonacci(\n    idx: usize,\n    count: usize,\n    area: &Rect,\n    layout_flip: Option<Axis>,\n    resize_adjustments: Vec<Option<Rect>>,\n    column_split_ratio: f32,\n    row_split_ratio: f32,\n) -> Vec<Rect> {\n    let mut a = *area;\n\n    let resized = if let Some(Some(r)) = resize_adjustments.get(idx) {\n        a.left += r.left;\n        a.top += r.top;\n        a.right += r.right;\n        a.bottom += r.bottom;\n        a\n    } else {\n        *area\n    };\n\n    #[allow(clippy::cast_possible_truncation)]\n    let primary_resized_width = (resized.right as f32 * column_split_ratio) as i32;\n    #[allow(clippy::cast_possible_truncation)]\n    let primary_resized_height = (resized.bottom as f32 * row_split_ratio) as i32;\n\n    let (main_x, alt_x, alt_y, main_y);\n\n    if let Some(flip) = layout_flip {\n        match flip {\n            Axis::Horizontal => {\n                main_x = resized.left + (area.right - primary_resized_width);\n                alt_x = resized.left;\n\n                alt_y = resized.top + primary_resized_height;\n                main_y = resized.top;\n            }\n            Axis::Vertical => {\n                main_y = resized.top + (area.bottom - primary_resized_height);\n                alt_y = resized.top;\n\n                main_x = resized.left;\n                alt_x = resized.left + primary_resized_width;\n            }\n            Axis::HorizontalAndVertical => {\n                main_x = resized.left + (area.right - primary_resized_width);\n                alt_x = resized.left;\n                main_y = resized.top + (area.bottom - primary_resized_height);\n                alt_y = resized.top;\n            }\n        }\n    } else {\n        main_x = resized.left;\n        alt_x = resized.left + primary_resized_width;\n        main_y = resized.top;\n        alt_y = resized.top + primary_resized_height;\n    }\n\n    #[allow(clippy::if_not_else)]\n    if count == 0 {\n        vec![]\n    } else if count == 1 {\n        vec![Rect {\n            left: resized.left,\n            top: resized.top,\n            right: resized.right,\n            bottom: resized.bottom,\n        }]\n    } else if !idx.is_multiple_of(2) {\n        let mut res = vec![Rect {\n            left: resized.left,\n            top: main_y,\n            right: resized.right,\n            bottom: primary_resized_height,\n        }];\n        res.append(&mut recursive_fibonacci(\n            idx + 1,\n            count - 1,\n            &Rect {\n                left: area.left,\n                top: alt_y,\n                right: area.right,\n                bottom: area.bottom - primary_resized_height,\n            },\n            layout_flip,\n            resize_adjustments,\n            column_split_ratio,\n            row_split_ratio,\n        ));\n        res\n    } else {\n        let mut res = vec![Rect {\n            left: main_x,\n            top: resized.top,\n            right: primary_resized_width,\n            bottom: resized.bottom,\n        }];\n        res.append(&mut recursive_fibonacci(\n            idx + 1,\n            count - 1,\n            &Rect {\n                left: alt_x,\n                top: area.top,\n                right: area.right - primary_resized_width,\n                bottom: area.bottom,\n            },\n            layout_flip,\n            resize_adjustments,\n            column_split_ratio,\n            row_split_ratio,\n        ));\n        res\n    }\n}\n\nfn calculate_columns_adjustment(resize_dimensions: &[Option<Rect>]) -> Vec<Rect> {\n    let len = resize_dimensions.len();\n    let mut result = vec![Rect::default(); len];\n    match len {\n        0 | 1 => (),\n        _ => {\n            for (i, rect) in resize_dimensions.iter().enumerate() {\n                if let Some(rect) = rect {\n                    if i != 0 {\n                        resize_right(&mut result[i - 1], rect.left);\n                        resize_left(&mut result[i], rect.left);\n                    }\n\n                    if i != len - 1 {\n                        resize_right(&mut result[i], rect.right);\n                        resize_left(&mut result[i + 1], rect.right);\n                    }\n                }\n            }\n        }\n    };\n\n    result\n}\n\nfn calculate_rows_adjustment(resize_dimensions: &[Option<Rect>]) -> Vec<Rect> {\n    let len = resize_dimensions.len();\n    let mut result = vec![Rect::default(); len];\n    match len {\n        0 | 1 => (),\n        _ => {\n            for (i, rect) in resize_dimensions.iter().enumerate() {\n                if let Some(rect) = rect {\n                    if i != 0 {\n                        resize_bottom(&mut result[i - 1], rect.top);\n                        resize_top(&mut result[i], rect.top);\n                    }\n\n                    if i != len - 1 {\n                        resize_bottom(&mut result[i], rect.bottom);\n                        resize_top(&mut result[i + 1], rect.bottom);\n                    }\n                }\n            }\n        }\n    };\n\n    result\n}\n\nfn calculate_vertical_stack_adjustment(resize_dimensions: &[Option<Rect>]) -> Vec<Rect> {\n    let len = resize_dimensions.len();\n    let mut result = vec![Rect::default(); len];\n    match len {\n        // One container can't be resized\n        0 | 1 => (),\n        _ => {\n            let (master, stack) = result.split_at_mut(1);\n            let primary = &mut master[0];\n\n            if let Some(resize) = resize_dimensions[0] {\n                resize_right(primary, resize.right);\n                for s in &mut *stack {\n                    resize_left(s, resize.right);\n                }\n            }\n\n            // Handle stack on the right\n            for (i, rect) in resize_dimensions[1..].iter().enumerate() {\n                if let Some(rect) = rect {\n                    resize_right(primary, rect.left);\n                    stack\n                        .iter_mut()\n                        .for_each(|vertical_element| resize_left(vertical_element, rect.left));\n\n                    // Containers in stack except first can be resized up displacing container\n                    // above them\n                    if i != 0 {\n                        resize_bottom(&mut stack[i - 1], rect.top);\n                        resize_top(&mut stack[i], rect.top);\n                    }\n\n                    // Containers in stack except last can be resized down displacing container\n                    // below them\n                    if i != stack.len() - 1 {\n                        resize_bottom(&mut stack[i], rect.bottom);\n                        resize_top(&mut stack[i + 1], rect.bottom);\n                    }\n                }\n            }\n        }\n    };\n\n    result\n}\n\nfn calculate_right_vertical_stack_adjustment(resize_dimensions: &[Option<Rect>]) -> Vec<Rect> {\n    let len = resize_dimensions.len();\n    let mut result = vec![Rect::default(); len];\n    match len {\n        // One container can't be resized\n        0 | 1 => (),\n        _ => {\n            let (master, stack) = result.split_at_mut(1);\n            let primary = &mut master[0];\n\n            if let Some(resize) = resize_dimensions[0] {\n                resize_left(primary, resize.left);\n                for s in &mut *stack {\n                    resize_right(s, resize.left);\n                }\n            }\n\n            // Handle stack on the left\n            for (i, rect) in resize_dimensions[1..].iter().enumerate() {\n                if let Some(rect) = rect {\n                    resize_left(primary, rect.right);\n                    stack\n                        .iter_mut()\n                        .for_each(|vertical_element| resize_right(vertical_element, rect.right));\n\n                    // Containers in stack except first can be resized up displacing container\n                    // above them\n                    if i != 0 {\n                        resize_bottom(&mut stack[i - 1], rect.top);\n                        resize_top(&mut stack[i], rect.top);\n                    }\n\n                    // Containers in stack except last can be resized down displacing container\n                    // below them\n                    if i != stack.len() - 1 {\n                        resize_bottom(&mut stack[i], rect.bottom);\n                        resize_top(&mut stack[i + 1], rect.bottom);\n                    }\n                }\n            }\n        }\n    };\n\n    result\n}\n\nfn calculate_horizontal_stack_adjustment(resize_dimensions: &[Option<Rect>]) -> Vec<Rect> {\n    let len = resize_dimensions.len();\n    let mut result = vec![Rect::default(); len];\n    match len {\n        0 | 1 => (),\n        _ => {\n            let (primary, rest) = result.split_at_mut(1);\n            let primary = &mut primary[0];\n            if let Some(resize_primary) = resize_dimensions[0] {\n                resize_bottom(primary, resize_primary.bottom);\n\n                for horizontal_element in &mut *rest {\n                    resize_top(horizontal_element, resize_primary.bottom);\n                }\n            }\n\n            for (i, rect) in resize_dimensions[1..].iter().enumerate() {\n                if let Some(rect) = rect {\n                    resize_bottom(primary, rect.top);\n                    rest.iter_mut()\n                        .for_each(|vertical_element| resize_top(vertical_element, rect.top));\n\n                    if i != 0 {\n                        resize_right(&mut rest[i - 1], rect.left);\n                        resize_left(&mut rest[i], rect.left);\n                    }\n\n                    if i != rest.len() - 1 {\n                        resize_right(&mut rest[i], rect.right);\n                        resize_left(&mut rest[i + 1], rect.right);\n                    }\n                }\n            }\n        }\n    };\n\n    result\n}\n\nfn calculate_ultrawide_adjustment(resize_dimensions: &[Option<Rect>]) -> Vec<Rect> {\n    let len = resize_dimensions.len();\n    let mut result = vec![Rect::default(); len];\n    match len {\n        // One container can't be resized\n        0 | 1 => (),\n        2 => {\n            let (primary, secondary) = result.split_at_mut(1);\n            let primary = &mut primary[0];\n            let secondary = &mut secondary[0];\n            // With two containers on screen container 0 is on the right\n            if let Some(resize_primary) = resize_dimensions[0] {\n                resize_left(primary, resize_primary.left);\n                resize_right(secondary, resize_primary.left);\n            }\n\n            if let Some(resize_secondary) = resize_dimensions[1] {\n                resize_left(primary, resize_secondary.right);\n                resize_right(secondary, resize_secondary.right);\n            }\n        }\n        _ => {\n            let (primary, rest) = result.split_at_mut(1);\n            let (secondary, tertiary) = rest.split_at_mut(1);\n            let primary = &mut primary[0];\n            let secondary = &mut secondary[0];\n            // With three or more containers container 0 is in the center\n            if let Some(resize_primary) = resize_dimensions[0] {\n                resize_left(primary, resize_primary.left);\n                resize_right(primary, resize_primary.right);\n\n                resize_right(secondary, resize_primary.left);\n\n                for vertical_element in &mut *tertiary {\n                    resize_left(vertical_element, resize_primary.right);\n                }\n            }\n\n            // Container 1 is on the left\n            if let Some(resize_secondary) = resize_dimensions[1] {\n                resize_left(primary, resize_secondary.right);\n                resize_right(secondary, resize_secondary.right);\n            }\n\n            // Handle stack on the right\n            for (i, rect) in resize_dimensions[2..].iter().enumerate() {\n                if let Some(rect) = rect {\n                    resize_right(primary, rect.left);\n                    tertiary\n                        .iter_mut()\n                        .for_each(|vertical_element| resize_left(vertical_element, rect.left));\n\n                    // Containers in stack except first can be resized up displacing container\n                    // above them\n                    if i != 0 {\n                        resize_bottom(&mut tertiary[i - 1], rect.top);\n                        resize_top(&mut tertiary[i], rect.top);\n                    }\n\n                    // Containers in stack except last can be resized down displacing container\n                    // below them\n                    if i != tertiary.len() - 1 {\n                        resize_bottom(&mut tertiary[i], rect.bottom);\n                        resize_top(&mut tertiary[i + 1], rect.bottom);\n                    }\n                }\n            }\n        }\n    };\n\n    result\n}\n\nfn calculate_scrolling_adjustment(resize_dimensions: &[Option<Rect>]) -> Vec<Rect> {\n    let len = resize_dimensions.len();\n    let mut result = vec![Rect::default(); len];\n\n    if len <= 1 {\n        return result;\n    }\n\n    for (i, rect) in resize_dimensions.iter().enumerate() {\n        if let Some(rect) = rect {\n            let is_leftmost = i == 0;\n            let is_rightmost = i == len - 1;\n\n            resize_left(&mut result[i], rect.left);\n            resize_right(&mut result[i], rect.right);\n            resize_top(&mut result[i], rect.top);\n            resize_bottom(&mut result[i], rect.bottom);\n\n            if !is_leftmost && rect.left != 0 {\n                resize_right(&mut result[i - 1], rect.left);\n            }\n\n            if !is_rightmost && rect.right != 0 {\n                resize_left(&mut result[i + 1], rect.right);\n            }\n        }\n    }\n\n    result\n}\n\nfn resize_left(rect: &mut Rect, resize: i32) {\n    rect.left += resize / 2;\n    rect.right += -resize / 2;\n}\n\nfn resize_right(rect: &mut Rect, resize: i32) {\n    rect.right += resize / 2;\n}\n\nfn resize_top(rect: &mut Rect, resize: i32) {\n    rect.top += resize / 2;\n    rect.bottom += -resize / 2;\n}\n\nfn resize_bottom(rect: &mut Rect, resize: i32) {\n    rect.bottom += resize / 2;\n}\n\n#[cfg(test)]\n#[path = \"arrangement_tests.rs\"]\nmod tests;\n"
  },
  {
    "path": "komorebi-layouts/src/arrangement_tests.rs",
    "content": "use super::*;\nuse std::num::NonZeroUsize;\n\n// Helper to create a test area\nfn test_area() -> Rect {\n    Rect {\n        left: 0,\n        top: 0,\n        right: 1000,\n        bottom: 800,\n    }\n}\n\n// Helper to create LayoutOptions with column ratios\nfn layout_options_with_column_ratios(ratios: &[f32]) -> LayoutOptions {\n    let mut arr = [None; MAX_RATIOS];\n    for (i, &r) in ratios.iter().take(MAX_RATIOS).enumerate() {\n        arr[i] = Some(r);\n    }\n    LayoutOptions {\n        scrolling: None,\n        grid: None,\n        column_ratios: Some(arr),\n        row_ratios: None,\n    }\n}\n\n// Helper to create LayoutOptions with row ratios\nfn layout_options_with_row_ratios(ratios: &[f32]) -> LayoutOptions {\n    let mut arr = [None; MAX_RATIOS];\n    for (i, &r) in ratios.iter().take(MAX_RATIOS).enumerate() {\n        arr[i] = Some(r);\n    }\n    LayoutOptions {\n        scrolling: None,\n        grid: None,\n        column_ratios: None,\n        row_ratios: Some(arr),\n    }\n}\n\n// Helper to create LayoutOptions with both column and row ratios\nfn layout_options_with_ratios(column_ratios: &[f32], row_ratios: &[f32]) -> LayoutOptions {\n    let mut col_arr = [None; MAX_RATIOS];\n    for (i, &r) in column_ratios.iter().take(MAX_RATIOS).enumerate() {\n        col_arr[i] = Some(r);\n    }\n    let mut row_arr = [None; MAX_RATIOS];\n    for (i, &r) in row_ratios.iter().take(MAX_RATIOS).enumerate() {\n        row_arr[i] = Some(r);\n    }\n    LayoutOptions {\n        scrolling: None,\n        grid: None,\n        column_ratios: Some(col_arr),\n        row_ratios: Some(row_arr),\n    }\n}\n\nfn assert_containers_adjacent_horizontally(layouts: &[Rect], area: &Rect) {\n    let mut sorted: Vec<&Rect> = layouts.iter().collect();\n    sorted.sort_by_key(|r| r.left);\n\n    for pair in sorted.windows(2) {\n        assert_eq!(\n            pair[0].left + pair[0].right,\n            pair[1].left,\n            \"gap between containers at left={} (width {}) and left={}\",\n            pair[0].left,\n            pair[0].right,\n            pair[1].left,\n        );\n    }\n\n    let rightmost = sorted.last().unwrap();\n    assert_eq!(\n        rightmost.left + rightmost.right,\n        area.left + area.right,\n        \"rightmost container does not reach the area edge\",\n    );\n}\n\nfn assert_containers_adjacent_vertically(layouts: &[Rect], area: &Rect) {\n    let mut sorted: Vec<&Rect> = layouts.iter().collect();\n    sorted.sort_by_key(|r| r.top);\n\n    for pair in sorted.windows(2) {\n        assert_eq!(\n            pair[0].top + pair[0].bottom,\n            pair[1].top,\n            \"gap between containers at top={} (height {}) and top={}\",\n            pair[0].top,\n            pair[0].bottom,\n            pair[1].top,\n        );\n    }\n\n    let bottommost = sorted.last().unwrap();\n    assert_eq!(\n        bottommost.top + bottommost.bottom,\n        area.top + area.bottom,\n        \"bottommost container does not reach the area edge\",\n    );\n}\n\nmod columns_with_ratios_tests {\n    use super::*;\n\n    #[test]\n    fn test_columns_equal_width_no_ratios() {\n        let area = test_area();\n        let layouts = columns_with_ratios(&area, 4, None);\n\n        assert_eq!(layouts.len(), 4);\n        // Each column should be 250 pixels wide (1000 / 4)\n        for layout in &layouts {\n            assert_eq!(layout.right, 250);\n            assert_eq!(layout.bottom, 800);\n        }\n    }\n\n    #[test]\n    fn test_columns_with_single_ratio() {\n        let area = test_area();\n        let opts = layout_options_with_column_ratios(&[0.3]);\n        let layouts = columns_with_ratios(&area, 3, opts.column_ratios);\n\n        assert_eq!(layouts.len(), 3);\n        // First column: 30% of 1000 = 300\n        assert_eq!(layouts[0].right, 300);\n        // Remaining 700 split between 2 columns = 350 each\n        assert_eq!(layouts[1].right, 350);\n        assert_eq!(layouts[2].right, 350);\n    }\n\n    #[test]\n    fn test_columns_with_multiple_ratios() {\n        let area = test_area();\n        let opts = layout_options_with_column_ratios(&[0.2, 0.3, 0.5]);\n        let layouts = columns_with_ratios(&area, 4, opts.column_ratios);\n\n        assert_eq!(layouts.len(), 4);\n        // First column: 20% of 1000 = 200\n        assert_eq!(layouts[0].right, 200);\n        // Second column: 30% of 1000 = 300\n        assert_eq!(layouts[1].right, 300);\n        // Third column: 50% of 1000 = 500\n        // But wait - cumulative is 1.0, so third might be truncated\n        // Let's check what actually happens\n        // Actually, the sum 0.2 + 0.3 = 0.5 < 1.0, and 0.5 + 0.5 = 1.0\n        // So 0.5 won't be included because cumulative would reach 1.0\n    }\n\n    #[test]\n    fn test_columns_positions_are_correct() {\n        let area = test_area();\n        let opts = layout_options_with_column_ratios(&[0.3, 0.4]);\n        let layouts = columns_with_ratios(&area, 3, opts.column_ratios);\n\n        // First column starts at 0\n        assert_eq!(layouts[0].left, 0);\n        // Second column starts where first ends\n        assert_eq!(layouts[1].left, layouts[0].right);\n        // Third column starts where second ends\n        assert_eq!(layouts[2].left, layouts[1].left + layouts[1].right);\n    }\n\n    #[test]\n    fn test_columns_last_column_gets_remaining_space() {\n        let area = test_area();\n        let opts = layout_options_with_column_ratios(&[0.3]);\n        let layouts = columns_with_ratios(&area, 2, opts.column_ratios);\n\n        assert_eq!(layouts.len(), 2);\n        // First column: 30% = 300\n        assert_eq!(layouts[0].right, 300);\n        // Last column gets remaining space: 700\n        assert_eq!(layouts[1].right, 700);\n    }\n\n    #[test]\n    fn test_columns_single_column() {\n        let area = test_area();\n        let opts = layout_options_with_column_ratios(&[0.5]);\n        let layouts = columns_with_ratios(&area, 1, opts.column_ratios);\n\n        assert_eq!(layouts.len(), 1);\n        // Single column takes full width regardless of ratio\n        assert_eq!(layouts[0].right, 1000);\n    }\n\n    #[test]\n    fn test_columns_more_columns_than_ratios() {\n        let area = test_area();\n        let opts = layout_options_with_column_ratios(&[0.2]);\n        let layouts = columns_with_ratios(&area, 5, opts.column_ratios);\n\n        assert_eq!(layouts.len(), 5);\n        // First column: 20% = 200\n        assert_eq!(layouts[0].right, 200);\n        // Remaining 800 split among 4 columns = 200 each\n        for i in 1..5 {\n            assert_eq!(layouts[i].right, 200);\n        }\n    }\n\n    #[test]\n    fn test_columns_cover_full_width_no_ratios() {\n        // 1000 / 3 = 333, 333*3 = 999 => 1px remainder\n        let area = test_area();\n        let layouts = columns_with_ratios(&area, 3, None);\n\n        let total_width: i32 = layouts.iter().map(|r| r.right).sum();\n        assert_eq!(\n            total_width, area.right,\n            \"columns should cover full width, got {total_width} expected {}\",\n            area.right,\n        );\n\n        let last = layouts.last().unwrap();\n        let right_edge = last.left + last.right;\n        assert_eq!(right_edge, area.left + area.right);\n    }\n\n    #[test]\n    fn test_columns_cover_full_width_with_ratios() {\n        // ratio=0.3 with 4 columns: col0=300, remaining 700/3=233, 233*3=699 => 1px remainder\n        let area = test_area();\n        let opts = layout_options_with_column_ratios(&[0.3]);\n        let layouts = columns_with_ratios(&area, 4, opts.column_ratios);\n\n        let total_width: i32 = layouts.iter().map(|r| r.right).sum();\n        assert_eq!(\n            total_width, area.right,\n            \"columns should cover full width, got {total_width} expected {}\",\n            area.right,\n        );\n\n        let last = layouts.last().unwrap();\n        let right_edge = last.left + last.right;\n        assert_eq!(right_edge, area.left + area.right);\n    }\n}\n\nmod rows_with_ratios_tests {\n    use super::*;\n\n    #[test]\n    fn test_rows_equal_height_no_ratios() {\n        let area = test_area();\n        let layouts = rows_with_ratios(&area, 4, None);\n\n        assert_eq!(layouts.len(), 4);\n        // Each row should be 200 pixels tall (800 / 4)\n        for layout in &layouts {\n            assert_eq!(layout.bottom, 200);\n            assert_eq!(layout.right, 1000);\n        }\n    }\n\n    #[test]\n    fn test_rows_with_single_ratio() {\n        let area = test_area();\n        let opts = layout_options_with_row_ratios(&[0.5]);\n        let layouts = rows_with_ratios(&area, 3, opts.row_ratios);\n\n        assert_eq!(layouts.len(), 3);\n        // First row: 50% of 800 = 400\n        assert_eq!(layouts[0].bottom, 400);\n        // Remaining 400 split between 2 rows = 200 each\n        assert_eq!(layouts[1].bottom, 200);\n        assert_eq!(layouts[2].bottom, 200);\n    }\n\n    #[test]\n    fn test_rows_positions_are_correct() {\n        let area = test_area();\n        let opts = layout_options_with_row_ratios(&[0.25, 0.25]);\n        let layouts = rows_with_ratios(&area, 3, opts.row_ratios);\n\n        // First row starts at top\n        assert_eq!(layouts[0].top, 0);\n        // Second row starts where first ends\n        assert_eq!(layouts[1].top, layouts[0].bottom);\n        // Third row starts where second ends\n        assert_eq!(layouts[2].top, layouts[1].top + layouts[1].bottom);\n    }\n\n    #[test]\n    fn test_rows_last_row_gets_remaining_space() {\n        let area = test_area();\n        let opts = layout_options_with_row_ratios(&[0.25]);\n        let layouts = rows_with_ratios(&area, 2, opts.row_ratios);\n\n        assert_eq!(layouts.len(), 2);\n        // First row: 25% of 800 = 200\n        assert_eq!(layouts[0].bottom, 200);\n        // Last row gets remaining: 600\n        assert_eq!(layouts[1].bottom, 600);\n    }\n\n    #[test]\n    fn test_rows_cover_full_height_no_ratios() {\n        // 800 / 3 = 266, 266*3 = 798 => 2px remainder\n        let area = test_area();\n        let layouts = rows_with_ratios(&area, 3, None);\n\n        let total_height: i32 = layouts.iter().map(|r| r.bottom).sum();\n        assert_eq!(\n            total_height, area.bottom,\n            \"rows should cover full height, got {total_height} expected {}\",\n            area.bottom,\n        );\n\n        let last = layouts.last().unwrap();\n        let bottom_edge = last.top + last.bottom;\n        assert_eq!(bottom_edge, area.top + area.bottom);\n    }\n\n    #[test]\n    fn test_rows_cover_full_height_with_ratios() {\n        // ratio=0.3 with 4 rows: row0=240, remaining 560/3=186, 186*3=558 => 2px remainder\n        let area = test_area();\n        let opts = layout_options_with_row_ratios(&[0.3]);\n        let layouts = rows_with_ratios(&area, 4, opts.row_ratios);\n\n        let total_height: i32 = layouts.iter().map(|r| r.bottom).sum();\n        assert_eq!(\n            total_height, area.bottom,\n            \"rows should cover full height, got {total_height} expected {}\",\n            area.bottom,\n        );\n\n        let last = layouts.last().unwrap();\n        let bottom_edge = last.top + last.bottom;\n        assert_eq!(bottom_edge, area.top + area.bottom);\n    }\n}\n\nmod vertical_stack_layout_tests {\n    use super::*;\n\n    #[test]\n    fn test_vertical_stack_default_ratio() {\n        let area = test_area();\n        let len = NonZeroUsize::new(3).unwrap();\n        let layouts =\n            DefaultLayout::VerticalStack.calculate(&area, len, None, None, &[], 0, None, &[]);\n\n        assert_eq!(layouts.len(), 3);\n        // Primary column should be 50% (default ratio)\n        assert_eq!(layouts[0].right, 500);\n    }\n\n    #[test]\n    fn test_vertical_stack_custom_ratio() {\n        let area = test_area();\n        let len = NonZeroUsize::new(3).unwrap();\n        let opts = layout_options_with_column_ratios(&[0.7]);\n        let layouts =\n            DefaultLayout::VerticalStack.calculate(&area, len, None, None, &[], 0, Some(opts), &[]);\n\n        assert_eq!(layouts.len(), 3);\n        // Primary column should be 70%\n        assert_eq!(layouts[0].right, 700);\n        // Stack columns should share remaining 30%\n        assert_eq!(layouts[1].right, 300);\n        assert_eq!(layouts[2].right, 300);\n    }\n\n    #[test]\n    fn test_vertical_stack_with_row_ratios() {\n        let area = test_area();\n        let len = NonZeroUsize::new(4).unwrap();\n        let opts = layout_options_with_ratios(&[0.6], &[0.5, 0.3]);\n        let layouts =\n            DefaultLayout::VerticalStack.calculate(&area, len, None, None, &[], 0, Some(opts), &[]);\n\n        assert_eq!(layouts.len(), 4);\n        // Primary column: 60%\n        assert_eq!(layouts[0].right, 600);\n        // Stack rows should use row_ratios\n        // First stack row: 50% of 800 = 400\n        assert_eq!(layouts[1].bottom, 400);\n        // Second stack row: 30% of 800 = 240\n        assert_eq!(layouts[2].bottom, 240);\n    }\n\n    #[test]\n    fn test_vertical_stack_single_window() {\n        let area = test_area();\n        let len = NonZeroUsize::new(1).unwrap();\n        let opts = layout_options_with_column_ratios(&[0.6]);\n        let layouts =\n            DefaultLayout::VerticalStack.calculate(&area, len, None, None, &[], 0, Some(opts), &[]);\n\n        assert_eq!(layouts.len(), 1);\n        // Single window should take full width\n        assert_eq!(layouts[0].right, 1000);\n    }\n}\n\nmod horizontal_stack_layout_tests {\n    use super::*;\n\n    #[test]\n    fn test_horizontal_stack_default_ratio() {\n        let area = test_area();\n        let len = NonZeroUsize::new(3).unwrap();\n        let layouts =\n            DefaultLayout::HorizontalStack.calculate(&area, len, None, None, &[], 0, None, &[]);\n\n        assert_eq!(layouts.len(), 3);\n        // Primary row should be 50% height (default ratio)\n        assert_eq!(layouts[0].bottom, 400);\n    }\n\n    #[test]\n    fn test_horizontal_stack_custom_ratio() {\n        let area = test_area();\n        let len = NonZeroUsize::new(3).unwrap();\n        let opts = layout_options_with_row_ratios(&[0.7]);\n        let layouts = DefaultLayout::HorizontalStack.calculate(\n            &area,\n            len,\n            None,\n            None,\n            &[],\n            0,\n            Some(opts),\n            &[],\n        );\n\n        assert_eq!(layouts.len(), 3);\n        // Primary row should be 70% height\n        assert_eq!(layouts[0].bottom, 560);\n    }\n\n    #[test]\n    fn test_horizontal_stack_columns_cover_full_width() {\n        // 4 windows: primary row + 3 stack columns\n        // stack width = 1000, 1000/3 = 333, 333*3 = 999 => 1px gap\n        let area = test_area();\n        let len = NonZeroUsize::new(4).unwrap();\n        let layouts =\n            DefaultLayout::HorizontalStack.calculate(&area, len, None, None, &[], 0, None, &[]);\n\n        // Stack windows (indices 1..4) share the bottom row\n        let stack = &layouts[1..];\n        let last = stack.last().unwrap();\n        let right_edge = last.left + last.right;\n        assert_eq!(\n            right_edge,\n            area.left + area.right,\n            \"stack columns should cover full width, right edge is {right_edge} expected {}\",\n            area.left + area.right,\n        );\n    }\n}\n\nmod vertical_stack_rows_cover_full_height_tests {\n    use super::*;\n\n    #[test]\n    fn test_vertical_stack_rows_cover_full_height() {\n        // 4 windows: primary column + 3 stack rows\n        // stack height = 800, 800/3 = 266, 266*3 = 798 => 2px gap\n        let area = test_area();\n        let len = NonZeroUsize::new(4).unwrap();\n        let layouts =\n            DefaultLayout::VerticalStack.calculate(&area, len, None, None, &[], 0, None, &[]);\n\n        // Stack windows (indices 1..4) share the right column\n        let stack = &layouts[1..];\n        let last = stack.last().unwrap();\n        let bottom_edge = last.top + last.bottom;\n        assert_eq!(\n            bottom_edge,\n            area.top + area.bottom,\n            \"stack rows should cover full height, bottom edge is {bottom_edge} expected {}\",\n            area.top + area.bottom,\n        );\n    }\n}\n\nmod scrolling_layout_tests {\n    use super::*;\n\n    #[test]\n    fn test_scrolling_visible_columns_cover_full_width() {\n        // 1921 / 3 = 640, 640*3 = 1920 => 1px gap\n        let area = Rect {\n            left: 0,\n            top: 0,\n            right: 1921,\n            bottom: 800,\n        };\n        let len = NonZeroUsize::new(5).unwrap();\n        let opts = LayoutOptions {\n            scrolling: Some(crate::ScrollingLayoutOptions {\n                columns: 3,\n                center_focused_column: None,\n            }),\n            grid: None,\n            column_ratios: None,\n            row_ratios: None,\n        };\n        let layouts =\n            DefaultLayout::Scrolling.calculate(&area, len, None, None, &[], 0, Some(opts), &[]);\n\n        // First 3 windows should be visible (focused_idx=0)\n        let visible = &layouts[0..3];\n        let last_visible = visible.last().unwrap();\n        let right_edge = last_visible.left + last_visible.right;\n        assert_eq!(\n            right_edge,\n            area.left + area.right,\n            \"visible columns should cover full width, right edge is {right_edge} expected {}\",\n            area.left + area.right,\n        );\n    }\n}\n\nmod ultrawide_layout_tests {\n    use super::*;\n\n    #[test]\n    fn test_ultrawide_default_ratios() {\n        let area = test_area();\n        let len = NonZeroUsize::new(3).unwrap();\n        let layouts = DefaultLayout::UltrawideVerticalStack.calculate(\n            &area,\n            len,\n            None,\n            None,\n            &[],\n            0,\n            None,\n            &[],\n        );\n\n        assert_eq!(layouts.len(), 3);\n        // Primary (center): 50% = 500\n        assert_eq!(layouts[0].right, 500);\n        // Secondary (left): 25% = 250\n        assert_eq!(layouts[1].right, 250);\n        // Tertiary gets remaining: 250\n        assert_eq!(layouts[2].right, 250);\n    }\n\n    #[test]\n    fn test_ultrawide_custom_ratios() {\n        let area = test_area();\n        let len = NonZeroUsize::new(4).unwrap();\n        let opts = layout_options_with_column_ratios(&[0.5, 0.2]);\n        let layouts = DefaultLayout::UltrawideVerticalStack.calculate(\n            &area,\n            len,\n            None,\n            None,\n            &[],\n            0,\n            Some(opts),\n            &[],\n        );\n\n        assert_eq!(layouts.len(), 4);\n        // Primary (center): 50% = 500\n        assert_eq!(layouts[0].right, 500);\n        // Secondary (left): 20% = 200\n        assert_eq!(layouts[1].right, 200);\n        // Tertiary column gets remaining: 300\n        assert_eq!(layouts[2].right, 300);\n        assert_eq!(layouts[3].right, 300);\n    }\n\n    #[test]\n    fn test_ultrawide_two_windows() {\n        let area = test_area();\n        let len = NonZeroUsize::new(2).unwrap();\n        let opts = layout_options_with_column_ratios(&[0.6]);\n        let layouts = DefaultLayout::UltrawideVerticalStack.calculate(\n            &area,\n            len,\n            None,\n            None,\n            &[],\n            0,\n            Some(opts),\n            &[],\n        );\n\n        assert_eq!(layouts.len(), 2);\n        // Primary: 60% = 600\n        assert_eq!(layouts[0].right, 600);\n        // Secondary gets remaining: 400\n        assert_eq!(layouts[1].right, 400);\n    }\n}\n\nmod bsp_layout_tests {\n    use super::*;\n\n    #[test]\n    fn test_bsp_default_ratio() {\n        let area = test_area();\n        let len = NonZeroUsize::new(2).unwrap();\n        let layouts = DefaultLayout::BSP.calculate(&area, len, None, None, &[], 0, None, &[]);\n\n        assert_eq!(layouts.len(), 2);\n        // First window should be 50% width\n        assert_eq!(layouts[0].right, 500);\n    }\n\n    #[test]\n    fn test_bsp_custom_column_ratio() {\n        let area = test_area();\n        let len = NonZeroUsize::new(2).unwrap();\n        let opts = layout_options_with_column_ratios(&[0.7]);\n        let layouts = DefaultLayout::BSP.calculate(&area, len, None, None, &[], 0, Some(opts), &[]);\n\n        assert_eq!(layouts.len(), 2);\n        // First window should be 70% width\n        assert_eq!(layouts[0].right, 700);\n    }\n\n    #[test]\n    fn test_bsp_custom_row_ratio() {\n        let area = test_area();\n        let len = NonZeroUsize::new(3).unwrap();\n        let opts = layout_options_with_ratios(&[0.5], &[0.7]);\n        let layouts = DefaultLayout::BSP.calculate(&area, len, None, None, &[], 0, Some(opts), &[]);\n\n        assert_eq!(layouts.len(), 3);\n        // Second window should be 70% of remaining height\n        assert_eq!(layouts[1].bottom, 560);\n    }\n\n    #[test]\n    fn test_bsp_horizontal_flip_no_gap_with_resize() {\n        let area = test_area();\n        let len = NonZeroUsize::new(2).unwrap();\n        let opts = layout_options_with_column_ratios(&[0.7]);\n\n        // Container 0 resized right by 50\n        let resize = [\n            Some(Rect {\n                left: 0,\n                top: 0,\n                right: 50,\n                bottom: 0,\n            }),\n            None,\n        ];\n\n        let layouts = DefaultLayout::BSP.calculate(\n            &area,\n            len,\n            None,\n            Some(Axis::Horizontal),\n            &resize,\n            0,\n            Some(opts),\n            &[],\n        );\n\n        assert_eq!(layouts.len(), 2);\n        assert_containers_adjacent_horizontally(&layouts, &area);\n    }\n\n    #[test]\n    fn test_bsp_vertical_flip_no_gap_with_resize() {\n        let area = test_area();\n        let len = NonZeroUsize::new(3).unwrap();\n        let opts = layout_options_with_ratios(&[0.5], &[0.7]);\n\n        // Container 1 resized bottom by 50\n        let resize = [\n            None,\n            Some(Rect {\n                left: 0,\n                top: 0,\n                right: 0,\n                bottom: 50,\n            }),\n            None,\n        ];\n\n        let layouts = DefaultLayout::BSP.calculate(\n            &area,\n            len,\n            None,\n            Some(Axis::Vertical),\n            &resize,\n            0,\n            Some(opts),\n            &[],\n        );\n\n        assert_eq!(layouts.len(), 3);\n        // Containers 1 and 2 share the right column vertically\n        assert_containers_adjacent_vertically(\n            &layouts[1..],\n            &Rect {\n                left: layouts[1].left,\n                top: area.top,\n                right: layouts[1].right,\n                bottom: area.bottom,\n            },\n        );\n    }\n\n    #[test]\n    fn test_bsp_horizontal_and_vertical_flip_no_gap_with_resize() {\n        let area = test_area();\n        let len = NonZeroUsize::new(3).unwrap();\n        let opts = layout_options_with_ratios(&[0.7], &[0.7]);\n\n        // Both containers resized\n        let resize = [\n            Some(Rect {\n                left: 0,\n                top: 0,\n                right: 50,\n                bottom: 0,\n            }),\n            Some(Rect {\n                left: 0,\n                top: 0,\n                right: 0,\n                bottom: 40,\n            }),\n            None,\n        ];\n\n        let layouts = DefaultLayout::BSP.calculate(\n            &area,\n            len,\n            None,\n            Some(Axis::HorizontalAndVertical),\n            &resize,\n            0,\n            Some(opts),\n            &[],\n        );\n\n        assert_eq!(layouts.len(), 3);\n        assert_containers_adjacent_horizontally(&[layouts[0], layouts[1]], &area);\n        assert_containers_adjacent_vertically(\n            &layouts[1..],\n            &Rect {\n                left: layouts[1].left,\n                top: area.top,\n                right: layouts[1].right,\n                bottom: area.bottom,\n            },\n        );\n    }\n\n    #[test]\n    fn test_bsp_flip_no_gap_across_multiple_ratios() {\n        let area = test_area();\n\n        for &ratio in &[0.3, 0.4, 0.6, 0.7, 0.8] {\n            let opts = layout_options_with_column_ratios(&[ratio]);\n            let len = NonZeroUsize::new(2).unwrap();\n\n            for &delta in &[25, 50, 100] {\n                let resize = [\n                    Some(Rect {\n                        left: 0,\n                        top: 0,\n                        right: delta,\n                        bottom: 0,\n                    }),\n                    None,\n                ];\n\n                let layouts = DefaultLayout::BSP.calculate(\n                    &area,\n                    len,\n                    None,\n                    Some(Axis::Horizontal),\n                    &resize,\n                    0,\n                    Some(opts),\n                    &[],\n                );\n\n                assert_containers_adjacent_horizontally(&layouts, &area);\n            }\n        }\n    }\n}\n\nmod right_main_vertical_stack_tests {\n    use super::*;\n\n    #[test]\n    fn test_right_main_default_ratio() {\n        let area = test_area();\n        let len = NonZeroUsize::new(3).unwrap();\n        let layouts = DefaultLayout::RightMainVerticalStack.calculate(\n            &area,\n            len,\n            None,\n            None,\n            &[],\n            0,\n            None,\n            &[],\n        );\n\n        assert_eq!(layouts.len(), 3);\n        // Primary should be on the right, 50% width\n        assert_eq!(layouts[0].right, 500);\n        assert_eq!(layouts[0].left, 500); // Right side\n    }\n\n    #[test]\n    fn test_right_main_custom_ratio() {\n        let area = test_area();\n        let len = NonZeroUsize::new(3).unwrap();\n        let opts = layout_options_with_column_ratios(&[0.6]);\n        let layouts = DefaultLayout::RightMainVerticalStack.calculate(\n            &area,\n            len,\n            None,\n            None,\n            &[],\n            0,\n            Some(opts),\n            &[],\n        );\n\n        assert_eq!(layouts.len(), 3);\n        // Primary: 60% = 600\n        assert_eq!(layouts[0].right, 600);\n        // Should be positioned on the right\n        assert_eq!(layouts[0].left, 400);\n    }\n}\n\nmod columns_layout_tests {\n    use super::*;\n\n    #[test]\n    fn test_columns_layout_with_ratios() {\n        let area = test_area();\n        let len = NonZeroUsize::new(3).unwrap();\n        let opts = layout_options_with_column_ratios(&[0.2, 0.5]);\n        let layouts =\n            DefaultLayout::Columns.calculate(&area, len, None, None, &[], 0, Some(opts), &[]);\n\n        assert_eq!(layouts.len(), 3);\n        assert_eq!(layouts[0].right, 200); // 20%\n        assert_eq!(layouts[1].right, 500); // 50%\n        assert_eq!(layouts[2].right, 300); // remaining\n    }\n}\n\nmod rows_layout_tests {\n    use super::*;\n\n    #[test]\n    fn test_rows_layout_with_ratios() {\n        let area = test_area();\n        let len = NonZeroUsize::new(3).unwrap();\n        let opts = layout_options_with_row_ratios(&[0.25, 0.5]);\n        let layouts =\n            DefaultLayout::Rows.calculate(&area, len, None, None, &[], 0, Some(opts), &[]);\n\n        assert_eq!(layouts.len(), 3);\n        assert_eq!(layouts[0].bottom, 200); // 25%\n        assert_eq!(layouts[1].bottom, 400); // 50%\n        assert_eq!(layouts[2].bottom, 200); // remaining\n    }\n}\n\nmod grid_layout_tests {\n    use super::*;\n\n    #[test]\n    fn test_grid_with_column_ratios() {\n        let area = test_area();\n        let len = NonZeroUsize::new(4).unwrap();\n        let opts = layout_options_with_column_ratios(&[0.3]);\n        let layouts =\n            DefaultLayout::Grid.calculate(&area, len, None, None, &[], 0, Some(opts), &[]);\n\n        assert_eq!(layouts.len(), 4);\n        // Grid with 4 windows should be 2x2\n        // First column: 30% = 300\n        assert_eq!(layouts[0].right, 300);\n        assert_eq!(layouts[1].right, 300);\n    }\n\n    #[test]\n    fn test_grid_without_ratios() {\n        let area = test_area();\n        let len = NonZeroUsize::new(4).unwrap();\n        let layouts = DefaultLayout::Grid.calculate(&area, len, None, None, &[], 0, None, &[]);\n\n        assert_eq!(layouts.len(), 4);\n        // 2x2 grid, equal columns = 500 each\n        assert_eq!(layouts[0].right, 500);\n        assert_eq!(layouts[2].right, 500);\n    }\n\n    #[test]\n    fn test_grid_flip_horizontal_with_ratios_no_overlap() {\n        // 4 windows => 2x2 grid, column_ratios=[0.3]\n        // col 0: width=300 (30%), col 1: width=700 (remaining)\n        // With horizontal flip: col 1 (width 700) at left=0, col 0 (width 300) at left=700\n        let area = test_area(); // 1000x800\n        let opts = layout_options_with_column_ratios(&[0.3]);\n        let layouts = DefaultLayout::Grid.calculate(\n            &area,\n            NonZeroUsize::new(4).unwrap(),\n            None,\n            Some(Axis::Horizontal),\n            &[],\n            0,\n            Some(opts),\n            &[],\n        );\n\n        assert_eq!(layouts.len(), 4);\n\n        // Group by left position\n        let mut columns: std::collections::BTreeMap<i32, Vec<&Rect>> =\n            std::collections::BTreeMap::new();\n        for layout in &layouts {\n            columns.entry(layout.left).or_default().push(layout);\n        }\n\n        assert_eq!(\n            columns.len(),\n            2,\n            \"expected 2 columns, got {:?}\",\n            columns.keys().collect::<Vec<_>>()\n        );\n\n        // No container should overlap with any other\n        for (i, a) in layouts.iter().enumerate() {\n            for (j, b) in layouts.iter().enumerate() {\n                if i >= j {\n                    continue;\n                }\n                let h_overlap = a.left < b.left + b.right && b.left < a.left + a.right;\n                let v_overlap = a.top < b.top + b.bottom && b.top < a.top + a.bottom;\n                assert!(\n                    !(h_overlap && v_overlap),\n                    \"containers {i} and {j} overlap: {a:?} vs {b:?}\"\n                );\n            }\n        }\n\n        // Columns should tile the full width with no gaps\n        let col_entries: Vec<_> = columns.iter().collect();\n        let first_left = *col_entries[0].0;\n        let last = col_entries.last().unwrap();\n        let last_right_edge = last.0 + last.1[0].right;\n        assert_eq!(\n            first_left, area.left,\n            \"first column should start at area.left\"\n        );\n        assert_eq!(\n            last_right_edge,\n            area.left + area.right,\n            \"last column should reach area right edge\"\n        );\n    }\n\n    #[test]\n    fn test_grid_flip_all_axes_with_ratios_no_overlap() {\n        let area = test_area();\n        let opts = layout_options_with_column_ratios(&[0.3]);\n\n        for flip in [\n            Axis::Horizontal,\n            Axis::Vertical,\n            Axis::HorizontalAndVertical,\n        ] {\n            let layouts = DefaultLayout::Grid.calculate(\n                &area,\n                NonZeroUsize::new(4).unwrap(),\n                None,\n                Some(flip),\n                &[],\n                0,\n                Some(opts),\n                &[],\n            );\n\n            for (i, a) in layouts.iter().enumerate() {\n                for (j, b) in layouts.iter().enumerate() {\n                    if i >= j {\n                        continue;\n                    }\n                    let h_overlap = a.left < b.left + b.right && b.left < a.left + a.right;\n                    let v_overlap = a.top < b.top + b.bottom && b.top < a.top + a.bottom;\n                    assert!(\n                        !(h_overlap && v_overlap),\n                        \"{flip:?}: containers {i} and {j} overlap: {a:?} vs {b:?}\"\n                    );\n                }\n            }\n\n            // All containers should cover the full area\n            let total_area: i64 = layouts\n                .iter()\n                .map(|r| r.right as i64 * r.bottom as i64)\n                .sum();\n            assert_eq!(\n                total_area,\n                area.right as i64 * area.bottom as i64,\n                \"{flip:?}: total container area doesn't match grid area\"\n            );\n        }\n    }\n\n    #[test]\n    fn test_grid_uneven_rows_cover_full_height() {\n        // 7 windows => ceil(sqrt(7)) = 3 columns\n        // Distribution: col0=2 rows, col1=2 rows, col2=3 rows\n        // With area.bottom=800:\n        //   2-row columns: 800/2=400 each, total=800 (ok)\n        //   3-row column:  800/3=266 each, total=798 (2px gap!)\n        let area = Rect {\n            left: 0,\n            top: 0,\n            right: 1200,\n            bottom: 800,\n        };\n        let layouts = DefaultLayout::Grid.calculate(\n            &area,\n            NonZeroUsize::new(7).unwrap(),\n            None,\n            None,\n            &[],\n            0,\n            None,\n            &[],\n        );\n\n        assert_eq!(layouts.len(), 7);\n\n        // Group windows by column (by their left position)\n        let mut columns: std::collections::BTreeMap<i32, Vec<&Rect>> =\n            std::collections::BTreeMap::new();\n        for layout in &layouts {\n            columns.entry(layout.left).or_default().push(layout);\n        }\n\n        // Every column's windows should cover the full area height\n        for (&col_left, windows) in &columns {\n            // Sort by top position\n            let mut sorted: Vec<&&Rect> = windows.iter().collect();\n            sorted.sort_by_key(|w| w.top);\n\n            // First window should start at area.top\n            assert_eq!(\n                sorted[0].top, area.top,\n                \"column at left={col_left}: first window should start at area.top\"\n            );\n\n            // Last window's bottom edge should reach area.bottom\n            let last = sorted.last().unwrap();\n            let bottom_edge = last.top + last.bottom;\n            assert_eq!(\n                bottom_edge,\n                area.bottom,\n                \"column at left={col_left} ({} rows): bottom edge is {bottom_edge}, \\\n                 expected {}. Gap of {} pixels\",\n                windows.len(),\n                area.bottom,\n                area.bottom - bottom_edge,\n            );\n        }\n    }\n\n    #[test]\n    fn test_grid_uneven_rows_cover_full_height_with_vertical_flip() {\n        let area = Rect {\n            left: 0,\n            top: 0,\n            right: 1200,\n            bottom: 800,\n        };\n\n        for flip in [Axis::Vertical, Axis::HorizontalAndVertical] {\n            let layouts = DefaultLayout::Grid.calculate(\n                &area,\n                NonZeroUsize::new(7).unwrap(),\n                None,\n                Some(flip),\n                &[],\n                0,\n                None,\n                &[],\n            );\n\n            let mut columns: std::collections::BTreeMap<i32, Vec<&Rect>> =\n                std::collections::BTreeMap::new();\n            for layout in &layouts {\n                columns.entry(layout.left).or_default().push(layout);\n            }\n\n            for (&col_left, windows) in &columns {\n                let mut sorted: Vec<&&Rect> = windows.iter().collect();\n                sorted.sort_by_key(|w| w.top);\n\n                assert_eq!(\n                    sorted[0].top, area.top,\n                    \"{flip:?}: column at left={col_left}: first window should start at area.top\"\n                );\n\n                let last = sorted.last().unwrap();\n                let bottom_edge = last.top + last.bottom;\n                assert_eq!(\n                    bottom_edge,\n                    area.bottom,\n                    \"{flip:?}: column at left={col_left} ({} rows): bottom edge is {bottom_edge}, \\\n                     expected {}. Gap of {} pixels\",\n                    windows.len(),\n                    area.bottom,\n                    area.bottom - bottom_edge,\n                );\n\n                // Adjacent windows within the column should have no gaps\n                for pair in sorted.windows(2) {\n                    let edge = pair[0].top + pair[0].bottom;\n                    assert_eq!(\n                        edge, pair[1].top,\n                        \"{flip:?}: column at left={col_left}: gap between rows at y={edge} and y={}\",\n                        pair[1].top,\n                    );\n                }\n            }\n        }\n    }\n\n    #[test]\n    fn test_grid_uneven_columns_cover_full_width() {\n        // 5 windows => ceil(sqrt(5)) = 3 columns\n        // With area.right=1000: 1000/3=333 each, total=999 (1px gap!)\n        let area = Rect {\n            left: 0,\n            top: 0,\n            right: 1000,\n            bottom: 800,\n        };\n        let layouts = DefaultLayout::Grid.calculate(\n            &area,\n            NonZeroUsize::new(5).unwrap(),\n            None,\n            None,\n            &[],\n            0,\n            None,\n            &[],\n        );\n\n        assert_eq!(layouts.len(), 5);\n\n        // Group windows by column (by their left position)\n        let mut columns: std::collections::BTreeMap<i32, Vec<&Rect>> =\n            std::collections::BTreeMap::new();\n        for layout in &layouts {\n            columns.entry(layout.left).or_default().push(layout);\n        }\n\n        // First column should start at area.left\n        let first_left = *columns.keys().next().unwrap();\n        assert_eq!(\n            first_left, area.left,\n            \"first column should start at area.left\"\n        );\n\n        // Last column's right edge should reach area.right\n        let (&last_left, last_windows) = columns.iter().last().unwrap();\n        let last_right_edge = last_left + last_windows[0].right;\n        assert_eq!(\n            last_right_edge,\n            area.left + area.right,\n            \"last column right edge is {last_right_edge}, expected {}. Gap of {} pixels\",\n            area.left + area.right,\n            area.left + area.right - last_right_edge,\n        );\n\n        // Adjacent columns should have no gaps\n        let col_entries: Vec<_> = columns.iter().collect();\n        for pair in col_entries.windows(2) {\n            let (&left_a, windows_a) = pair[0];\n            let (&left_b, _) = pair[1];\n            let right_edge_a = left_a + windows_a[0].right;\n            assert_eq!(\n                right_edge_a, left_b,\n                \"gap between columns at x={right_edge_a} and x={left_b}\",\n            );\n        }\n    }\n\n    #[test]\n    fn test_grid_uneven_columns_cover_full_width_with_horizontal_flip() {\n        let area = Rect {\n            left: 0,\n            top: 0,\n            right: 1000,\n            bottom: 800,\n        };\n\n        for flip in [Axis::Horizontal, Axis::HorizontalAndVertical] {\n            let layouts = DefaultLayout::Grid.calculate(\n                &area,\n                NonZeroUsize::new(5).unwrap(),\n                None,\n                Some(flip),\n                &[],\n                0,\n                None,\n                &[],\n            );\n\n            let mut columns: std::collections::BTreeMap<i32, Vec<&Rect>> =\n                std::collections::BTreeMap::new();\n            for layout in &layouts {\n                columns.entry(layout.left).or_default().push(layout);\n            }\n\n            let first_left = *columns.keys().next().unwrap();\n            assert_eq!(\n                first_left, area.left,\n                \"{flip:?}: first column should start at area.left\"\n            );\n\n            let (&last_left, last_windows) = columns.iter().last().unwrap();\n            let last_right_edge = last_left + last_windows[0].right;\n            assert_eq!(\n                last_right_edge,\n                area.left + area.right,\n                \"{flip:?}: last column right edge is {last_right_edge}, expected {}. Gap of {} pixels\",\n                area.left + area.right,\n                area.left + area.right - last_right_edge,\n            );\n\n            // Adjacent columns should have no gaps\n            let col_entries: Vec<_> = columns.iter().collect();\n            for pair in col_entries.windows(2) {\n                let (&left_a, windows_a) = pair[0];\n                let (&left_b, _) = pair[1];\n                let right_edge_a = left_a + windows_a[0].right;\n                assert_eq!(\n                    right_edge_a, left_b,\n                    \"{flip:?}: gap between columns at x={right_edge_a} and x={left_b}\",\n                );\n            }\n        }\n    }\n}\n\nmod layout_flip_tests {\n    use super::*;\n\n    #[test]\n    fn test_columns_flip_horizontal() {\n        let area = test_area();\n        let len = NonZeroUsize::new(3).unwrap();\n        let opts = layout_options_with_column_ratios(&[0.2, 0.3]);\n        let layouts = DefaultLayout::Columns.calculate(\n            &area,\n            len,\n            None,\n            Some(Axis::Horizontal),\n            &[],\n            0,\n            Some(opts),\n            &[],\n        );\n\n        assert_eq!(layouts.len(), 3);\n        // Columns should be reversed\n        // Last column (originally 50%) should now be first\n        assert_eq!(layouts[2].left, 0);\n    }\n\n    #[test]\n    fn test_rows_flip_vertical() {\n        let area = test_area();\n        let len = NonZeroUsize::new(3).unwrap();\n        let opts = layout_options_with_row_ratios(&[0.25, 0.5]);\n        let layouts = DefaultLayout::Rows.calculate(\n            &area,\n            len,\n            None,\n            Some(Axis::Vertical),\n            &[],\n            0,\n            Some(opts),\n            &[],\n        );\n\n        assert_eq!(layouts.len(), 3);\n        // Rows should be reversed\n        // Last row should now be at top\n        assert_eq!(layouts[2].top, 0);\n    }\n}\n\nmod flip_remainder_coverage_tests {\n    use super::*;\n\n    /// Verify that layouts tile the full area with no gaps after flipping.\n    /// Checks that the leftmost edge == area.left, rightmost edge == area.left + area.right,\n    /// topmost edge == area.top, bottommost edge == area.top + area.bottom,\n    /// and no two windows overlap.\n    fn assert_full_coverage(layouts: &[Rect], area: &Rect, label: &str) {\n        assert!(!layouts.is_empty(), \"{label}: no layouts produced\");\n\n        let left_edge = layouts.iter().map(|r| r.left).min().unwrap();\n        let top_edge = layouts.iter().map(|r| r.top).min().unwrap();\n        let right_edge = layouts.iter().map(|r| r.left + r.right).max().unwrap();\n        let bottom_edge = layouts.iter().map(|r| r.top + r.bottom).max().unwrap();\n\n        assert_eq!(left_edge, area.left, \"{label}: left edge gap\");\n        assert_eq!(top_edge, area.top, \"{label}: top edge gap\");\n        assert_eq!(\n            right_edge,\n            area.left + area.right,\n            \"{label}: right edge gap of {} pixels\",\n            area.left + area.right - right_edge,\n        );\n        assert_eq!(\n            bottom_edge,\n            area.top + area.bottom,\n            \"{label}: bottom edge gap of {} pixels\",\n            area.top + area.bottom - bottom_edge,\n        );\n\n        // No overlaps\n        for (i, a) in layouts.iter().enumerate() {\n            for (j, b) in layouts.iter().enumerate() {\n                if i >= j {\n                    continue;\n                }\n                let h = a.left < b.left + b.right && b.left < a.left + a.right;\n                let v = a.top < b.top + b.bottom && b.top < a.top + a.bottom;\n                assert!(\n                    !(h && v),\n                    \"{label}: windows {i} and {j} overlap: {a:?} vs {b:?}\"\n                );\n            }\n        }\n    }\n\n    // Area whose dimensions are not evenly divisible by 3\n    fn uneven_area() -> Rect {\n        Rect {\n            left: 0,\n            top: 0,\n            right: 1000, // 1000/3 = 333 rem 1\n            bottom: 800, // 800/3 = 266 rem 2\n        }\n    }\n\n    #[test]\n    fn test_columns_flipped_cover_full_area() {\n        let area = uneven_area();\n        let len = NonZeroUsize::new(3).unwrap();\n        for flip in [Axis::Horizontal, Axis::HorizontalAndVertical] {\n            let layouts =\n                DefaultLayout::Columns.calculate(&area, len, None, Some(flip), &[], 0, None, &[]);\n            assert_full_coverage(&layouts, &area, &format!(\"Columns {flip:?}\"));\n        }\n    }\n\n    #[test]\n    fn test_rows_flipped_cover_full_area() {\n        let area = uneven_area();\n        let len = NonZeroUsize::new(3).unwrap();\n        for flip in [Axis::Vertical, Axis::HorizontalAndVertical] {\n            let layouts =\n                DefaultLayout::Rows.calculate(&area, len, None, Some(flip), &[], 0, None, &[]);\n            assert_full_coverage(&layouts, &area, &format!(\"Rows {flip:?}\"));\n        }\n    }\n\n    #[test]\n    fn test_vertical_stack_flipped_cover_full_area() {\n        let area = uneven_area();\n        // 4 windows: 1 primary + 3 stack rows (triggers remainder in rows_with_ratios)\n        let len = NonZeroUsize::new(4).unwrap();\n        for flip in [\n            Axis::Horizontal,\n            Axis::Vertical,\n            Axis::HorizontalAndVertical,\n        ] {\n            let layouts = DefaultLayout::VerticalStack.calculate(\n                &area,\n                len,\n                None,\n                Some(flip),\n                &[],\n                0,\n                None,\n                &[],\n            );\n            assert_full_coverage(&layouts, &area, &format!(\"VerticalStack {flip:?}\"));\n        }\n    }\n\n    #[test]\n    fn test_horizontal_stack_flipped_cover_full_area() {\n        let area = uneven_area();\n        // 4 windows: 1 primary + 3 stack columns (triggers remainder in columns_with_ratios)\n        let len = NonZeroUsize::new(4).unwrap();\n        for flip in [\n            Axis::Horizontal,\n            Axis::Vertical,\n            Axis::HorizontalAndVertical,\n        ] {\n            let layouts = DefaultLayout::HorizontalStack.calculate(\n                &area,\n                len,\n                None,\n                Some(flip),\n                &[],\n                0,\n                None,\n                &[],\n            );\n            assert_full_coverage(&layouts, &area, &format!(\"HorizontalStack {flip:?}\"));\n        }\n    }\n\n    #[test]\n    fn test_right_main_vertical_stack_flipped_cover_full_area() {\n        let area = uneven_area();\n        let len = NonZeroUsize::new(4).unwrap();\n        for flip in [\n            Axis::Horizontal,\n            Axis::Vertical,\n            Axis::HorizontalAndVertical,\n        ] {\n            let layouts = DefaultLayout::RightMainVerticalStack.calculate(\n                &area,\n                len,\n                None,\n                Some(flip),\n                &[],\n                0,\n                None,\n                &[],\n            );\n            assert_full_coverage(&layouts, &area, &format!(\"RightMainVerticalStack {flip:?}\"));\n        }\n    }\n\n    #[test]\n    fn test_ultrawide_vertical_stack_flipped_cover_full_area() {\n        let area = uneven_area();\n        // 5 windows: primary + secondary + 3 tertiary rows (triggers remainder)\n        let len = NonZeroUsize::new(5).unwrap();\n        for flip in [\n            Axis::Horizontal,\n            Axis::Vertical,\n            Axis::HorizontalAndVertical,\n        ] {\n            let layouts = DefaultLayout::UltrawideVerticalStack.calculate(\n                &area,\n                len,\n                None,\n                Some(flip),\n                &[],\n                0,\n                None,\n                &[],\n            );\n            assert_full_coverage(&layouts, &area, &format!(\"UltrawideVerticalStack {flip:?}\"));\n        }\n    }\n}\n\nmod container_padding_tests {\n    use super::*;\n\n    #[test]\n    fn test_padding_applied_to_all_layouts() {\n        let area = test_area();\n        let len = NonZeroUsize::new(2).unwrap();\n        let padding = 10;\n        let layouts =\n            DefaultLayout::Columns.calculate(&area, len, Some(padding), None, &[], 0, None, &[]);\n\n        assert_eq!(layouts.len(), 2);\n        // Each layout should have padding applied\n        // left increases, right decreases, top increases, bottom decreases\n        assert_eq!(layouts[0].left, padding);\n        assert_eq!(layouts[0].top, padding);\n        assert_eq!(layouts[0].right, 500 - padding * 2);\n        assert_eq!(layouts[0].bottom, 800 - padding * 2);\n    }\n}\n\nmod flip_resize_adjacency_tests {\n    use super::*;\n\n    fn resize_3() -> Vec<Option<Rect>> {\n        vec![\n            Some(Rect {\n                left: 0,\n                top: 0,\n                right: 50,\n                bottom: 0,\n            }),\n            Some(Rect {\n                left: 0,\n                top: 0,\n                right: 0,\n                bottom: 40,\n            }),\n            None,\n        ]\n    }\n\n    fn resize_4() -> Vec<Option<Rect>> {\n        vec![\n            Some(Rect {\n                left: 0,\n                top: 0,\n                right: 50,\n                bottom: 0,\n            }),\n            Some(Rect {\n                left: 0,\n                top: 0,\n                right: 0,\n                bottom: 40,\n            }),\n            None,\n            None,\n        ]\n    }\n\n    #[test]\n    fn test_columns_flip_resize_no_gap() {\n        let area = test_area();\n        let opts = layout_options_with_column_ratios(&[0.3, 0.5]);\n\n        for flip in [Axis::Horizontal, Axis::HorizontalAndVertical] {\n            let layouts = DefaultLayout::Columns.calculate(\n                &area,\n                NonZeroUsize::new(3).unwrap(),\n                None,\n                Some(flip),\n                &resize_3(),\n                0,\n                Some(opts),\n                &[],\n            );\n            assert_containers_adjacent_horizontally(&layouts, &area);\n        }\n    }\n\n    #[test]\n    fn test_rows_flip_resize_no_gap() {\n        let area = test_area();\n        let opts = layout_options_with_row_ratios(&[0.3, 0.5]);\n\n        for flip in [Axis::Vertical, Axis::HorizontalAndVertical] {\n            let layouts = DefaultLayout::Rows.calculate(\n                &area,\n                NonZeroUsize::new(3).unwrap(),\n                None,\n                Some(flip),\n                &resize_3(),\n                0,\n                Some(opts),\n                &[],\n            );\n            assert_containers_adjacent_vertically(&layouts, &area);\n        }\n    }\n\n    #[test]\n    fn test_vertical_stack_flip_resize_no_gap() {\n        let area = test_area();\n        let opts = layout_options_with_ratios(&[0.7], &[0.4]);\n\n        for flip in [\n            Axis::Horizontal,\n            Axis::Vertical,\n            Axis::HorizontalAndVertical,\n        ] {\n            let layouts = DefaultLayout::VerticalStack.calculate(\n                &area,\n                NonZeroUsize::new(4).unwrap(),\n                None,\n                Some(flip),\n                &resize_4(),\n                0,\n                Some(opts),\n                &[],\n            );\n\n            // Primary and stack share the horizontal axis\n            let primary = &layouts[0];\n            let stack = &layouts[1..];\n\n            // All stack elements should be in the same column\n            let stack_left = stack[0].left;\n            let stack_width = stack[0].right;\n            for s in stack {\n                assert_eq!(s.left, stack_left);\n                assert_eq!(s.right, stack_width);\n            }\n\n            // Primary and stack column should be adjacent and fill the area\n            if primary.left < stack_left {\n                assert_eq!(primary.left + primary.right, stack_left);\n                assert_eq!(stack_left + stack_width, area.left + area.right);\n            } else {\n                assert_eq!(stack_left + stack_width, primary.left);\n                assert_eq!(primary.left + primary.right, area.left + area.right);\n            }\n\n            // Stack elements should tile vertically\n            assert_containers_adjacent_vertically(\n                stack,\n                &Rect {\n                    left: stack_left,\n                    top: area.top,\n                    right: stack_width,\n                    bottom: area.bottom,\n                },\n            );\n        }\n    }\n\n    #[test]\n    fn test_right_main_vertical_stack_flip_resize_no_gap() {\n        let area = test_area();\n        let opts = layout_options_with_ratios(&[0.7], &[0.4]);\n\n        for flip in [\n            Axis::Horizontal,\n            Axis::Vertical,\n            Axis::HorizontalAndVertical,\n        ] {\n            let layouts = DefaultLayout::RightMainVerticalStack.calculate(\n                &area,\n                NonZeroUsize::new(4).unwrap(),\n                None,\n                Some(flip),\n                &resize_4(),\n                0,\n                Some(opts),\n                &[],\n            );\n\n            let primary = &layouts[0];\n            let stack = &layouts[1..];\n\n            let stack_left = stack[0].left;\n            let stack_width = stack[0].right;\n            for s in stack {\n                assert_eq!(s.left, stack_left);\n                assert_eq!(s.right, stack_width);\n            }\n\n            if primary.left < stack_left {\n                assert_eq!(primary.left + primary.right, stack_left);\n                assert_eq!(stack_left + stack_width, area.left + area.right);\n            } else {\n                assert_eq!(stack_left + stack_width, primary.left);\n                assert_eq!(primary.left + primary.right, area.left + area.right);\n            }\n\n            assert_containers_adjacent_vertically(\n                stack,\n                &Rect {\n                    left: stack_left,\n                    top: area.top,\n                    right: stack_width,\n                    bottom: area.bottom,\n                },\n            );\n        }\n    }\n\n    #[test]\n    fn test_horizontal_stack_flip_resize_no_gap() {\n        let area = test_area();\n        let opts = layout_options_with_ratios(&[0.3, 0.5], &[0.7]);\n\n        for flip in [\n            Axis::Horizontal,\n            Axis::Vertical,\n            Axis::HorizontalAndVertical,\n        ] {\n            let layouts = DefaultLayout::HorizontalStack.calculate(\n                &area,\n                NonZeroUsize::new(4).unwrap(),\n                None,\n                Some(flip),\n                &resize_4(),\n                0,\n                Some(opts),\n                &[],\n            );\n\n            let primary = &layouts[0];\n            let stack = &layouts[1..];\n\n            // All stack elements should be in the same row\n            let stack_top = stack[0].top;\n            let stack_height = stack[0].bottom;\n            for s in stack {\n                assert_eq!(s.top, stack_top);\n                assert_eq!(s.bottom, stack_height);\n            }\n\n            // Primary and stack row should be adjacent and fill the area\n            if primary.top < stack_top {\n                assert_eq!(primary.top + primary.bottom, stack_top);\n                assert_eq!(stack_top + stack_height, area.top + area.bottom);\n            } else {\n                assert_eq!(stack_top + stack_height, primary.top);\n                assert_eq!(primary.top + primary.bottom, area.top + area.bottom);\n            }\n\n            // Stack elements should tile horizontally\n            assert_containers_adjacent_horizontally(\n                stack,\n                &Rect {\n                    left: area.left,\n                    top: stack_top,\n                    right: area.right,\n                    bottom: stack_height,\n                },\n            );\n        }\n    }\n\n    #[test]\n    fn test_ultrawide_vertical_stack_flip_resize_no_gap() {\n        let area = test_area();\n        let opts = layout_options_with_ratios(&[0.5, 0.2], &[0.6]);\n\n        for flip in [\n            Axis::Horizontal,\n            Axis::Vertical,\n            Axis::HorizontalAndVertical,\n        ] {\n            let layouts = DefaultLayout::UltrawideVerticalStack.calculate(\n                &area,\n                NonZeroUsize::new(4).unwrap(),\n                None,\n                Some(flip),\n                &resize_4(),\n                0,\n                Some(opts),\n                &[],\n            );\n\n            let primary = &layouts[0];\n            let secondary = &layouts[1];\n            let tertiary = &layouts[2..];\n\n            // All tertiary elements share the same column\n            let tert_left = tertiary[0].left;\n            let tert_width = tertiary[0].right;\n            for t in tertiary {\n                assert_eq!(t.left, tert_left);\n                assert_eq!(t.right, tert_width);\n            }\n\n            // The three columns (primary, secondary, tertiary) should tile horizontally\n            let columns = [\n                Rect {\n                    left: primary.left,\n                    top: 0,\n                    right: primary.right,\n                    bottom: 0,\n                },\n                Rect {\n                    left: secondary.left,\n                    top: 0,\n                    right: secondary.right,\n                    bottom: 0,\n                },\n                Rect {\n                    left: tert_left,\n                    top: 0,\n                    right: tert_width,\n                    bottom: 0,\n                },\n            ];\n            assert_containers_adjacent_horizontally(&columns, &area);\n\n            // Tertiary elements should tile vertically\n            if tertiary.len() > 1 {\n                assert_containers_adjacent_vertically(\n                    tertiary,\n                    &Rect {\n                        left: tert_left,\n                        top: area.top,\n                        right: tert_width,\n                        bottom: area.bottom,\n                    },\n                );\n            }\n        }\n    }\n\n    #[test]\n    fn test_scrolling_resize_no_gap() {\n        let area = test_area();\n\n        // Scrolling doesn't support flip, but verify resize adjacency\n        let layouts = DefaultLayout::Scrolling.calculate(\n            &area,\n            NonZeroUsize::new(3).unwrap(),\n            None,\n            None,\n            &resize_3(),\n            0,\n            None,\n            &[],\n        );\n\n        // Adjacent visible columns should not have gaps\n        for pair in layouts.windows(2) {\n            assert_eq!(\n                pair[0].left + pair[0].right,\n                pair[1].left,\n                \"scrolling gap at left={} (width {}) and left={}\",\n                pair[0].left,\n                pair[0].right,\n                pair[1].left,\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "komorebi-layouts/src/custom_layout.rs",
    "content": "use color_eyre::eyre;\nuse color_eyre::eyre::bail;\nuse serde::Deserialize;\nuse serde::Serialize;\nuse std::collections::HashMap;\nuse std::fs::File;\nuse std::io::BufReader;\nuse std::ops::Deref;\nuse std::ops::DerefMut;\nuse std::path::Path;\n\nuse super::Rect;\n\n#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\npub struct CustomLayout(Vec<Column>);\n\nimpl Deref for CustomLayout {\n    type Target = Vec<Column>;\n\n    fn deref(&self) -> &Self::Target {\n        &self.0\n    }\n}\n\nimpl DerefMut for CustomLayout {\n    fn deref_mut(&mut self) -> &mut Self::Target {\n        &mut self.0\n    }\n}\n\nimpl CustomLayout {\n    pub fn from_path<P: AsRef<Path>>(path: P) -> eyre::Result<Self> {\n        let path = path.as_ref();\n        let layout: Self = match path.extension() {\n            Some(extension) if extension == \"yaml\" || extension == \"yml\" => {\n                serde_json::from_reader(BufReader::new(File::open(path)?))?\n            }\n            Some(extension) if extension == \"json\" => {\n                serde_json::from_reader(BufReader::new(File::open(path)?))?\n            }\n            _ => bail!(\"custom layouts must be json or yaml files\"),\n        };\n\n        if !layout.is_valid() {\n            bail!(\"the layout file provided was invalid\");\n        }\n\n        Ok(layout)\n    }\n\n    #[must_use]\n    pub fn column_with_idx(&self, idx: usize) -> (usize, Option<&Column>) {\n        let column_idx = self.column_for_container_idx(idx);\n        let column = self.get(column_idx);\n        (column_idx, column)\n    }\n\n    #[must_use]\n    pub fn primary_idx(&self) -> Option<usize> {\n        for (i, column) in self.iter().enumerate() {\n            if let Column::Primary(_) = column {\n                return Option::from(i);\n            }\n        }\n\n        None\n    }\n\n    #[must_use]\n    pub fn primary_width_percentage(&self) -> Option<f32> {\n        for column in self.iter() {\n            if let Column::Primary(Option::Some(ColumnWidth::WidthPercentage(percentage))) = column\n            {\n                return Option::from(*percentage);\n            }\n        }\n\n        None\n    }\n\n    pub fn set_primary_width_percentage(&mut self, percentage: f32) {\n        for column in self.iter_mut() {\n            if let Column::Primary(Option::Some(ColumnWidth::WidthPercentage(current))) = column {\n                *current = percentage;\n            }\n        }\n    }\n\n    #[must_use]\n    pub fn is_valid(&self) -> bool {\n        // A valid layout must have at least one column\n        if self.is_empty() {\n            return false;\n        };\n\n        // Vertical column splits aren't supported at the moment\n        for column in self.iter() {\n            match column {\n                Column::Tertiary(ColumnSplit::Vertical)\n                | Column::Secondary(Some(ColumnSplitWithCapacity::Vertical(_))) => return false,\n                _ => {}\n            }\n        }\n\n        // The final column must not have a fixed capacity\n        match self.last() {\n            Some(Column::Tertiary(_)) => {}\n            _ => return false,\n        }\n\n        let mut primaries = 0;\n        let mut tertiaries = 0;\n\n        for column in self.iter() {\n            match column {\n                Column::Primary(_) => primaries += 1,\n                Column::Tertiary(_) => tertiaries += 1,\n                Column::Secondary(_) => {}\n            }\n        }\n\n        // There must only be one primary and one tertiary column\n        matches!(primaries, 1) && matches!(tertiaries, 1)\n    }\n\n    pub(crate) fn column_container_counts(&self) -> HashMap<usize, usize> {\n        let mut count_map = HashMap::new();\n\n        for (idx, column) in self.iter().enumerate() {\n            match column {\n                Column::Primary(_) | Column::Secondary(None) => {\n                    count_map.insert(idx, 1);\n                }\n                Column::Secondary(Some(split)) => {\n                    count_map.insert(\n                        idx,\n                        match split {\n                            ColumnSplitWithCapacity::Vertical(n)\n                            | ColumnSplitWithCapacity::Horizontal(n) => *n,\n                        },\n                    );\n                }\n                Column::Tertiary(_) => {}\n            }\n        }\n\n        count_map\n    }\n\n    #[must_use]\n    pub fn first_container_idx(&self, col_idx: usize) -> usize {\n        let count_map = self.column_container_counts();\n        let mut container_idx_accumulator = 0;\n\n        for i in 0..col_idx {\n            if let Some(n) = count_map.get(&i) {\n                container_idx_accumulator += n;\n            }\n        }\n\n        container_idx_accumulator\n    }\n\n    #[must_use]\n    pub fn column_for_container_idx(&self, idx: usize) -> usize {\n        let count_map = self.column_container_counts();\n        let mut container_idx_accumulator = 0;\n\n        // always -1 because we don't insert the tertiary column in the count_map\n        for i in 0..self.len() - 1 {\n            if let Some(n) = count_map.get(&i) {\n                container_idx_accumulator += n;\n\n                // The accumulator becomes greater than the window container index\n                // for the first time when we reach a column that contains that\n                // window container index\n                if container_idx_accumulator > idx {\n                    return i;\n                }\n            }\n        }\n\n        // If the accumulator never reaches a point where it is greater than the\n        // window container index, then the only remaining possibility is the\n        // final tertiary column\n        self.len() - 1\n    }\n\n    #[must_use]\n    pub fn column_area(&self, work_area: &Rect, idx: usize, offset: Option<usize>) -> Rect {\n        let divisor = offset.map_or_else(|| self.len(), |offset| self.len() - offset);\n\n        #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]\n        let equal_width = work_area.right / divisor as i32;\n        let mut left = work_area.left;\n        let right = equal_width;\n\n        for _ in 0..idx {\n            left += right;\n        }\n\n        Rect {\n            left,\n            top: work_area.top,\n            right,\n            bottom: work_area.bottom,\n        }\n    }\n\n    #[must_use]\n    pub fn column_area_with_last(\n        len: usize,\n        work_area: &Rect,\n        primary_right: i32,\n        last_column: Option<Rect>,\n        offset: Option<usize>,\n    ) -> Rect {\n        let divisor = offset.map_or_else(|| len - 1, |offset| len - offset - 1);\n\n        #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]\n        let equal_width = (work_area.right - primary_right) / divisor as i32;\n        let left = last_column.map_or(work_area.left, |last| last.left + last.right);\n        let right = equal_width;\n\n        Rect {\n            left,\n            top: work_area.top,\n            right,\n            bottom: work_area.bottom,\n        }\n    }\n\n    #[must_use]\n    pub fn main_column_area(\n        work_area: &Rect,\n        primary_right: i32,\n        last_column: Option<Rect>,\n    ) -> Rect {\n        let left = last_column.map_or(work_area.left, |last| last.left + last.right);\n\n        Rect {\n            left,\n            top: work_area.top,\n            right: primary_right,\n            bottom: work_area.bottom,\n        }\n    }\n}\n\n#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n#[serde(tag = \"column\", content = \"configuration\")]\npub enum Column {\n    Primary(Option<ColumnWidth>),\n    Secondary(Option<ColumnSplitWithCapacity>),\n    Tertiary(ColumnSplit),\n}\n\n#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\npub enum ColumnWidth {\n    WidthPercentage(f32),\n}\n\n#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\npub enum ColumnSplit {\n    Horizontal,\n    Vertical,\n}\n\n#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\npub enum ColumnSplitWithCapacity {\n    Horizontal(usize),\n    Vertical(usize),\n}\n"
  },
  {
    "path": "komorebi-layouts/src/cycle_direction.rs",
    "content": "use std::num::NonZeroUsize;\n\nuse clap::ValueEnum;\nuse serde::Deserialize;\nuse serde::Serialize;\nuse strum::Display;\nuse strum::EnumString;\n\n#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\npub enum CycleDirection {\n    Previous,\n    Next,\n}\n\nimpl CycleDirection {\n    #[must_use]\n    pub const fn next_idx(&self, idx: usize, len: NonZeroUsize) -> usize {\n        match self {\n            Self::Previous => {\n                if idx == 0 {\n                    len.get() - 1\n                } else {\n                    idx - 1\n                }\n            }\n            Self::Next => {\n                if idx == len.get() - 1 {\n                    0\n                } else {\n                    idx + 1\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "komorebi-layouts/src/default_layout.rs",
    "content": "use clap::ValueEnum;\nuse serde::Deserialize;\nuse serde::Serialize;\nuse strum::Display;\nuse strum::EnumString;\n\nuse super::OperationDirection;\nuse super::Rect;\nuse super::Sizing;\n\n/// Maximum number of ratio values that can be specified for column_ratios and row_ratios\npub const MAX_RATIOS: usize = 5;\n\n/// Minimum allowed ratio value (prevents zero-sized windows)\npub const MIN_RATIO: f32 = 0.1;\n\n/// Maximum allowed ratio value (ensures space for remaining windows)\npub const MAX_RATIO: f32 = 0.9;\n\n/// Default ratio value when none is specified\npub const DEFAULT_RATIO: f32 = 0.5;\n\n/// Default secondary ratio value for UltrawideVerticalStack layout\npub const DEFAULT_SECONDARY_RATIO: f32 = 0.25;\n\n/// Validates and converts a Vec of ratios into a fixed-size array.\n/// - Clamps values to MIN_RATIO..MAX_RATIO range\n/// - Truncates when cumulative sum reaches or exceeds 1.0\n/// - Limits to MAX_RATIOS values\n#[must_use]\npub fn validate_ratios(ratios: &[f32]) -> [Option<f32>; MAX_RATIOS] {\n    let mut arr = [None; MAX_RATIOS];\n    let mut cumulative_sum = 0.0_f32;\n\n    for (i, &val) in ratios.iter().take(MAX_RATIOS).enumerate() {\n        let clamped_val = val.clamp(MIN_RATIO, MAX_RATIO);\n\n        // Only add this ratio if cumulative sum stays below 1.0\n        if cumulative_sum + clamped_val < 1.0 {\n            arr[i] = Some(clamped_val);\n            cumulative_sum += clamped_val;\n        } else {\n            // Stop adding ratios - cumulative sum would reach or exceed 1.0\n            tracing::debug!(\n                \"Truncating ratios at index {} - cumulative sum {} + {} would reach/exceed 1.0\",\n                i,\n                cumulative_sum,\n                clamped_val\n            );\n            break;\n        }\n    }\n    arr\n}\n\n#[derive(\n    Clone, Copy, Debug, Serialize, Deserialize, Eq, PartialEq, Display, EnumString, ValueEnum,\n)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// A predefined komorebi layout\npub enum DefaultLayout {\n    /// BSP Layout\n    ///\n    /// ```text\n    /// +-------+-----+\n    /// |       |     |\n    /// |       +--+--+\n    /// |       |  |--|\n    /// +-------+--+--+\n    /// ```\n    BSP,\n    /// Columns Layout\n    ///\n    /// ```text\n    /// +--+--+--+--+\n    /// |  |  |  |  |\n    /// |  |  |  |  |\n    /// |  |  |  |  |\n    /// +--+--+--+--+\n    /// ```\n    Columns,\n    /// Rows Layout\n    ///\n    /// ```text\n    /// +-----------+\n    /// |-----------|\n    /// |-----------|\n    /// |-----------|\n    /// +-----------+\n    /// ```\n    Rows,\n    /// Vertical Stack Layout\n    ///\n    /// ```text\n    /// +-------+-----+\n    /// |       |     |\n    /// |       +-----+\n    /// |       |     |\n    /// +-------+-----+\n    /// ```\n    VerticalStack,\n    /// Horizontal Stack Layout\n    ///\n    /// ```text\n    /// +------+------+\n    /// |             |\n    /// |------+------+\n    /// |      |      |\n    /// +------+------+\n    /// ```\n    HorizontalStack,\n    /// Ultrawide Vertical Stack Layout\n    ///\n    /// ```text\n    /// +-----+-----------+-----+\n    /// |     |           |     |\n    /// |     |           +-----+\n    /// |     |           |     |\n    /// |     |           +-----+\n    /// |     |           |     |\n    /// +-----+-----------+-----+\n    /// ```\n    UltrawideVerticalStack,\n    /// Grid Layout\n    ///\n    /// ```text\n    /// +-----+-----+   +---+---+---+   +---+---+---+   +---+---+---+\n    /// |     |     |   |   |   |   |   |   |   |   |   |   |   |   |\n    /// |     |     |   |   |   |   |   |   |   |   |   |   |   +---+\n    /// +-----+-----+   |   +---+---+   +---+---+---+   +---+---|   |\n    /// |     |     |   |   |   |   |   |   |   |   |   |   |   +---+\n    /// |     |     |   |   |   |   |   |   |   |   |   |   |   |   |\n    /// +-----+-----+   +---+---+---+   +---+---+---+   +---+---+---+\n    ///   4 windows       5 windows       6 windows       7 windows\n    /// ```\n    Grid,\n    /// Right Main Vertical Stack Layout\n    ///\n    /// ```text\n    /// +-----+-------+\n    /// |     |       |\n    /// +-----+       |\n    /// |     |       |\n    /// +-----+-------+\n    /// ```\n    RightMainVerticalStack,\n    /// Scrolling Layout\n    ///\n    /// ```text\n    /// +--+--+--+--+--+--+\n    /// |     |     |     |\n    /// |     |     |     |\n    /// |     |     |     |\n    /// +--+--+--+--+--+--+\n    /// ```\n    Scrolling,\n    // NOTE: If any new layout is added, please make sure to register the same in `DefaultLayout::cycle`\n}\n\n/// Helper to deserialize a variable-length array into a fixed [Option<f32>; MAX_RATIOS]\n/// Ratios are truncated when their cumulative sum reaches or exceeds 1.0 to ensure\n/// there's always remaining space for additional windows.\nfn deserialize_ratios<'de, D>(\n    deserializer: D,\n) -> Result<Option<[Option<f32>; MAX_RATIOS]>, D::Error>\nwhere\n    D: serde::Deserializer<'de>,\n{\n    let opt: Option<Vec<f32>> = Option::deserialize(deserializer)?;\n    Ok(opt.map(|vec| validate_ratios(&vec)))\n}\n\n/// Helper to serialize [Option<f32>; MAX_RATIOS] as a compact array (without trailing nulls)\nfn serialize_ratios<S>(\n    value: &Option<[Option<f32>; MAX_RATIOS]>,\n    serializer: S,\n) -> Result<S::Ok, S::Error>\nwhere\n    S: serde::Serializer,\n{\n    match value {\n        None => serializer.serialize_none(),\n        Some(arr) => {\n            // Find last non-None index\n            let last_idx = arr\n                .iter()\n                .rposition(|x| x.is_some())\n                .map(|i| i + 1)\n                .unwrap_or(0);\n            let vec: Vec<f32> = arr.iter().take(last_idx).filter_map(|&x| x).collect();\n            serializer.serialize_some(&vec)\n        }\n    }\n}\n\n#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Options for specific layouts\npub struct LayoutOptions {\n    /// Options related to the Scrolling layout\n    pub scrolling: Option<ScrollingLayoutOptions>,\n    /// Options related to the Grid layout\n    pub grid: Option<GridLayoutOptions>,\n    /// Column width ratios (up to MAX_RATIOS values between 0.1 and 0.9)\n    ///\n    /// - Used by Columns layout: ratios for each column width\n    /// - Used by Grid layout: ratios for column widths\n    /// - Used by BSP, VerticalStack, RightMainVerticalStack: column_ratios[0] as primary split ratio\n    /// - Used by HorizontalStack: column_ratios[0] as primary split ratio (top area height)\n    /// - Used by UltrawideVerticalStack: column_ratios[0] as center ratio, column_ratios[1] as left ratio\n    ///\n    /// Columns without a ratio share remaining space equally.\n    /// Example: `[0.3, 0.4, 0.3]` for 30%-40%-30% columns\n    #[serde(\n        default,\n        deserialize_with = \"deserialize_ratios\",\n        serialize_with = \"serialize_ratios\"\n    )]\n    pub column_ratios: Option<[Option<f32>; MAX_RATIOS]>,\n    /// Row height ratios (up to MAX_RATIOS values between 0.1 and 0.9)\n    ///\n    /// - Used by Rows layout: ratios for each row height\n    /// - Used by Grid layout: ratios for row heights\n    ///\n    /// Rows without a ratio share remaining space equally.\n    /// Example: `[0.5, 0.5]` for 50%-50% rows\n    #[serde(\n        default,\n        deserialize_with = \"deserialize_ratios\",\n        serialize_with = \"serialize_ratios\"\n    )]\n    pub row_ratios: Option<[Option<f32>; MAX_RATIOS]>,\n}\n\n#[derive(Clone, Copy, Debug, Serialize, Deserialize, Eq, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Options for the Scrolling layout\npub struct ScrollingLayoutOptions {\n    /// Desired number of visible columns (default: 3)\n    pub columns: usize,\n    /// With an odd number of visible columns, keep the focused window column centered\n    pub center_focused_column: Option<bool>,\n}\n\n#[derive(Clone, Copy, Debug, Serialize, Deserialize, Eq, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Options for the Grid layout\npub struct GridLayoutOptions {\n    /// Maximum number of rows per grid column\n    pub rows: usize,\n}\n\nimpl DefaultLayout {\n    pub fn leftmost_index(&self, len: usize) -> usize {\n        match self {\n            Self::UltrawideVerticalStack | Self::RightMainVerticalStack => match len {\n                n if n > 1 => 1,\n                _ => 0,\n            },\n            Self::Scrolling => 0,\n            DefaultLayout::BSP\n            | DefaultLayout::Columns\n            | DefaultLayout::Rows\n            | DefaultLayout::VerticalStack\n            | DefaultLayout::HorizontalStack\n            | DefaultLayout::Grid => 0,\n        }\n    }\n\n    pub fn rightmost_index(&self, len: usize) -> usize {\n        match self {\n            DefaultLayout::BSP\n            | DefaultLayout::Columns\n            | DefaultLayout::Rows\n            | DefaultLayout::VerticalStack\n            | DefaultLayout::HorizontalStack\n            | DefaultLayout::Grid => len.saturating_sub(1),\n            DefaultLayout::UltrawideVerticalStack => match len {\n                2 => 0,\n                _ => len.saturating_sub(1),\n            },\n            DefaultLayout::RightMainVerticalStack => 0,\n            DefaultLayout::Scrolling => len.saturating_sub(1),\n        }\n    }\n\n    #[must_use]\n    #[allow(clippy::cast_precision_loss, clippy::only_used_in_recursion)]\n    pub fn resize(\n        &self,\n        unaltered: &Rect,\n        resize: &Option<Rect>,\n        edge: OperationDirection,\n        sizing: Sizing,\n        delta: i32,\n    ) -> Option<Rect> {\n        if !matches!(\n            self,\n            Self::BSP\n                | Self::Columns\n                | Self::Rows\n                | Self::VerticalStack\n                | Self::RightMainVerticalStack\n                | Self::HorizontalStack\n                | Self::UltrawideVerticalStack\n                | Self::Scrolling\n        ) {\n            return None;\n        };\n\n        let mut r = resize.unwrap_or_default();\n\n        let resize_delta = delta;\n\n        match edge {\n            OperationDirection::Left => match sizing {\n                Sizing::Increase => {\n                    // Some final checks to make sure the user can't infinitely resize to\n                    // the point of pushing other windows out of bounds\n\n                    // Note: These checks cannot take into account the changes made to the\n                    // edges of adjacent windows at operation time, so it is still possible\n                    // to push windows out of bounds by maxing out an Increase Left on a\n                    // Window with index 1, and then maxing out a Decrease Right on a Window\n                    // with index 0. I don't think it's worth trying to defensively program\n                    // against this; if people end up in this situation they are better off\n                    // just hitting the retile command\n                    let diff = ((r.left + -resize_delta) as f32).abs();\n                    if diff < unaltered.right as f32 {\n                        r.left += -resize_delta;\n                    }\n                }\n                Sizing::Decrease => {\n                    let diff = ((r.left - -resize_delta) as f32).abs();\n                    if diff < unaltered.right as f32 {\n                        r.left -= -resize_delta;\n                    }\n                }\n            },\n            OperationDirection::Up => match sizing {\n                Sizing::Increase => {\n                    let diff = ((r.top + resize_delta) as f32).abs();\n                    if diff < unaltered.bottom as f32 {\n                        r.top += -resize_delta;\n                    }\n                }\n                Sizing::Decrease => {\n                    let diff = ((r.top - resize_delta) as f32).abs();\n                    if diff < unaltered.bottom as f32 {\n                        r.top -= -resize_delta;\n                    }\n                }\n            },\n            OperationDirection::Right => match sizing {\n                Sizing::Increase => {\n                    let diff = ((r.right + resize_delta) as f32).abs();\n                    if diff < unaltered.right as f32 {\n                        r.right += resize_delta;\n                    }\n                }\n                Sizing::Decrease => {\n                    let diff = ((r.right - resize_delta) as f32).abs();\n                    if diff < unaltered.right as f32 {\n                        r.right -= resize_delta;\n                    }\n                }\n            },\n            OperationDirection::Down => match sizing {\n                Sizing::Increase => {\n                    let diff = ((r.bottom + resize_delta) as f32).abs();\n                    if diff < unaltered.bottom as f32 {\n                        r.bottom += resize_delta;\n                    }\n                }\n                Sizing::Decrease => {\n                    let diff = ((r.bottom - resize_delta) as f32).abs();\n                    if diff < unaltered.bottom as f32 {\n                        r.bottom -= resize_delta;\n                    }\n                }\n            },\n        };\n\n        if r.eq(&Rect::default()) {\n            None\n        } else {\n            Option::from(r)\n        }\n    }\n\n    #[must_use]\n    pub const fn cycle_next(self) -> Self {\n        match self {\n            Self::BSP => Self::Columns,\n            Self::Columns => Self::Rows,\n            Self::Rows => Self::VerticalStack,\n            Self::VerticalStack => Self::HorizontalStack,\n            Self::HorizontalStack => Self::UltrawideVerticalStack,\n            Self::UltrawideVerticalStack => Self::Grid,\n            Self::Grid => Self::RightMainVerticalStack,\n            Self::RightMainVerticalStack => Self::Scrolling,\n            Self::Scrolling => Self::BSP,\n        }\n    }\n\n    #[must_use]\n    pub const fn cycle_previous(self) -> Self {\n        match self {\n            Self::Scrolling => Self::RightMainVerticalStack,\n            Self::RightMainVerticalStack => Self::Grid,\n            Self::Grid => Self::UltrawideVerticalStack,\n            Self::UltrawideVerticalStack => Self::HorizontalStack,\n            Self::HorizontalStack => Self::VerticalStack,\n            Self::VerticalStack => Self::Rows,\n            Self::Rows => Self::Columns,\n            Self::Columns => Self::BSP,\n            Self::BSP => Self::RightMainVerticalStack,\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    // Helper to create LayoutOptions with column ratios\n    fn layout_options_with_column_ratios(ratios: &[f32]) -> LayoutOptions {\n        let mut arr = [None; MAX_RATIOS];\n        for (i, &r) in ratios.iter().take(MAX_RATIOS).enumerate() {\n            arr[i] = Some(r);\n        }\n        LayoutOptions {\n            scrolling: None,\n            grid: None,\n            column_ratios: Some(arr),\n            row_ratios: None,\n        }\n    }\n\n    // Helper to create LayoutOptions with row ratios\n    fn layout_options_with_row_ratios(ratios: &[f32]) -> LayoutOptions {\n        let mut arr = [None; MAX_RATIOS];\n        for (i, &r) in ratios.iter().take(MAX_RATIOS).enumerate() {\n            arr[i] = Some(r);\n        }\n        LayoutOptions {\n            scrolling: None,\n            grid: None,\n            column_ratios: None,\n            row_ratios: Some(arr),\n        }\n    }\n\n    // Helper to create LayoutOptions with both column and row ratios\n    fn layout_options_with_ratios(column_ratios: &[f32], row_ratios: &[f32]) -> LayoutOptions {\n        let mut col_arr = [None; MAX_RATIOS];\n        for (i, &r) in column_ratios.iter().take(MAX_RATIOS).enumerate() {\n            col_arr[i] = Some(r);\n        }\n        let mut row_arr = [None; MAX_RATIOS];\n        for (i, &r) in row_ratios.iter().take(MAX_RATIOS).enumerate() {\n            row_arr[i] = Some(r);\n        }\n        LayoutOptions {\n            scrolling: None,\n            grid: None,\n            column_ratios: Some(col_arr),\n            row_ratios: Some(row_arr),\n        }\n    }\n\n    mod deserialize_ratios_tests {\n        use super::*;\n\n        #[test]\n        fn test_deserialize_valid_ratios() {\n            let json = r#\"{\"column_ratios\": [0.3, 0.4, 0.2]}\"#;\n            let opts: LayoutOptions = serde_json::from_str(json).unwrap();\n\n            let ratios = opts.column_ratios.unwrap();\n            assert_eq!(ratios[0], Some(0.3));\n            assert_eq!(ratios[1], Some(0.4));\n            assert_eq!(ratios[2], Some(0.2));\n            assert_eq!(ratios[3], None);\n            assert_eq!(ratios[4], None);\n        }\n\n        #[test]\n        fn test_deserialize_clamps_values_to_min() {\n            // Values below MIN_RATIO should be clamped\n            let json = r#\"{\"column_ratios\": [0.05]}\"#;\n            let opts: LayoutOptions = serde_json::from_str(json).unwrap();\n\n            let ratios = opts.column_ratios.unwrap();\n            assert_eq!(ratios[0], Some(MIN_RATIO)); // Clamped to 0.1\n        }\n\n        #[test]\n        fn test_deserialize_clamps_values_to_max() {\n            // Values above MAX_RATIO should be clamped\n            let json = r#\"{\"column_ratios\": [0.95]}\"#;\n            let opts: LayoutOptions = serde_json::from_str(json).unwrap();\n\n            let ratios = opts.column_ratios.unwrap();\n            // 0.9 is the max, so it should be clamped\n            assert!(ratios[0].unwrap() <= MAX_RATIO);\n        }\n\n        #[test]\n        fn test_deserialize_truncates_when_sum_exceeds_one() {\n            // Sum of ratios should not reach 1.0\n            // [0.5, 0.4] = 0.9, then 0.3 would make it 1.2, so it should be truncated\n            let json = r#\"{\"column_ratios\": [0.5, 0.4, 0.3]}\"#;\n            let opts: LayoutOptions = serde_json::from_str(json).unwrap();\n\n            let ratios = opts.column_ratios.unwrap();\n            assert_eq!(ratios[0], Some(0.5));\n            assert_eq!(ratios[1], Some(0.4));\n            // Third ratio should be truncated because 0.5 + 0.4 + 0.3 >= 1.0\n            assert_eq!(ratios[2], None);\n        }\n\n        #[test]\n        fn test_deserialize_truncates_at_max_ratios() {\n            // More than MAX_RATIOS values should be truncated\n            let json = r#\"{\"column_ratios\": [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]}\"#;\n            let opts: LayoutOptions = serde_json::from_str(json).unwrap();\n\n            let ratios = opts.column_ratios.unwrap();\n            // Only MAX_RATIOS (5) values should be stored\n            for i in 0..MAX_RATIOS {\n                assert_eq!(ratios[i], Some(0.1));\n            }\n        }\n\n        #[test]\n        fn test_deserialize_empty_array() {\n            let json = r#\"{\"column_ratios\": []}\"#;\n            let opts: LayoutOptions = serde_json::from_str(json).unwrap();\n\n            let ratios = opts.column_ratios.unwrap();\n            for i in 0..MAX_RATIOS {\n                assert_eq!(ratios[i], None);\n            }\n        }\n\n        #[test]\n        fn test_deserialize_null() {\n            let json = r#\"{\"column_ratios\": null}\"#;\n            let opts: LayoutOptions = serde_json::from_str(json).unwrap();\n            assert!(opts.column_ratios.is_none());\n        }\n\n        #[test]\n        fn test_deserialize_row_ratios() {\n            let json = r#\"{\"row_ratios\": [0.3, 0.5]}\"#;\n            let opts: LayoutOptions = serde_json::from_str(json).unwrap();\n\n            let ratios = opts.row_ratios.unwrap();\n            assert_eq!(ratios[0], Some(0.3));\n            assert_eq!(ratios[1], Some(0.5));\n            assert_eq!(ratios[2], None);\n        }\n    }\n\n    mod serialize_ratios_tests {\n        use super::*;\n\n        #[test]\n        fn test_serialize_ratios_compact() {\n            let opts = layout_options_with_column_ratios(&[0.3, 0.4]);\n            let json = serde_json::to_string(&opts).unwrap();\n\n            // Should serialize ratios as compact array without trailing nulls in the ratios array\n            assert!(json.contains(\"0.3\") && json.contains(\"0.4\"));\n        }\n\n        #[test]\n        fn test_serialize_none_ratios() {\n            let opts = LayoutOptions {\n                scrolling: None,\n                grid: None,\n                column_ratios: None,\n                row_ratios: None,\n            };\n            let json = serde_json::to_string(&opts).unwrap();\n\n            // None values should serialize as null or be omitted\n            assert!(!json.contains(\"[\"));\n        }\n\n        #[test]\n        fn test_roundtrip_serialization() {\n            let original = layout_options_with_column_ratios(&[0.3, 0.4, 0.2]);\n            let json = serde_json::to_string(&original).unwrap();\n            let deserialized: LayoutOptions = serde_json::from_str(&json).unwrap();\n\n            assert_eq!(original.column_ratios, deserialized.column_ratios);\n        }\n\n        #[test]\n        fn test_serialize_row_ratios() {\n            let opts = layout_options_with_row_ratios(&[0.3, 0.5]);\n            let json = serde_json::to_string(&opts).unwrap();\n\n            assert!(json.contains(\"row_ratios\"));\n            assert!(json.contains(\"0.3\") && json.contains(\"0.5\"));\n        }\n\n        #[test]\n        fn test_roundtrip_row_ratios() {\n            let original = layout_options_with_row_ratios(&[0.4, 0.3]);\n            let json = serde_json::to_string(&original).unwrap();\n            let deserialized: LayoutOptions = serde_json::from_str(&json).unwrap();\n\n            assert_eq!(original.row_ratios, deserialized.row_ratios);\n            assert!(original.column_ratios.is_none());\n        }\n\n        #[test]\n        fn test_roundtrip_both_ratios() {\n            let original = layout_options_with_ratios(&[0.3, 0.4], &[0.5, 0.3]);\n            let json = serde_json::to_string(&original).unwrap();\n            let deserialized: LayoutOptions = serde_json::from_str(&json).unwrap();\n\n            assert_eq!(original.column_ratios, deserialized.column_ratios);\n            assert_eq!(original.row_ratios, deserialized.row_ratios);\n        }\n    }\n\n    mod ratio_constants_tests {\n        use super::*;\n\n        #[test]\n        fn test_constants_valid_ranges() {\n            assert!(MIN_RATIO > 0.0);\n            assert!(MIN_RATIO < MAX_RATIO);\n            assert!(MAX_RATIO < 1.0);\n            assert!(DEFAULT_RATIO >= MIN_RATIO && DEFAULT_RATIO <= MAX_RATIO);\n            assert!(DEFAULT_SECONDARY_RATIO >= MIN_RATIO && DEFAULT_SECONDARY_RATIO <= MAX_RATIO);\n            assert!(MAX_RATIOS >= 1);\n        }\n\n        #[test]\n        fn test_default_ratio_is_half() {\n            assert_eq!(DEFAULT_RATIO, 0.5);\n        }\n\n        #[test]\n        fn test_max_ratios_is_five() {\n            assert_eq!(MAX_RATIOS, 5);\n        }\n    }\n\n    mod layout_options_tests {\n        use super::*;\n\n        #[test]\n        fn test_layout_options_default_values() {\n            let json = r#\"{}\"#;\n            let opts: LayoutOptions = serde_json::from_str(json).unwrap();\n\n            assert!(opts.scrolling.is_none());\n            assert!(opts.grid.is_none());\n            assert!(opts.column_ratios.is_none());\n            assert!(opts.row_ratios.is_none());\n        }\n\n        #[test]\n        fn test_layout_options_with_all_fields() {\n            let json = r#\"{\n                \"scrolling\": {\"columns\": 3},\n                \"grid\": {\"rows\": 2},\n                \"column_ratios\": [0.3, 0.4],\n                \"row_ratios\": [0.5]\n            }\"#;\n            let opts: LayoutOptions = serde_json::from_str(json).unwrap();\n\n            assert!(opts.scrolling.is_some());\n            assert_eq!(opts.scrolling.unwrap().columns, 3);\n            assert!(opts.grid.is_some());\n            assert_eq!(opts.grid.unwrap().rows, 2);\n            assert!(opts.column_ratios.is_some());\n            assert!(opts.row_ratios.is_some());\n        }\n    }\n\n    mod default_layout_tests {\n        use super::*;\n\n        #[test]\n        fn test_cycle_next_covers_all_layouts() {\n            let start = DefaultLayout::BSP;\n            let mut current = start;\n            let mut visited = vec![current];\n\n            loop {\n                current = current.cycle_next();\n                if current == start {\n                    break;\n                }\n                assert!(\n                    !visited.contains(&current),\n                    \"Cycle contains duplicate: {:?}\",\n                    current\n                );\n                visited.push(current);\n            }\n\n            // Should have visited all layouts\n            assert_eq!(visited.len(), 9); // 9 layouts total\n        }\n\n        #[test]\n        fn test_cycle_previous_is_inverse_of_next() {\n            // Note: cycle_previous has some inconsistencies in the current implementation\n            // This test documents the expected behavior for most layouts\n            let layouts_with_correct_inverse = [\n                DefaultLayout::Columns,\n                DefaultLayout::Rows,\n                DefaultLayout::VerticalStack,\n                DefaultLayout::HorizontalStack,\n                DefaultLayout::UltrawideVerticalStack,\n                DefaultLayout::Grid,\n                DefaultLayout::RightMainVerticalStack,\n            ];\n\n            for layout in layouts_with_correct_inverse {\n                let next = layout.cycle_next();\n                assert_eq!(\n                    next.cycle_previous(),\n                    layout,\n                    \"cycle_previous should be inverse of cycle_next for {:?}\",\n                    layout\n                );\n            }\n        }\n\n        #[test]\n        fn test_leftmost_index_standard_layouts() {\n            assert_eq!(DefaultLayout::BSP.leftmost_index(5), 0);\n            assert_eq!(DefaultLayout::Columns.leftmost_index(5), 0);\n            assert_eq!(DefaultLayout::Rows.leftmost_index(5), 0);\n            assert_eq!(DefaultLayout::VerticalStack.leftmost_index(5), 0);\n            assert_eq!(DefaultLayout::HorizontalStack.leftmost_index(5), 0);\n            assert_eq!(DefaultLayout::Grid.leftmost_index(5), 0);\n        }\n\n        #[test]\n        fn test_leftmost_index_ultrawide() {\n            assert_eq!(DefaultLayout::UltrawideVerticalStack.leftmost_index(1), 0);\n            assert_eq!(DefaultLayout::UltrawideVerticalStack.leftmost_index(2), 1);\n            assert_eq!(DefaultLayout::UltrawideVerticalStack.leftmost_index(5), 1);\n        }\n\n        #[test]\n        fn test_leftmost_index_right_main() {\n            assert_eq!(DefaultLayout::RightMainVerticalStack.leftmost_index(1), 0);\n            assert_eq!(DefaultLayout::RightMainVerticalStack.leftmost_index(2), 1);\n            assert_eq!(DefaultLayout::RightMainVerticalStack.leftmost_index(5), 1);\n        }\n\n        #[test]\n        fn test_rightmost_index_standard_layouts() {\n            assert_eq!(DefaultLayout::BSP.rightmost_index(5), 4);\n            assert_eq!(DefaultLayout::Columns.rightmost_index(5), 4);\n            assert_eq!(DefaultLayout::Rows.rightmost_index(5), 4);\n            assert_eq!(DefaultLayout::VerticalStack.rightmost_index(5), 4);\n        }\n\n        #[test]\n        fn test_rightmost_index_right_main() {\n            assert_eq!(DefaultLayout::RightMainVerticalStack.rightmost_index(1), 0);\n            assert_eq!(DefaultLayout::RightMainVerticalStack.rightmost_index(5), 0);\n        }\n\n        #[test]\n        fn test_rightmost_index_ultrawide() {\n            assert_eq!(DefaultLayout::UltrawideVerticalStack.rightmost_index(1), 0);\n            assert_eq!(DefaultLayout::UltrawideVerticalStack.rightmost_index(2), 0);\n            assert_eq!(DefaultLayout::UltrawideVerticalStack.rightmost_index(3), 2);\n            assert_eq!(DefaultLayout::UltrawideVerticalStack.rightmost_index(5), 4);\n        }\n    }\n}\n"
  },
  {
    "path": "komorebi-layouts/src/direction.rs",
    "content": "use super::DefaultLayout;\nuse super::OperationDirection;\n#[cfg(feature = \"win32\")]\nuse super::custom_layout::Column;\n#[cfg(feature = \"win32\")]\nuse super::custom_layout::ColumnSplit;\n#[cfg(feature = \"win32\")]\nuse super::custom_layout::ColumnSplitWithCapacity;\n#[cfg(feature = \"win32\")]\nuse super::custom_layout::CustomLayout;\nuse crate::default_layout::LayoutOptions;\n\npub trait Direction {\n    fn index_in_direction(\n        &self,\n        op_direction: OperationDirection,\n        idx: usize,\n        count: usize,\n        layout_options: Option<LayoutOptions>,\n    ) -> Option<usize>;\n\n    fn is_valid_direction(\n        &self,\n        op_direction: OperationDirection,\n        idx: usize,\n        count: usize,\n        layout_options: Option<LayoutOptions>,\n    ) -> bool;\n    fn up_index(\n        &self,\n        op_direction: Option<OperationDirection>,\n        idx: usize,\n        count: Option<usize>,\n        layout_options: Option<LayoutOptions>,\n    ) -> usize;\n    fn down_index(\n        &self,\n        op_direction: Option<OperationDirection>,\n        idx: usize,\n        count: Option<usize>,\n        layout_options: Option<LayoutOptions>,\n    ) -> usize;\n    fn left_index(\n        &self,\n        op_direction: Option<OperationDirection>,\n        idx: usize,\n        count: Option<usize>,\n        layout_options: Option<LayoutOptions>,\n    ) -> usize;\n    fn right_index(\n        &self,\n        op_direction: Option<OperationDirection>,\n        idx: usize,\n        count: Option<usize>,\n        layout_options: Option<LayoutOptions>,\n    ) -> usize;\n}\n\nimpl Direction for DefaultLayout {\n    fn index_in_direction(\n        &self,\n        op_direction: OperationDirection,\n        idx: usize,\n        count: usize,\n        layout_options: Option<LayoutOptions>,\n    ) -> Option<usize> {\n        match op_direction {\n            OperationDirection::Left => {\n                if self.is_valid_direction(op_direction, idx, count, layout_options) {\n                    Option::from(self.left_index(\n                        Some(op_direction),\n                        idx,\n                        Some(count),\n                        layout_options,\n                    ))\n                } else {\n                    None\n                }\n            }\n            OperationDirection::Right => {\n                if self.is_valid_direction(op_direction, idx, count, layout_options) {\n                    Option::from(self.right_index(\n                        Some(op_direction),\n                        idx,\n                        Some(count),\n                        layout_options,\n                    ))\n                } else {\n                    None\n                }\n            }\n            OperationDirection::Up => {\n                if self.is_valid_direction(op_direction, idx, count, layout_options) {\n                    Option::from(self.up_index(\n                        Some(op_direction),\n                        idx,\n                        Some(count),\n                        layout_options,\n                    ))\n                } else {\n                    None\n                }\n            }\n            OperationDirection::Down => {\n                if self.is_valid_direction(op_direction, idx, count, layout_options) {\n                    Option::from(self.down_index(\n                        Some(op_direction),\n                        idx,\n                        Some(count),\n                        layout_options,\n                    ))\n                } else {\n                    None\n                }\n            }\n        }\n    }\n\n    fn is_valid_direction(\n        &self,\n        op_direction: OperationDirection,\n        idx: usize,\n        count: usize,\n        layout_options: Option<LayoutOptions>,\n    ) -> bool {\n        if count < 2 {\n            return false;\n        }\n\n        match op_direction {\n            OperationDirection::Up => match self {\n                Self::BSP => idx != 0 && idx != 1,\n                Self::Columns => false,\n                Self::Rows | Self::HorizontalStack => idx != 0,\n                Self::VerticalStack | Self::RightMainVerticalStack => idx != 0 && idx != 1,\n                Self::UltrawideVerticalStack => idx > 2,\n                Self::Grid => !is_grid_edge(op_direction, idx, count, layout_options),\n                Self::Scrolling => false,\n            },\n            OperationDirection::Down => match self {\n                Self::BSP => idx != count - 1 && !idx.is_multiple_of(2),\n                Self::Columns => false,\n                Self::Rows => idx != count - 1,\n                Self::VerticalStack | Self::RightMainVerticalStack => idx != 0 && idx != count - 1,\n                Self::HorizontalStack => idx == 0,\n                Self::UltrawideVerticalStack => idx > 1 && idx != count - 1,\n                Self::Grid => !is_grid_edge(op_direction, idx, count, layout_options),\n                Self::Scrolling => false,\n            },\n            OperationDirection::Left => match self {\n                Self::BSP => idx != 0,\n                Self::Columns | Self::VerticalStack => idx != 0,\n                Self::RightMainVerticalStack => idx == 0,\n                Self::Rows => false,\n                Self::HorizontalStack => idx != 0 && idx != 1,\n                Self::UltrawideVerticalStack => idx != 1,\n                Self::Grid => !is_grid_edge(op_direction, idx, count, layout_options),\n                Self::Scrolling => idx != 0,\n            },\n            OperationDirection::Right => match self {\n                Self::BSP => idx.is_multiple_of(2) && idx != count - 1,\n                Self::Columns => idx != count - 1,\n                Self::Rows => false,\n                Self::VerticalStack => idx == 0,\n                Self::RightMainVerticalStack => idx != 0,\n                Self::HorizontalStack => idx != 0 && idx != count - 1,\n                Self::UltrawideVerticalStack => match count {\n                    2 => idx != 0,\n                    _ => idx < 2,\n                },\n                Self::Grid => !is_grid_edge(op_direction, idx, count, layout_options),\n                Self::Scrolling => idx != count - 1,\n            },\n        }\n    }\n\n    fn up_index(\n        &self,\n        op_direction: Option<OperationDirection>,\n        idx: usize,\n        count: Option<usize>,\n        layout_options: Option<LayoutOptions>,\n    ) -> usize {\n        match self {\n            Self::BSP => {\n                if idx.is_multiple_of(2) {\n                    idx - 1\n                } else {\n                    idx - 2\n                }\n            }\n            Self::Columns => unreachable!(),\n            Self::Rows\n            | Self::VerticalStack\n            | Self::UltrawideVerticalStack\n            | Self::RightMainVerticalStack => idx - 1,\n            Self::HorizontalStack => 0,\n            Self::Grid => grid_neighbor(op_direction, idx, count, layout_options),\n            Self::Scrolling => unreachable!(),\n        }\n    }\n\n    fn down_index(\n        &self,\n        op_direction: Option<OperationDirection>,\n        idx: usize,\n        count: Option<usize>,\n        layout_options: Option<LayoutOptions>,\n    ) -> usize {\n        match self {\n            Self::BSP\n            | Self::Rows\n            | Self::VerticalStack\n            | Self::UltrawideVerticalStack\n            | Self::RightMainVerticalStack => idx + 1,\n            Self::Columns => unreachable!(),\n            Self::HorizontalStack => 1,\n            Self::Grid => grid_neighbor(op_direction, idx, count, layout_options),\n            Self::Scrolling => unreachable!(),\n        }\n    }\n\n    fn left_index(\n        &self,\n        op_direction: Option<OperationDirection>,\n        idx: usize,\n        count: Option<usize>,\n        layout_options: Option<LayoutOptions>,\n    ) -> usize {\n        match self {\n            Self::BSP => {\n                if idx.is_multiple_of(2) {\n                    idx - 2\n                } else {\n                    idx - 1\n                }\n            }\n            Self::Columns | Self::HorizontalStack => idx - 1,\n            Self::Rows => unreachable!(),\n            Self::VerticalStack => 0,\n            Self::RightMainVerticalStack => 1,\n            Self::UltrawideVerticalStack => match idx {\n                0 => 1,\n                1 => unreachable!(),\n                _ => 0,\n            },\n            Self::Grid => grid_neighbor(op_direction, idx, count, layout_options),\n            Self::Scrolling => idx - 1,\n        }\n    }\n\n    fn right_index(\n        &self,\n        op_direction: Option<OperationDirection>,\n        idx: usize,\n        count: Option<usize>,\n        layout_options: Option<LayoutOptions>,\n    ) -> usize {\n        match self {\n            Self::BSP | Self::Columns | Self::HorizontalStack => idx + 1,\n            Self::Rows => unreachable!(),\n            Self::VerticalStack => 1,\n            Self::RightMainVerticalStack => 0,\n            Self::UltrawideVerticalStack => match idx {\n                1 => 0,\n                0 => 2,\n                _ => unreachable!(),\n            },\n            Self::Grid => grid_neighbor(op_direction, idx, count, layout_options),\n            Self::Scrolling => idx + 1,\n        }\n    }\n}\n\nstruct GridItem {\n    state: GridItemState,\n    row: usize,\n    num_rows: usize,\n    touching_edges: GridTouchingEdges,\n}\n\nenum GridItemState {\n    Valid,\n    Invalid,\n}\n\n#[allow(clippy::struct_excessive_bools)]\nstruct GridTouchingEdges {\n    left: bool,\n    right: bool,\n    up: bool,\n    down: bool,\n}\n\n#[allow(\n    clippy::cast_possible_truncation,\n    clippy::cast_precision_loss,\n    clippy::cast_sign_loss\n)]\nfn get_grid_item(idx: usize, count: usize, layout_options: Option<LayoutOptions>) -> GridItem {\n    let row_constraint = layout_options.and_then(|o| o.grid.map(|g| g.rows));\n    let num_cols = if let Some(rows) = row_constraint {\n        ((count as f32) / (rows as f32)).ceil() as i32\n    } else {\n        (count as f32).sqrt().ceil() as i32\n    };\n\n    let mut iter = 0;\n\n    for col in 0..num_cols {\n        let remaining_windows = (count - iter) as i32;\n        let remaining_columns = num_cols - col;\n\n        let num_rows_in_this_col = if let Some(rows) = row_constraint {\n            (remaining_windows / remaining_columns).min(rows as i32)\n        } else {\n            remaining_windows / remaining_columns\n        };\n\n        for row in 0..num_rows_in_this_col {\n            if iter == idx {\n                return GridItem {\n                    state: GridItemState::Valid,\n                    row: (row + 1) as usize,\n                    num_rows: num_rows_in_this_col as usize,\n                    touching_edges: GridTouchingEdges {\n                        left: col == 0,\n                        right: col == num_cols - 1,\n                        up: row == 0,\n                        down: row == num_rows_in_this_col - 1,\n                    },\n                };\n            }\n\n            iter += 1;\n        }\n    }\n\n    GridItem {\n        state: GridItemState::Invalid,\n        row: 0,\n        num_rows: 0,\n        touching_edges: GridTouchingEdges {\n            left: true,\n            right: true,\n            up: true,\n            down: true,\n        },\n    }\n}\n\nfn is_grid_edge(\n    op_direction: OperationDirection,\n    idx: usize,\n    count: usize,\n    layout_options: Option<LayoutOptions>,\n) -> bool {\n    let item = get_grid_item(idx, count, layout_options);\n\n    match item.state {\n        GridItemState::Invalid => false,\n        GridItemState::Valid => match op_direction {\n            OperationDirection::Left => item.touching_edges.left,\n            OperationDirection::Right => item.touching_edges.right,\n            OperationDirection::Up => item.touching_edges.up,\n            OperationDirection::Down => item.touching_edges.down,\n        },\n    }\n}\n\nfn grid_neighbor(\n    op_direction: Option<OperationDirection>,\n    idx: usize,\n    count: Option<usize>,\n    layout_options: Option<LayoutOptions>,\n) -> usize {\n    let Some(op_direction) = op_direction else {\n        return 0;\n    };\n\n    let Some(count) = count else {\n        return 0;\n    };\n\n    let item = get_grid_item(idx, count, layout_options);\n\n    match op_direction {\n        OperationDirection::Left => {\n            let item_from_prev_col = get_grid_item(idx - item.row, count, layout_options);\n\n            if item.touching_edges.up && item.num_rows != item_from_prev_col.num_rows {\n                return idx - (item.num_rows - 1);\n            }\n\n            if item.num_rows != item_from_prev_col.num_rows && !item.touching_edges.down {\n                return idx - (item.num_rows - 1);\n            }\n\n            idx - item.num_rows\n        }\n        OperationDirection::Right => idx + item.num_rows,\n        OperationDirection::Up => idx - 1,\n        OperationDirection::Down => idx + 1,\n    }\n}\n\n#[cfg(feature = \"win32\")]\nimpl Direction for CustomLayout {\n    fn index_in_direction(\n        &self,\n        op_direction: OperationDirection,\n        idx: usize,\n        count: usize,\n        layout_options: Option<LayoutOptions>,\n    ) -> Option<usize> {\n        if count <= self.len() {\n            return DefaultLayout::Columns.index_in_direction(\n                op_direction,\n                idx,\n                count,\n                layout_options,\n            );\n        }\n\n        match op_direction {\n            OperationDirection::Left => {\n                if self.is_valid_direction(op_direction, idx, count, layout_options) {\n                    Option::from(self.left_index(None, idx, None, layout_options))\n                } else {\n                    None\n                }\n            }\n            OperationDirection::Right => {\n                if self.is_valid_direction(op_direction, idx, count, layout_options) {\n                    Option::from(self.right_index(None, idx, None, layout_options))\n                } else {\n                    None\n                }\n            }\n            OperationDirection::Up => {\n                if self.is_valid_direction(op_direction, idx, count, layout_options) {\n                    Option::from(self.up_index(None, idx, None, layout_options))\n                } else {\n                    None\n                }\n            }\n            OperationDirection::Down => {\n                if self.is_valid_direction(op_direction, idx, count, layout_options) {\n                    Option::from(self.down_index(None, idx, None, layout_options))\n                } else {\n                    None\n                }\n            }\n        }\n    }\n\n    fn is_valid_direction(\n        &self,\n        op_direction: OperationDirection,\n        idx: usize,\n        count: usize,\n        layout_options: Option<LayoutOptions>,\n    ) -> bool {\n        if count <= self.len() {\n            return DefaultLayout::Columns.is_valid_direction(\n                op_direction,\n                idx,\n                count,\n                layout_options,\n            );\n        }\n\n        match op_direction {\n            OperationDirection::Left => idx != 0 && self.column_for_container_idx(idx) != 0,\n            OperationDirection::Right => {\n                idx != count - 1 && self.column_for_container_idx(idx) != self.len() - 1\n            }\n            OperationDirection::Up => {\n                if idx == 0 {\n                    return false;\n                }\n\n                let (column_idx, column) = self.column_with_idx(idx);\n                column.is_some_and(|column| match column {\n                    Column::Secondary(Some(ColumnSplitWithCapacity::Horizontal(_)))\n                    | Column::Tertiary(ColumnSplit::Horizontal) => {\n                        self.column_for_container_idx(idx - 1) == column_idx\n                    }\n                    _ => false,\n                })\n            }\n            OperationDirection::Down => {\n                if idx == count - 1 {\n                    return false;\n                }\n\n                let (column_idx, column) = self.column_with_idx(idx);\n                column.is_some_and(|column| match column {\n                    Column::Secondary(Some(ColumnSplitWithCapacity::Horizontal(_)))\n                    | Column::Tertiary(ColumnSplit::Horizontal) => {\n                        self.column_for_container_idx(idx + 1) == column_idx\n                    }\n                    _ => false,\n                })\n            }\n        }\n    }\n\n    fn up_index(\n        &self,\n        _op_direction: Option<OperationDirection>,\n        idx: usize,\n        _count: Option<usize>,\n        _layout_options: Option<LayoutOptions>,\n    ) -> usize {\n        idx - 1\n    }\n\n    fn down_index(\n        &self,\n        _op_direction: Option<OperationDirection>,\n        idx: usize,\n        _count: Option<usize>,\n        _layout_options: Option<LayoutOptions>,\n    ) -> usize {\n        idx + 1\n    }\n\n    fn left_index(\n        &self,\n        _op_direction: Option<OperationDirection>,\n        idx: usize,\n        _count: Option<usize>,\n        _layout_options: Option<LayoutOptions>,\n    ) -> usize {\n        let column_idx = self.column_for_container_idx(idx);\n        if column_idx - 1 == 0 {\n            0\n        } else {\n            self.first_container_idx(column_idx - 1)\n        }\n    }\n\n    fn right_index(\n        &self,\n        _op_direction: Option<OperationDirection>,\n        idx: usize,\n        _count: Option<usize>,\n        _layout_options: Option<LayoutOptions>,\n    ) -> usize {\n        let column_idx = self.column_for_container_idx(idx);\n        self.first_container_idx(column_idx + 1)\n    }\n}\n"
  },
  {
    "path": "komorebi-layouts/src/layout.rs",
    "content": "use serde::Deserialize;\nuse serde::Serialize;\n\nuse super::Arrangement;\n#[cfg(feature = \"win32\")]\nuse super::CustomLayout;\nuse super::DefaultLayout;\nuse super::Direction;\n\n#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\npub enum Layout {\n    Default(DefaultLayout),\n    #[cfg(feature = \"win32\")]\n    Custom(CustomLayout),\n}\n\nimpl Layout {\n    #[must_use]\n    pub fn as_boxed_direction(&self) -> Box<dyn Direction> {\n        match self {\n            Layout::Default(layout) => Box::new(*layout),\n            #[cfg(feature = \"win32\")]\n            Layout::Custom(layout) => Box::new(layout.clone()),\n        }\n    }\n\n    #[must_use]\n    pub fn as_boxed_arrangement(&self) -> Box<dyn Arrangement> {\n        match self {\n            Layout::Default(layout) => Box::new(*layout),\n            #[cfg(feature = \"win32\")]\n            Layout::Custom(layout) => Box::new(layout.clone()),\n        }\n    }\n}\n"
  },
  {
    "path": "komorebi-layouts/src/lib.rs",
    "content": "#![warn(clippy::all)]\n#![allow(clippy::missing_errors_doc, clippy::use_self, clippy::doc_markdown)]\n\n//! Layout system for the komorebi window manager.\n//!\n//! This crate provides the core layout algorithms and types for arranging windows\n//! in various configurations. It includes optional Windows-specific functionality\n//! behind the `win32` feature flag.\n\npub mod arrangement;\n#[cfg(feature = \"win32\")]\npub mod custom_layout;\npub mod cycle_direction;\npub mod default_layout;\npub mod direction;\npub mod layout;\npub mod operation_direction;\npub mod rect;\npub mod sizing;\n\npub use arrangement::*;\n#[cfg(feature = \"win32\")]\npub use custom_layout::*;\npub use cycle_direction::*;\npub use default_layout::*;\npub use direction::*;\npub use layout::*;\npub use operation_direction::*;\npub use rect::*;\npub use sizing::*;\n"
  },
  {
    "path": "komorebi-layouts/src/operation_direction.rs",
    "content": "use std::num::NonZeroUsize;\n\nuse super::Axis;\nuse super::direction::Direction;\nuse crate::default_layout::LayoutOptions;\nuse clap::ValueEnum;\nuse serde::Deserialize;\nuse serde::Serialize;\nuse strum::Display;\nuse strum::EnumString;\n\n#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\npub enum OperationDirection {\n    Left,\n    Right,\n    Up,\n    Down,\n}\n\nimpl OperationDirection {\n    #[must_use]\n    pub const fn opposite(self) -> Self {\n        match self {\n            Self::Left => Self::Right,\n            Self::Right => Self::Left,\n            Self::Up => Self::Down,\n            Self::Down => Self::Up,\n        }\n    }\n\n    fn flip(self, layout_flip: Option<Axis>) -> Self {\n        layout_flip.map_or(self, |flip| match self {\n            Self::Left => match flip {\n                Axis::Horizontal | Axis::HorizontalAndVertical => Self::Right,\n                Axis::Vertical => self,\n            },\n            Self::Right => match flip {\n                Axis::Horizontal | Axis::HorizontalAndVertical => Self::Left,\n                Axis::Vertical => self,\n            },\n            Self::Up => match flip {\n                Axis::Vertical | Axis::HorizontalAndVertical => Self::Down,\n                Axis::Horizontal => self,\n            },\n            Self::Down => match flip {\n                Axis::Vertical | Axis::HorizontalAndVertical => Self::Up,\n                Axis::Horizontal => self,\n            },\n        })\n    }\n\n    #[must_use]\n    pub fn destination(\n        self,\n        layout: &dyn Direction,\n        layout_flip: Option<Axis>,\n        idx: usize,\n        len: NonZeroUsize,\n        layout_options: Option<LayoutOptions>,\n    ) -> Option<usize> {\n        layout.index_in_direction(self.flip(layout_flip), idx, len.get(), layout_options)\n    }\n}\n"
  },
  {
    "path": "komorebi-layouts/src/rect.rs",
    "content": "use serde::Deserialize;\nuse serde::Serialize;\n\n#[cfg(feature = \"win32\")]\nuse windows::Win32::Foundation::RECT;\n\n#[cfg(feature = \"darwin\")]\nuse objc2_core_foundation::CGFloat;\n#[cfg(feature = \"darwin\")]\nuse objc2_core_foundation::CGPoint;\n#[cfg(feature = \"darwin\")]\nuse objc2_core_foundation::CGRect;\n#[cfg(feature = \"darwin\")]\nuse objc2_core_foundation::CGSize;\n\n#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, Eq, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Rectangle dimensions\npub struct Rect {\n    /// Left point of the rectangle\n    pub left: i32,\n    /// Top point of the rectangle\n    pub top: i32,\n    /// Width of the recentangle (from the left point)\n    pub right: i32,\n    /// Height of the rectangle (from the top point)\n    pub bottom: i32,\n}\n\n#[cfg(feature = \"win32\")]\nimpl From<RECT> for Rect {\n    fn from(rect: RECT) -> Self {\n        Self {\n            left: rect.left,\n            top: rect.top,\n            right: rect.right - rect.left,\n            bottom: rect.bottom - rect.top,\n        }\n    }\n}\n\n#[cfg(feature = \"win32\")]\nimpl From<Rect> for RECT {\n    fn from(rect: Rect) -> Self {\n        Self {\n            left: rect.left,\n            top: rect.top,\n            right: rect.right,\n            bottom: rect.bottom,\n        }\n    }\n}\n\n#[cfg(feature = \"darwin\")]\nimpl From<CGSize> for Rect {\n    fn from(value: CGSize) -> Self {\n        Self {\n            left: 0,\n            top: 0,\n            right: value.width as i32,\n            bottom: value.height as i32,\n        }\n    }\n}\n\n#[cfg(feature = \"darwin\")]\nimpl From<CGRect> for Rect {\n    fn from(value: CGRect) -> Self {\n        Self {\n            left: value.origin.x as i32,\n            top: value.origin.y as i32,\n            right: value.size.width as i32,\n            bottom: value.size.height as i32,\n        }\n    }\n}\n\n#[cfg(feature = \"darwin\")]\nimpl From<&Rect> for CGRect {\n    fn from(value: &Rect) -> Self {\n        Self {\n            origin: CGPoint {\n                x: value.left as CGFloat,\n                y: value.top as CGFloat,\n            },\n            size: CGSize {\n                width: value.right as CGFloat,\n                height: value.bottom as CGFloat,\n            },\n        }\n    }\n}\n\n#[cfg(feature = \"darwin\")]\nimpl From<Rect> for CGRect {\n    fn from(value: Rect) -> Self {\n        CGRect::from(&value)\n    }\n}\n\nimpl Rect {\n    pub fn is_same_size_as(&self, rhs: &Self) -> bool {\n        self.right == rhs.right && self.bottom == rhs.bottom\n    }\n\n    pub fn has_same_position_as(&self, rhs: &Self) -> bool {\n        self.left == rhs.left && self.top == rhs.top\n    }\n}\n\nimpl Rect {\n    /// decrease the size of self by the padding amount.\n    pub fn add_padding<T>(&mut self, padding: T)\n    where\n        T: Into<Option<i32>>,\n    {\n        if let Some(padding) = padding.into() {\n            self.left += padding;\n            self.top += padding;\n            self.right -= padding * 2;\n            self.bottom -= padding * 2;\n        }\n    }\n\n    /// increase the size of self by the margin amount.\n    pub fn add_margin(&mut self, margin: i32) {\n        self.left -= margin;\n        self.top -= margin;\n        self.right += margin * 2;\n        self.bottom += margin * 2;\n    }\n\n    pub fn left_padding(&mut self, padding: i32) {\n        self.left += padding;\n    }\n\n    pub fn right_padding(&mut self, padding: i32) {\n        self.right -= padding;\n    }\n\n    #[must_use]\n    pub const fn contains_point(&self, point: (i32, i32)) -> bool {\n        point.0 >= self.left\n            && point.0 <= self.left + self.right\n            && point.1 >= self.top\n            && point.1 <= self.top + self.bottom\n    }\n\n    #[must_use]\n    pub const fn scale(&self, system_dpi: i32, rect_dpi: i32) -> Rect {\n        Rect {\n            left: (self.left * rect_dpi) / system_dpi,\n            top: (self.top * rect_dpi) / system_dpi,\n            right: (self.right * rect_dpi) / system_dpi,\n            bottom: (self.bottom * rect_dpi) / system_dpi,\n        }\n    }\n\n    #[cfg(feature = \"win32\")]\n    #[must_use]\n    pub const fn rect(&self) -> RECT {\n        RECT {\n            left: self.left,\n            top: self.top,\n            right: self.left + self.right,\n            bottom: self.top + self.bottom,\n        }\n    }\n\n    #[cfg(feature = \"darwin\")]\n    #[must_use]\n    pub fn percentage_within_horizontal_bounds(&self, other: &Rect) -> f64 {\n        let overlap_left = self.left.max(other.left);\n        let overlap_right = (self.left + self.right).min(other.left + other.right);\n\n        let overlap_width = overlap_right - overlap_left;\n\n        if overlap_width <= 0 {\n            0.0\n        } else {\n            (overlap_width as f64) / (other.right as f64) * 100.0\n        }\n    }\n}\n"
  },
  {
    "path": "komorebi-layouts/src/sizing.rs",
    "content": "use clap::ValueEnum;\nuse serde::Deserialize;\nuse serde::Serialize;\nuse strum::Display;\nuse strum::EnumString;\n\n#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Sizing\npub enum Sizing {\n    /// Increase\n    Increase,\n    /// Decrease\n    Decrease,\n}\n\nimpl Sizing {\n    #[must_use]\n    pub const fn adjust_by(&self, value: i32, adjustment: i32) -> i32 {\n        match self {\n            Self::Increase => value + adjustment,\n            Self::Decrease => {\n                if value > 0 && value - adjustment >= 0 {\n                    value - adjustment\n                } else {\n                    value\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "komorebi-shortcuts/Cargo.toml",
    "content": "[package]\nname = \"komorebi-shortcuts\"\nversion = \"0.1.0\"\nedition = \"2024\"\n\n[dependencies]\nwhkd-parser = { git = \"https://github.com/LGUG2Z/whkd\", rev = \"v0.2.10\" }\nwhkd-core = { git = \"https://github.com/LGUG2Z/whkd\", rev = \"v0.2.10\" }\n\neframe = { workspace = true }\ndirs = { workspace = true }\n"
  },
  {
    "path": "komorebi-shortcuts/src/main.rs",
    "content": "use eframe::egui::ViewportBuilder;\nuse std::path::PathBuf;\nuse whkd_core::Whkdrc;\n\n#[derive(Default)]\nstruct Quicklook {\n    whkdrc: Option<Whkdrc>,\n    filter: String,\n}\n\nimpl Quicklook {\n    fn new(_cc: &eframe::CreationContext<'_>) -> Self {\n        // Customize egui here with cc.egui_ctx.set_fonts and cc.egui_ctx.set_visuals.\n        // Restore app state using cc.storage (requires the \"persistence\" feature).\n        // Use the cc.gl (a glow::Context) to create graphics shaders and buffers that you can use\n        // for e.g. egui::PaintCallback.\n        let mut home = std::env::var(\"WHKD_CONFIG_HOME\").map_or_else(\n            |_| {\n                dirs::home_dir()\n                    .expect(\"no home directory found\")\n                    .join(\".config\")\n            },\n            |home_path| {\n                let home = PathBuf::from(&home_path);\n\n                if home.as_path().is_dir() {\n                    home\n                } else {\n                    panic!(\n                        \"$Env:WHKD_CONFIG_HOME is set to '{home_path}', which is not a valid directory\",\n                    );\n                }\n            },\n        );\n        home.push(\"whkdrc\");\n\n        Self {\n            whkdrc: whkd_parser::load(&home).ok(),\n            filter: String::new(),\n        }\n    }\n}\n\nimpl eframe::App for Quicklook {\n    fn update(&mut self, ctx: &eframe::egui::Context, _frame: &mut eframe::Frame) {\n        eframe::egui::CentralPanel::default().show(ctx, |ui| {\n            ui.set_max_width(ui.available_width());\n            ui.set_max_height(ui.available_height());\n            eframe::egui::ScrollArea::vertical().show(ui, |ui| {\n                eframe::egui::Grid::new(\"grid\")\n                    .num_columns(2)\n                    .striped(true)\n                    .spacing([40.0, 4.0])\n                    .min_col_width(ui.available_width() / 2.0 - 20.0)\n                    .show(ui, |ui| {\n                        if let Some(whkdrc) = &self.whkdrc {\n                            ui.label(\"Filter\");\n                            ui.add(\n                                eframe::egui::text_edit::TextEdit::singleline(&mut self.filter)\n                                    .hint_text(\"Filter by command...\")\n                                    .background_color(ctx.style().visuals.faint_bg_color),\n                            );\n                            ui.end_row();\n\n                            for binding in &whkdrc.bindings {\n                                let keys = binding.keys.join(\" + \");\n                                if self.filter.is_empty() || binding.command.contains(&self.filter)\n                                {\n                                    ui.label(keys);\n                                    ui.label(&binding.command);\n                                    ui.end_row();\n                                }\n                            }\n                        }\n                    });\n            });\n        });\n    }\n}\n\nfn main() {\n    let viewport_builder = ViewportBuilder::default()\n        .with_resizable(true)\n        .with_decorations(false);\n\n    let native_options = eframe::NativeOptions {\n        viewport: viewport_builder,\n        centered: true,\n        ..Default::default()\n    };\n\n    eframe::run_native(\n        \"komorebi-shortcuts\",\n        native_options,\n        Box::new(|cc| Ok(Box::new(Quicklook::new(cc)))),\n    )\n    .unwrap();\n}\n"
  },
  {
    "path": "komorebi-themes/Cargo.toml",
    "content": "[package]\nname = \"komorebi-themes\"\nversion = \"0.1.41\"\nedition = \"2024\"\n\n[dependencies]\nbase16-egui-themes = { git = \"https://github.com/LGUG2Z/base16-egui-themes\", rev = \"b9e26b31f7a0e7ed239b14e5317e95d1bdc544bd\" }\n#catppuccin-egui = { version = \"5\", default-features = false, features = [\"egui32\"] }\ncatppuccin-egui = { git = \"https://github.com/LGUG2Z/catppuccin-egui\", rev = \"b2f95cbf441d1dd99f3c955ef10dcb84ce23c20a\", default-features = false, features = [\n  \"egui33\",\n] }\neframe = { workspace = true }\nschemars = { workspace = true, optional = true }\nserde = { workspace = true }\nserde_variant = \"0.1\"\nstrum = { workspace = true }\nhex_color = { version = \"3\", features = [\"serde\"] }\nflavours = { git = \"https://github.com/LGUG2Z/flavours\", version = \"0.7.2\" }\n\n[features]\ndefault = [\"schemars\"]\nschemars = [\"dep:schemars\"]\n"
  },
  {
    "path": "komorebi-themes/src/colour.rs",
    "content": "use hex_color::HexColor;\n#[cfg(feature = \"schemars\")]\nuse schemars::Schema;\n#[cfg(feature = \"schemars\")]\nuse schemars::SchemaGenerator;\n\nuse crate::Color32;\nuse serde::Deserialize;\nuse serde::Serialize;\n\n#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n#[serde(untagged)]\n/// Colour representation\npub enum Colour {\n    /// Colour represented as RGB\n    Rgb(Rgb),\n    /// Colour represented as Hex\n    Hex(Hex),\n}\n\nimpl From<Rgb> for Colour {\n    fn from(value: Rgb) -> Self {\n        Self::Rgb(value)\n    }\n}\n\nimpl From<u32> for Colour {\n    fn from(value: u32) -> Self {\n        Self::Rgb(Rgb::from(value))\n    }\n}\n\nimpl From<Color32> for Colour {\n    fn from(value: Color32) -> Self {\n        Colour::Rgb(Rgb::new(\n            value.r() as u32,\n            value.g() as u32,\n            value.b() as u32,\n        ))\n    }\n}\n\nimpl From<Colour> for Color32 {\n    fn from(value: Colour) -> Self {\n        match value {\n            Colour::Rgb(rgb) => Color32::from_rgb(rgb.r as u8, rgb.g as u8, rgb.b as u8),\n            Colour::Hex(hex) => {\n                let rgb = Rgb::from(hex);\n                Color32::from_rgb(rgb.r as u8, rgb.g as u8, rgb.b as u8)\n            }\n        }\n    }\n}\n\n/// Colour represented as a Hex string\n#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)]\npub struct Hex(pub HexColor);\n\n#[cfg(feature = \"schemars\")]\nimpl schemars::JsonSchema for Hex {\n    fn schema_name() -> std::borrow::Cow<'static, str> {\n        std::borrow::Cow::Borrowed(\"Hex\")\n    }\n\n    fn json_schema(_: &mut SchemaGenerator) -> Schema {\n        schemars::json_schema!({\n            \"type\": \"string\",\n            \"format\": \"color-hex\",\n            \"description\": \"Colour represented as a Hex string\"\n        })\n    }\n}\n\nimpl From<Colour> for u32 {\n    fn from(value: Colour) -> Self {\n        match value {\n            Colour::Rgb(val) => val.into(),\n            Colour::Hex(val) => (Rgb::from(val)).into(),\n        }\n    }\n}\n\n#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Colour represented as RGB\npub struct Rgb {\n    /// Red\n    pub r: u32,\n    /// Green\n    pub g: u32,\n    /// Blue\n    pub b: u32,\n}\n\nimpl Rgb {\n    pub const fn new(r: u32, g: u32, b: u32) -> Self {\n        Self { r, g, b }\n    }\n}\n\nimpl From<Hex> for Rgb {\n    fn from(value: Hex) -> Self {\n        value.0.into()\n    }\n}\n\nimpl From<HexColor> for Rgb {\n    fn from(value: HexColor) -> Self {\n        Self {\n            r: value.r as u32,\n            g: value.g as u32,\n            b: value.b as u32,\n        }\n    }\n}\n\nimpl From<Rgb> for u32 {\n    fn from(value: Rgb) -> Self {\n        value.r | (value.g << 8) | (value.b << 16)\n    }\n}\n\nimpl From<u32> for Rgb {\n    fn from(value: u32) -> Self {\n        Self {\n            r: value & 0xff,\n            g: (value >> 8) & 0xff,\n            b: (value >> 16) & 0xff,\n        }\n    }\n}\n"
  },
  {
    "path": "komorebi-themes/src/generator.rs",
    "content": "use crate::Base16ColourPalette;\nuse crate::colour::Colour;\nuse crate::colour::Hex;\nuse hex_color::HexColor;\nuse std::collections::VecDeque;\nuse std::fmt::Display;\nuse std::fmt::Formatter;\nuse std::path::Path;\n\nuse serde::Deserialize;\nuse serde::Serialize;\n\n#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(schemars::JsonSchema))]\n/// Theme variant\npub enum ThemeVariant {\n    #[default]\n    /// Dark variant\n    Dark,\n    /// Light variant\n    Light,\n}\n\nimpl Display for ThemeVariant {\n    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {\n        match self {\n            ThemeVariant::Dark => write!(f, \"dark\"),\n            ThemeVariant::Light => write!(f, \"light\"),\n        }\n    }\n}\n\nimpl From<ThemeVariant> for flavours::operations::generate::Mode {\n    fn from(value: ThemeVariant) -> Self {\n        match value {\n            ThemeVariant::Dark => Self::Dark,\n            ThemeVariant::Light => Self::Light,\n        }\n    }\n}\n\npub fn generate_base16_palette(\n    image_path: &Path,\n    variant: ThemeVariant,\n) -> Result<Base16ColourPalette, hex_color::ParseHexColorError> {\n    Base16ColourPalette::try_from(\n        &flavours::operations::generate::generate(image_path, variant.into(), false)\n            .unwrap_or_default(),\n    )\n}\n\nimpl TryFrom<&VecDeque<String>> for Base16ColourPalette {\n    type Error = hex_color::ParseHexColorError;\n\n    fn try_from(value: &VecDeque<String>) -> Result<Self, Self::Error> {\n        let fixed = value.iter().map(|s| format!(\"#{s}\")).collect::<Vec<_>>();\n        if fixed.len() != 16 {\n            return Err(hex_color::ParseHexColorError::Empty);\n        }\n\n        Ok(Self {\n            base_00: Colour::Hex(Hex(HexColor::parse(&fixed[0])?)),\n            base_01: Colour::Hex(Hex(HexColor::parse(&fixed[1])?)),\n            base_02: Colour::Hex(Hex(HexColor::parse(&fixed[2])?)),\n            base_03: Colour::Hex(Hex(HexColor::parse(&fixed[3])?)),\n            base_04: Colour::Hex(Hex(HexColor::parse(&fixed[4])?)),\n            base_05: Colour::Hex(Hex(HexColor::parse(&fixed[5])?)),\n            base_06: Colour::Hex(Hex(HexColor::parse(&fixed[6])?)),\n            base_07: Colour::Hex(Hex(HexColor::parse(&fixed[7])?)),\n            base_08: Colour::Hex(Hex(HexColor::parse(&fixed[8])?)),\n            base_09: Colour::Hex(Hex(HexColor::parse(&fixed[9])?)),\n            base_0a: Colour::Hex(Hex(HexColor::parse(&fixed[10])?)),\n            base_0b: Colour::Hex(Hex(HexColor::parse(&fixed[11])?)),\n            base_0c: Colour::Hex(Hex(HexColor::parse(&fixed[12])?)),\n            base_0d: Colour::Hex(Hex(HexColor::parse(&fixed[13])?)),\n            base_0e: Colour::Hex(Hex(HexColor::parse(&fixed[14])?)),\n            base_0f: Colour::Hex(Hex(HexColor::parse(&fixed[15])?)),\n        })\n    }\n}\n"
  },
  {
    "path": "komorebi-themes/src/lib.rs",
    "content": "#![warn(clippy::all)]\n#![allow(clippy::missing_errors_doc)]\n\npub mod colour;\nmod generator;\n\npub use generator::ThemeVariant;\npub use generator::generate_base16_palette;\n\nuse schemars::JsonSchema;\nuse serde::Deserialize;\nuse serde::Serialize;\nuse strum::Display;\nuse strum::IntoEnumIterator;\n\nuse crate::colour::Colour;\npub use base16_egui_themes::Base16;\npub use catppuccin_egui;\npub use eframe::egui::Color32;\nuse eframe::egui::Shadow;\nuse eframe::egui::Stroke;\nuse eframe::egui::Style;\nuse eframe::egui::Visuals;\nuse eframe::egui::style::Selection;\nuse eframe::egui::style::WidgetVisuals;\nuse eframe::egui::style::Widgets;\nuse serde_variant::to_variant_name;\n\n#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]\n#[serde(tag = \"type\")]\n/// Theme\npub enum Theme {\n    /// Theme from catppuccin-egui\n    Catppuccin {\n        name: Catppuccin,\n        accent: Option<CatppuccinValue>,\n    },\n    /// Theme from base16-egui-themes\n    Base16 {\n        name: Base16,\n        accent: Option<Base16Value>,\n    },\n    /// Custom base16 palette\n    Custom {\n        palette: Box<Base16ColourPalette>,\n        accent: Option<Base16Value>,\n    },\n}\n\n#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]\n/// Base16 colour palette: https://github.com/chriskempson/base16\npub struct Base16ColourPalette {\n    /// Base00\n    pub base_00: Colour,\n    /// Base01\n    pub base_01: Colour,\n    /// Base02\n    pub base_02: Colour,\n    /// Base03\n    pub base_03: Colour,\n    /// Base04\n    pub base_04: Colour,\n    /// Base05\n    pub base_05: Colour,\n    /// Base06\n    pub base_06: Colour,\n    /// Base07\n    pub base_07: Colour,\n    /// Base08\n    pub base_08: Colour,\n    /// Base09\n    pub base_09: Colour,\n    /// Base0A\n    pub base_0a: Colour,\n    /// Base0B\n    pub base_0b: Colour,\n    /// Base0C\n    pub base_0c: Colour,\n    /// Base0D\n    pub base_0d: Colour,\n    /// Base0E\n    pub base_0e: Colour,\n    /// Base0F\n    pub base_0f: Colour,\n}\n\nimpl Base16ColourPalette {\n    pub fn background(self) -> Color32 {\n        self.base_01.into()\n    }\n    pub fn style(self) -> Style {\n        let original = Style::default();\n        Style {\n            visuals: Visuals {\n                widgets: Widgets {\n                    noninteractive: WidgetVisuals {\n                        bg_fill: self.base_01.into(),\n                        weak_bg_fill: self.base_01.into(),\n                        bg_stroke: Stroke {\n                            color: self.base_02.into(),\n                            ..original.visuals.widgets.noninteractive.bg_stroke\n                        },\n                        fg_stroke: Stroke {\n                            color: self.base_05.into(),\n                            ..original.visuals.widgets.noninteractive.fg_stroke\n                        },\n                        ..original.visuals.widgets.noninteractive\n                    },\n                    inactive: WidgetVisuals {\n                        bg_fill: self.base_02.into(),\n                        weak_bg_fill: self.base_02.into(),\n                        bg_stroke: Stroke {\n                            color: Color32::from_rgba_premultiplied(0, 0, 0, 0),\n                            ..original.visuals.widgets.inactive.bg_stroke\n                        },\n                        fg_stroke: Stroke {\n                            color: self.base_05.into(),\n                            ..original.visuals.widgets.inactive.fg_stroke\n                        },\n                        ..original.visuals.widgets.inactive\n                    },\n                    hovered: WidgetVisuals {\n                        bg_fill: self.base_02.into(),\n                        weak_bg_fill: self.base_02.into(),\n                        bg_stroke: Stroke {\n                            color: self.base_03.into(),\n                            ..original.visuals.widgets.hovered.bg_stroke\n                        },\n                        fg_stroke: Stroke {\n                            color: self.base_06.into(),\n                            ..original.visuals.widgets.hovered.fg_stroke\n                        },\n                        ..original.visuals.widgets.hovered\n                    },\n                    active: WidgetVisuals {\n                        bg_fill: self.base_02.into(),\n                        weak_bg_fill: self.base_02.into(),\n                        bg_stroke: Stroke {\n                            color: self.base_03.into(),\n                            ..original.visuals.widgets.hovered.bg_stroke\n                        },\n                        fg_stroke: Stroke {\n                            color: self.base_06.into(),\n                            ..original.visuals.widgets.hovered.fg_stroke\n                        },\n                        ..original.visuals.widgets.active\n                    },\n                    open: WidgetVisuals {\n                        bg_fill: self.base_01.into(),\n                        weak_bg_fill: self.base_01.into(),\n                        bg_stroke: Stroke {\n                            color: self.base_02.into(),\n                            ..original.visuals.widgets.open.bg_stroke\n                        },\n                        fg_stroke: Stroke {\n                            color: self.base_06.into(),\n                            ..original.visuals.widgets.open.fg_stroke\n                        },\n                        ..original.visuals.widgets.open\n                    },\n                },\n                selection: Selection {\n                    bg_fill: self.base_02.into(),\n                    stroke: Stroke {\n                        color: self.base_06.into(),\n                        ..original.visuals.selection.stroke\n                    },\n                },\n                hyperlink_color: self.base_08.into(),\n                faint_bg_color: Color32::from_rgba_premultiplied(0, 0, 0, 0),\n                extreme_bg_color: self.base_00.into(),\n                code_bg_color: self.base_02.into(),\n                warn_fg_color: self.base_0c.into(),\n                error_fg_color: self.base_0b.into(),\n                window_shadow: Shadow {\n                    color: Color32::from_rgba_premultiplied(0, 0, 0, 96),\n                    ..original.visuals.window_shadow\n                },\n                window_fill: self.base_01.into(),\n                window_stroke: Stroke {\n                    color: self.base_02.into(),\n                    ..original.visuals.window_stroke\n                },\n                panel_fill: self.base_01.into(),\n                popup_shadow: Shadow {\n                    color: Color32::from_rgba_premultiplied(0, 0, 0, 96),\n                    ..original.visuals.popup_shadow\n                },\n                ..original.visuals\n            },\n            ..original\n        }\n    }\n}\n\nimpl Theme {\n    pub fn variant_names(&self) -> Vec<String> {\n        match self {\n            Theme::Catppuccin { .. } => {\n                vec![\n                    \"Frappe\".to_string(),\n                    \"Latte\".to_string(),\n                    \"Macchiato\".to_string(),\n                    \"Mocha\".to_string(),\n                ]\n            }\n            Theme::Base16 { .. } => Base16::iter()\n                .map(|variant| {\n                    to_variant_name(&variant)\n                        .expect(\"could not convert to variant name\")\n                        .to_string()\n                })\n                .collect(),\n            Theme::Custom { .. } => vec![\"Custom\".to_string()],\n        }\n    }\n}\n\n#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, Display, PartialEq)]\n/// Base16 value\npub enum Base16Value {\n    /// Base00\n    Base00,\n    /// Base01\n    Base01,\n    /// Base02\n    Base02,\n    /// Base03\n    Base03,\n    /// Base04\n    Base04,\n    /// Base05\n    Base05,\n    /// Base06\n    #[default]\n    Base06,\n    /// Base07\n    Base07,\n    /// Base08\n    Base08,\n    /// Base09\n    Base09,\n    /// Base0A\n    Base0A,\n    /// Base0B\n    Base0B,\n    /// Base0C\n    Base0C,\n    /// Base0D\n    Base0D,\n    /// Base0E\n    Base0E,\n    /// Base0F\n    Base0F,\n}\n\n/// Wrapper around a Base16 colour palette\npub enum Base16Wrapper {\n    /// Predefined Base16 colour palette\n    Base16(Base16),\n    /// Custom Base16 colour palette\n    Custom(Box<Base16ColourPalette>),\n}\n\nimpl Base16Value {\n    pub fn color32(&self, theme: Base16Wrapper) -> Color32 {\n        match theme {\n            Base16Wrapper::Base16(theme) => match self {\n                Base16Value::Base00 => theme.base00(),\n                Base16Value::Base01 => theme.base01(),\n                Base16Value::Base02 => theme.base02(),\n                Base16Value::Base03 => theme.base03(),\n                Base16Value::Base04 => theme.base04(),\n                Base16Value::Base05 => theme.base05(),\n                Base16Value::Base06 => theme.base06(),\n                Base16Value::Base07 => theme.base07(),\n                Base16Value::Base08 => theme.base08(),\n                Base16Value::Base09 => theme.base09(),\n                Base16Value::Base0A => theme.base0a(),\n                Base16Value::Base0B => theme.base0b(),\n                Base16Value::Base0C => theme.base0c(),\n                Base16Value::Base0D => theme.base0d(),\n                Base16Value::Base0E => theme.base0e(),\n                Base16Value::Base0F => theme.base0f(),\n            },\n            Base16Wrapper::Custom(colours) => match self {\n                Base16Value::Base00 => colours.base_00.into(),\n                Base16Value::Base01 => colours.base_01.into(),\n                Base16Value::Base02 => colours.base_02.into(),\n                Base16Value::Base03 => colours.base_03.into(),\n                Base16Value::Base04 => colours.base_04.into(),\n                Base16Value::Base05 => colours.base_05.into(),\n                Base16Value::Base06 => colours.base_06.into(),\n                Base16Value::Base07 => colours.base_07.into(),\n                Base16Value::Base08 => colours.base_08.into(),\n                Base16Value::Base09 => colours.base_09.into(),\n                Base16Value::Base0A => colours.base_0a.into(),\n                Base16Value::Base0B => colours.base_0b.into(),\n                Base16Value::Base0C => colours.base_0c.into(),\n                Base16Value::Base0D => colours.base_0d.into(),\n                Base16Value::Base0E => colours.base_0e.into(),\n                Base16Value::Base0F => colours.base_0f.into(),\n            },\n        }\n    }\n}\n\n#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, Display, PartialEq)]\n/// Catppuccin palette\npub enum Catppuccin {\n    /// Frappe (https://catppuccin.com/palette#flavor-frappe)\n    Frappe,\n    /// Latte (https://catppuccin.com/palette#flavor-latte)\n    Latte,\n    /// Macchiato (https://catppuccin.com/palette#flavor-macchiato)\n    Macchiato,\n    /// Mocha (https://catppuccin.com/palette#flavor-mocha)\n    Mocha,\n}\n\nimpl Catppuccin {\n    pub fn as_theme(self) -> catppuccin_egui::Theme {\n        self.into()\n    }\n}\n\nimpl From<Catppuccin> for catppuccin_egui::Theme {\n    fn from(val: Catppuccin) -> Self {\n        match val {\n            Catppuccin::Frappe => catppuccin_egui::FRAPPE,\n            Catppuccin::Latte => catppuccin_egui::LATTE,\n            Catppuccin::Macchiato => catppuccin_egui::MACCHIATO,\n            Catppuccin::Mocha => catppuccin_egui::MOCHA,\n        }\n    }\n}\n\n#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, Display, PartialEq)]\n/// Catppuccin Value\npub enum CatppuccinValue {\n    /// Rosewater\n    Rosewater,\n    /// Flamingo\n    Flamingo,\n    /// Pink\n    Pink,\n    /// Mauve\n    Mauve,\n    /// Red\n    Red,\n    /// Maroon\n    Maroon,\n    /// Peach\n    Peach,\n    /// Yellow\n    Yellow,\n    /// Green\n    Green,\n    /// Teal\n    Teal,\n    /// Sky\n    Sky,\n    /// Sapphire\n    Sapphire,\n    /// Blue\n    Blue,\n    /// Lavender\n    Lavender,\n    #[default]\n    /// Text\n    Text,\n    /// Subtext1\n    Subtext1,\n    /// Subtext0\n    Subtext0,\n    /// Overlay2\n    Overlay2,\n    /// Overlay1\n    Overlay1,\n    /// Overlay0\n    Overlay0,\n    /// Surface2\n    Surface2,\n    /// Surface1\n    Surface1,\n    /// Surface0\n    Surface0,\n    /// Base\n    Base,\n    /// Mantle\n    Mantle,\n    /// Crust\n    Crust,\n}\n\npub fn color32_compat(rgba: [u8; 4]) -> Color32 {\n    Color32::from_rgba_unmultiplied(rgba[0], rgba[1], rgba[2], rgba[3])\n}\n\nimpl CatppuccinValue {\n    pub fn color32(&self, theme: catppuccin_egui::Theme) -> Color32 {\n        match self {\n            CatppuccinValue::Rosewater => color32_compat(theme.rosewater.to_srgba_unmultiplied()),\n            CatppuccinValue::Flamingo => color32_compat(theme.flamingo.to_srgba_unmultiplied()),\n            CatppuccinValue::Pink => color32_compat(theme.pink.to_srgba_unmultiplied()),\n            CatppuccinValue::Mauve => color32_compat(theme.mauve.to_srgba_unmultiplied()),\n            CatppuccinValue::Red => color32_compat(theme.red.to_srgba_unmultiplied()),\n            CatppuccinValue::Maroon => color32_compat(theme.maroon.to_srgba_unmultiplied()),\n            CatppuccinValue::Peach => color32_compat(theme.peach.to_srgba_unmultiplied()),\n            CatppuccinValue::Yellow => color32_compat(theme.yellow.to_srgba_unmultiplied()),\n            CatppuccinValue::Green => color32_compat(theme.green.to_srgba_unmultiplied()),\n            CatppuccinValue::Teal => color32_compat(theme.teal.to_srgba_unmultiplied()),\n            CatppuccinValue::Sky => color32_compat(theme.sky.to_srgba_unmultiplied()),\n            CatppuccinValue::Sapphire => color32_compat(theme.sapphire.to_srgba_unmultiplied()),\n            CatppuccinValue::Blue => color32_compat(theme.blue.to_srgba_unmultiplied()),\n            CatppuccinValue::Lavender => color32_compat(theme.lavender.to_srgba_unmultiplied()),\n            CatppuccinValue::Text => color32_compat(theme.text.to_srgba_unmultiplied()),\n            CatppuccinValue::Subtext1 => color32_compat(theme.subtext1.to_srgba_unmultiplied()),\n            CatppuccinValue::Subtext0 => color32_compat(theme.subtext0.to_srgba_unmultiplied()),\n            CatppuccinValue::Overlay2 => color32_compat(theme.overlay2.to_srgba_unmultiplied()),\n            CatppuccinValue::Overlay1 => color32_compat(theme.overlay1.to_srgba_unmultiplied()),\n            CatppuccinValue::Overlay0 => color32_compat(theme.overlay0.to_srgba_unmultiplied()),\n            CatppuccinValue::Surface2 => color32_compat(theme.surface2.to_srgba_unmultiplied()),\n            CatppuccinValue::Surface1 => color32_compat(theme.surface1.to_srgba_unmultiplied()),\n            CatppuccinValue::Surface0 => color32_compat(theme.surface0.to_srgba_unmultiplied()),\n            CatppuccinValue::Base => color32_compat(theme.base.to_srgba_unmultiplied()),\n            CatppuccinValue::Mantle => color32_compat(theme.mantle.to_srgba_unmultiplied()),\n            CatppuccinValue::Crust => color32_compat(theme.crust.to_srgba_unmultiplied()),\n        }\n    }\n}\n\n#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(JsonSchema))]\n/// Theme from catppuccin-egui\npub struct KomorebiThemeCatppuccin {\n    /// Name of the Catppuccin theme (previews: https://github.com/catppuccin/catppuccin)\n    pub name: Catppuccin,\n    /// Single window border colour\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = CatppuccinValue::Blue)))]\n    pub single_border: Option<CatppuccinValue>,\n    /// Stack window border colour\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = CatppuccinValue::Green)))]\n    pub stack_border: Option<CatppuccinValue>,\n    /// Monocle window border colour\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = CatppuccinValue::Pink)))]\n    pub monocle_border: Option<CatppuccinValue>,\n    /// Floating window border colour\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = CatppuccinValue::Yellow)))]\n    pub floating_border: Option<CatppuccinValue>,\n    /// Unfocused window border colour\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = CatppuccinValue::Base)))]\n    pub unfocused_border: Option<CatppuccinValue>,\n    /// Unfocused locked window border colour\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = CatppuccinValue::Red)))]\n    pub unfocused_locked_border: Option<CatppuccinValue>,\n    #[cfg(target_os = \"windows\")]\n    /// Stackbar focused text colour\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = CatppuccinValue::Green)))]\n    pub stackbar_focused_text: Option<CatppuccinValue>,\n    #[cfg(target_os = \"windows\")]\n    /// Stackbar unfocused text colour\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = CatppuccinValue::Text)))]\n    pub stackbar_unfocused_text: Option<CatppuccinValue>,\n    #[cfg(target_os = \"windows\")]\n    /// Stackbar background colour\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = CatppuccinValue::Base)))]\n    pub stackbar_background: Option<CatppuccinValue>,\n    /// Bar accent colour\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = CatppuccinValue::Blue)))]\n    pub bar_accent: Option<CatppuccinValue>,\n}\n\n#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(JsonSchema))]\n/// Theme from base16-egui-themes\npub struct KomorebiThemeBase16 {\n    /// Name of the Base16 theme (theme previews: https://tinted-theming.github.io/tinted-gallery/)\n    pub name: Base16,\n    /// Single window border colour\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = Base16Value::Base0D)))]\n    pub single_border: Option<Base16Value>,\n    /// Stack window border colour\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = Base16Value::Base0B)))]\n    pub stack_border: Option<Base16Value>,\n    /// Monocle window border colour\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = Base16Value::Base0F)))]\n    pub monocle_border: Option<Base16Value>,\n    /// Floating window border colour\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = Base16Value::Base09)))]\n    pub floating_border: Option<Base16Value>,\n    /// Unfocused window border colour\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = Base16Value::Base01)))]\n    pub unfocused_border: Option<Base16Value>,\n    /// Unfocused locked window border colour\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = Base16Value::Base08)))]\n    pub unfocused_locked_border: Option<Base16Value>,\n    #[cfg(target_os = \"windows\")]\n    /// Stackbar focused text colour\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = Base16Value::Base0B)))]\n    pub stackbar_focused_text: Option<Base16Value>,\n    #[cfg(target_os = \"windows\")]\n    /// Stackbar unfocused text colour\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = Base16Value::Base05)))]\n    pub stackbar_unfocused_text: Option<Base16Value>,\n    #[cfg(target_os = \"windows\")]\n    /// Stackbar background colour\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = Base16Value::Base01)))]\n    pub stackbar_background: Option<Base16Value>,\n    /// Bar accent colour\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = Base16Value::Base0D)))]\n    pub bar_accent: Option<Base16Value>,\n}\n\n#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(JsonSchema))]\n/// Custom Base16 theme\npub struct KomorebiThemeCustom {\n    /// Colours of the custom Base16 theme palette\n    pub colours: Box<Base16ColourPalette>,\n    /// Single window border colour\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = Base16Value::Base0D)))]\n    pub single_border: Option<Base16Value>,\n    /// Stack window border colour\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = Base16Value::Base0B)))]\n    pub stack_border: Option<Base16Value>,\n    /// Monocle window border colour\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = Base16Value::Base0F)))]\n    pub monocle_border: Option<Base16Value>,\n    /// Floating window border colour\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = Base16Value::Base09)))]\n    pub floating_border: Option<Base16Value>,\n    /// Unfocused window border colour\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = Base16Value::Base01)))]\n    pub unfocused_border: Option<Base16Value>,\n    /// Unfocused locked window border colour\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = Base16Value::Base08)))]\n    pub unfocused_locked_border: Option<Base16Value>,\n    #[cfg(target_os = \"windows\")]\n    /// Stackbar focused text colour\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = Base16Value::Base0B)))]\n    pub stackbar_focused_text: Option<Base16Value>,\n    #[cfg(target_os = \"windows\")]\n    /// Stackbar unfocused text colour\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = Base16Value::Base05)))]\n    pub stackbar_unfocused_text: Option<Base16Value>,\n    #[cfg(target_os = \"windows\")]\n    /// Stackbar background colour\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = Base16Value::Base01)))]\n    pub stackbar_background: Option<Base16Value>,\n    /// Bar accent colour\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = Base16Value::Base0D)))]\n    pub bar_accent: Option<Base16Value>,\n}\n\n#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(JsonSchema))]\n#[serde(tag = \"palette\")]\n/// Komorebi theme\npub enum KomorebiTheme {\n    #[cfg_attr(feature = \"schemars\", schemars(title = \"Catppuccin\"))]\n    /// Theme from catppuccin-egui\n    Catppuccin(KomorebiThemeCatppuccin),\n    #[cfg_attr(feature = \"schemars\", schemars(title = \"Base16\"))]\n    /// Theme from base16-egui-themes\n    Base16(KomorebiThemeBase16),\n    #[cfg_attr(feature = \"schemars\", schemars(title = \"Custom\"))]\n    /// Custom Base16 theme\n    Custom(KomorebiThemeCustom),\n}\n\n#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(JsonSchema))]\n/// Theme from catppuccin-egui\npub struct KomobarThemeCatppuccin {\n    /// Name of the Catppuccin theme (previews: https://github.com/catppuccin/catppuccin)\n    pub name: Catppuccin,\n    /// Accent colour\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = CatppuccinValue::Blue)))]\n    pub accent: Option<CatppuccinValue>,\n    /// Auto select fill colour\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub auto_select_fill: Option<CatppuccinValue>,\n    /// Auto select text colour\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub auto_select_text: Option<CatppuccinValue>,\n}\n\n#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(JsonSchema))]\n/// Theme from base16-egui-themes\npub struct KomobarThemeBase16 {\n    /// Name of the Base16 theme (previews: https://tinted-theming.github.io/tinted-gallery/)\n    pub name: Base16,\n    /// Accent colour\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = Base16Value::Base0D)))]\n    pub accent: Option<Base16Value>,\n    /// Auto select fill colour\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub auto_select_fill: Option<Base16Value>,\n    /// Auto select text colour\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub auto_select_text: Option<Base16Value>,\n}\n\n#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]\n#[cfg_attr(feature = \"schemars\", derive(JsonSchema))]\n/// Theme from base16-egui-themes\npub struct KomobarThemeCustom {\n    /// Colours of the custom Base16 theme palette\n    pub colours: Box<Base16ColourPalette>,\n    /// Accent colour\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[cfg_attr(feature = \"schemars\", schemars(extend(\"default\" = CatppuccinValue::Blue)))]\n    pub accent: Option<Base16Value>,\n    /// Auto select fill colour\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub auto_select_fill: Option<Base16Value>,\n    /// Auto select text colour\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub auto_select_text: Option<Base16Value>,\n}\n\n#[derive(Clone, Debug, Serialize, Deserialize)]\n#[cfg_attr(feature = \"schemars\", derive(JsonSchema))]\n#[serde(tag = \"palette\")]\n/// Komorebi bar theme\npub enum KomobarTheme {\n    #[cfg_attr(feature = \"schemars\", schemars(title = \"Catppuccin\"))]\n    /// Theme from catppuccin-egui\n    Catppuccin(KomobarThemeCatppuccin),\n    #[cfg_attr(feature = \"schemars\", schemars(title = \"Base16\"))]\n    /// Theme from base16-egui-themes\n    Base16(KomobarThemeBase16),\n    #[cfg_attr(feature = \"schemars\", schemars(title = \"Custom\"))]\n    /// Custom Base16 theme\n    Custom(KomobarThemeCustom),\n}\n\nimpl From<KomorebiTheme> for KomobarTheme {\n    fn from(value: KomorebiTheme) -> Self {\n        match value {\n            KomorebiTheme::Catppuccin(KomorebiThemeCatppuccin {\n                name, bar_accent, ..\n            }) => Self::Catppuccin(KomobarThemeCatppuccin {\n                name,\n                accent: bar_accent,\n                auto_select_fill: None,\n                auto_select_text: None,\n            }),\n            KomorebiTheme::Base16(KomorebiThemeBase16 {\n                name, bar_accent, ..\n            }) => Self::Base16(KomobarThemeBase16 {\n                name,\n                accent: bar_accent,\n                auto_select_fill: None,\n                auto_select_text: None,\n            }),\n            KomorebiTheme::Custom(KomorebiThemeCustom {\n                colours,\n                bar_accent,\n                ..\n            }) => Self::Custom(KomobarThemeCustom {\n                colours,\n                accent: bar_accent,\n                auto_select_fill: None,\n                auto_select_text: None,\n            }),\n        }\n    }\n}\n"
  },
  {
    "path": "komorebic/Cargo.toml",
    "content": "[package]\nname = \"komorebic\"\nversion = \"0.1.41\"\ndescription = \"The command-line interface for Komorebi, a tiling window manager for Windows\"\nrepository = \"https://github.com/LGUG2Z/komorebi\"\nedition = \"2024\"\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\nkomorebi-client = { path = \"../komorebi-client\", default-features = false }\n\nchrono = { workspace = true }\nclap = { workspace = true }\ncolor-eyre = { workspace = true }\ndirs = { workspace = true }\ndunce = { workspace = true }\nfs-tail = \"0.1\"\nlazy_static = { workspace = true }\nmiette = { version = \"7\", features = [\"fancy\"] }\nopen = \"5\"\npaste = { workspace = true }\npowershell_script = \"1.0\"\nreqwest = { version = \"0.12\", features = [\"blocking\"] }\nschemars = { workspace = true, optional = true }\nserde = { workspace = true }\nserde_json = { workspace = true }\nshadow-rs = { workspace = true }\nsysinfo = { workspace = true }\nthiserror = \"2\"\nwhich = { workspace = true }\nwin-msgbox = \"0.2\"\nwindows = { workspace = true }\n\n[build-dependencies]\nreqwest = { version = \"0.12\", features = [\"blocking\"] }\nshadow-rs = { workspace = true }\n\n[features]\ndefault = [\"schemars\"]\nschemars = [\"dep:schemars\", \"komorebi-client/default\"]\n\n[lints.rust]\nunexpected_cfgs = { level = \"warn\", check-cfg = ['cfg(FALSE)'] }\n"
  },
  {
    "path": "komorebic/build.rs",
    "content": "use shadow_rs::ShadowBuilder;\n\nfn main() {\n    if std::fs::metadata(\"applications.json\").is_err() {\n        let applications_json = reqwest::blocking::get(\n        \"https://raw.githubusercontent.com/LGUG2Z/komorebi-application-specific-configuration/master/applications.json\"\n    ).unwrap().text().unwrap();\n        std::fs::write(\"applications.json\", applications_json).unwrap();\n    }\n\n    ShadowBuilder::builder().build().unwrap();\n}\n"
  },
  {
    "path": "komorebic/src/main.rs",
    "content": "#![warn(clippy::all)]\n#![allow(clippy::missing_errors_doc, clippy::doc_markdown)]\n#![allow(unused_assignments)] // false positives for the error reporter\n\nuse chrono::Utc;\nuse komorebi_client::PathExt;\nuse komorebi_client::replace_env_in_path;\nuse komorebi_client::splash;\nuse std::fs::File;\nuse std::fs::OpenOptions;\nuse std::io;\nuse std::io::BufRead;\nuse std::io::BufReader;\nuse std::io::Write;\nuse std::num::NonZeroUsize;\nuse std::path::PathBuf;\nuse std::process::Command;\nuse std::sync::atomic::AtomicBool;\nuse std::sync::atomic::Ordering;\nuse std::time::Duration;\n\nuse clap::CommandFactory;\nuse clap::Parser;\nuse clap::ValueEnum;\nuse color_eyre::eyre;\nuse color_eyre::eyre::OptionExt;\nuse color_eyre::eyre::bail;\nuse fs_tail::TailedFile;\nuse komorebi_client::AppSpecificConfigurationPath;\nuse komorebi_client::ApplicationSpecificConfiguration;\nuse komorebi_client::send_message;\nuse komorebi_client::send_query;\nuse lazy_static::lazy_static;\nuse miette::NamedSource;\nuse miette::Report;\nuse miette::SourceOffset;\nuse miette::SourceSpan;\nuse paste::paste;\nuse serde::Deserialize;\nuse sysinfo::ProcessesToUpdate;\nuse which::which;\nuse win_msgbox::OkayCancel;\nuse windows::Win32::Foundation::HWND;\nuse windows::Win32::UI::WindowsAndMessaging::SHOW_WINDOW_CMD;\nuse windows::Win32::UI::WindowsAndMessaging::SW_RESTORE;\nuse windows::Win32::UI::WindowsAndMessaging::ShowWindow;\n\nuse komorebi_client::ApplicationConfigurationGenerator;\nuse komorebi_client::ApplicationIdentifier;\nuse komorebi_client::Axis;\nuse komorebi_client::CycleDirection;\nuse komorebi_client::DefaultLayout;\nuse komorebi_client::FocusFollowsMouseImplementation;\nuse komorebi_client::HidingBehaviour;\nuse komorebi_client::MoveBehaviour;\nuse komorebi_client::OperationBehaviour;\nuse komorebi_client::OperationDirection;\nuse komorebi_client::Rect;\nuse komorebi_client::Sizing;\nuse komorebi_client::SocketMessage;\nuse komorebi_client::StateQuery;\nuse komorebi_client::StaticConfig;\nuse komorebi_client::WindowKind;\nuse komorebi_client::splash::ValidationFeedback;\n\nlazy_static! {\n    static ref HAS_CUSTOM_CONFIG_HOME: AtomicBool = AtomicBool::new(false);\n    static ref HOME_DIR: PathBuf = {\n        std::env::var(\"KOMOREBI_CONFIG_HOME\").map_or_else(\n            |_| dirs::home_dir().expect(\"there is no home directory\"),\n            |home_path| {\n                let home = home_path.replace_env();\n                if home.as_path().is_dir() {\n                    HAS_CUSTOM_CONFIG_HOME.store(true, Ordering::SeqCst);\n                    home\n                } else {\n                    panic!(\n                        \"$Env:KOMOREBI_CONFIG_HOME is set to '{home_path}', which is not a valid directory\",\n                    );\n                }\n            },\n        )\n    };\n    static ref DATA_DIR: PathBuf = dirs::data_local_dir()\n        .expect(\"there is no local data directory\")\n        .join(\"komorebi\");\n    static ref WHKD_CONFIG_DIR: PathBuf = {\n        std::env::var(\"WHKD_CONFIG_HOME\").map_or_else(\n            |_| {\n                dirs::home_dir()\n                    .expect(\"there is no home directory\")\n                    .join(\".config\")\n            },\n            |home_path| {\n                let whkd_config_home = home_path.replace_env();\n\n                assert!(\n                    whkd_config_home.is_dir(),\n                    \"$Env:WHKD_CONFIG_HOME is set to '{home_path}', which is not a valid directory\"\n                );\n\n                whkd_config_home\n            },\n        )\n    };\n}\n\nshadow_rs::shadow!(build);\n\n#[derive(thiserror::Error, Debug, miette::Diagnostic)]\n#[error(\"{message}\")]\n#[diagnostic(code(komorebi::configuration), help(\"try fixing this syntax error\"))]\nstruct ConfigurationError {\n    message: String,\n    #[source_code]\n    src: NamedSource<String>,\n    #[label(\"This bit here\")]\n    bad_bit: SourceSpan,\n}\n\n#[derive(Copy, Clone, ValueEnum)]\nenum BooleanState {\n    Enable,\n    Disable,\n}\n\nimpl From<BooleanState> for bool {\n    fn from(b: BooleanState) -> Self {\n        match b {\n            BooleanState::Enable => true,\n            BooleanState::Disable => false,\n        }\n    }\n}\n\nmacro_rules! gen_enum_subcommand_args {\n    // SubCommand Pattern: Enum Type\n    ( $( $name:ident: $element:ty ),+ $(,)? ) => {\n        $(\n            paste! {\n                #[derive(clap::Parser)]\n                pub struct $name {\n                    #[clap(value_enum)]\n                    [<$element:snake>]: $element\n                }\n            }\n        )+\n    };\n}\n\ngen_enum_subcommand_args! {\n    Focus: OperationDirection,\n    Move: OperationDirection,\n    PreselectDirection: OperationDirection,\n    CycleFocus: CycleDirection,\n    CycleMove: CycleDirection,\n    CycleMoveToWorkspace: CycleDirection,\n    CycleSendToWorkspace: CycleDirection,\n    CycleSendToMonitor: CycleDirection,\n    CycleMoveToMonitor: CycleDirection,\n    CycleMonitor: CycleDirection,\n    CycleWorkspace: CycleDirection,\n    CycleEmptyWorkspace: CycleDirection,\n    CycleMoveWorkspaceToMonitor: CycleDirection,\n    Stack: OperationDirection,\n    CycleStack: CycleDirection,\n    CycleStackIndex: CycleDirection,\n    FlipLayout: Axis,\n    ChangeLayout: DefaultLayout,\n    CycleLayout: CycleDirection,\n    WatchConfiguration: BooleanState,\n    MouseFollowsFocus: BooleanState,\n    Query: StateQuery,\n    WindowHidingBehaviour: HidingBehaviour,\n    CrossMonitorMoveBehaviour: MoveBehaviour,\n    UnmanagedWindowOperationBehaviour: OperationBehaviour,\n    PromoteWindow: OperationDirection,\n}\n\nmacro_rules! gen_target_subcommand_args {\n    // SubCommand Pattern\n    ( $( $name:ident ),+ $(,)? ) => {\n        $(\n            #[derive(clap::Parser)]\n            pub struct $name {\n                /// Target index (zero-indexed)\n                target: usize,\n            }\n        )+\n    };\n}\n\ngen_target_subcommand_args! {\n    MoveToMonitor,\n    MoveToWorkspace,\n    SendToMonitor,\n    SendToWorkspace,\n    FocusMonitor,\n    FocusWorkspace,\n    FocusWorkspaces,\n    MoveWorkspaceToMonitor,\n    SwapWorkspacesWithMonitor,\n    FocusStackWindow,\n}\n\nmacro_rules! gen_named_target_subcommand_args {\n    // SubCommand Pattern\n    ( $( $name:ident ),+ $(,)? ) => {\n        $(\n            #[derive(clap::Parser)]\n            pub struct $name {\n                /// Target workspace name\n                workspace: String,\n            }\n        )+\n    };\n}\n\ngen_named_target_subcommand_args! {\n    MoveToNamedWorkspace,\n    SendToNamedWorkspace,\n    FocusNamedWorkspace,\n    ClearNamedWorkspaceLayoutRules\n}\n\n// Thanks to @danielhenrymantilla for showing me how to use cfg_attr with an optional argument like\n// this on the Rust Programming Language Community Discord Server\nmacro_rules! gen_workspace_subcommand_args {\n    // Workspace Property: #[enum] Value Enum (if the value is an Enum)\n    // Workspace Property: Value Type (if the value is anything else)\n    ( $( $name:ident: $(#[enum] $(@$value_enum:tt)?)? $value:ty ),+ $(,)? ) => (\n        paste! {\n            $(\n                #[derive(clap::Parser)]\n                pub struct [<Workspace $name>] {\n                    /// Monitor index (zero-indexed)\n                    monitor: usize,\n\n                    /// Workspace index on the specified monitor (zero-indexed)\n                    workspace: usize,\n\n                    $(#[clap(value_enum)] $($value_enum)?)?\n                    #[cfg_attr(\n                        all($(FALSE $($value_enum)?)?),\n                        doc = \"\"$name \" of the workspace as a \"$value \"\"\n                    )]\n                    value: $value,\n                }\n            )+\n        }\n    )\n}\n\ngen_workspace_subcommand_args! {\n    Name: String,\n    Layout: #[enum] DefaultLayout,\n    Tiling: #[enum] BooleanState,\n}\n\nmacro_rules! gen_named_workspace_subcommand_args {\n    // Workspace Property: #[enum] Value Enum (if the value is an Enum)\n    // Workspace Property: Value Type (if the value is anything else)\n    ( $( $name:ident: $(#[enum] $(@$value_enum:tt)?)? $value:ty ),+ $(,)? ) => (\n        paste! {\n            $(\n                #[derive(clap::Parser)]\n                pub struct [<NamedWorkspace $name>] {\n                    /// Target workspace name\n                    workspace: String,\n\n                    $(#[clap(value_enum)] $($value_enum)?)?\n                    #[cfg_attr(\n                        all($(FALSE $($value_enum)?)?),\n                        doc = \"\"$name \" of the workspace as a \"$value \"\"\n                    )]\n                    value: $value,\n                }\n            )+\n        }\n    )\n}\n\ngen_named_workspace_subcommand_args! {\n    Layout: #[enum] DefaultLayout,\n    Tiling: #[enum] BooleanState,\n}\n\n#[derive(Parser)]\npub struct ClearWorkspaceLayoutRules {\n    /// Monitor index (zero-indexed)\n    monitor: usize,\n\n    /// Workspace index on the specified monitor (zero-indexed)\n    workspace: usize,\n}\n\n#[derive(Parser)]\npub struct WorkspaceCustomLayout {\n    /// Monitor index (zero-indexed)\n    monitor: usize,\n\n    /// Workspace index on the specified monitor (zero-indexed)\n    workspace: usize,\n\n    /// JSON or YAML file from which the custom layout definition should be loaded\n    #[clap(value_parser = replace_env_in_path)]\n    path: PathBuf,\n}\n\n#[derive(Parser)]\npub struct NamedWorkspaceCustomLayout {\n    /// Target workspace name\n    workspace: String,\n\n    /// JSON or YAML file from which the custom layout definition should be loaded\n    #[clap(value_parser = replace_env_in_path)]\n    path: PathBuf,\n}\n\n#[derive(Parser)]\npub struct WorkspaceLayoutRule {\n    /// Monitor index (zero-indexed)\n    monitor: usize,\n\n    /// Workspace index on the specified monitor (zero-indexed)\n    workspace: usize,\n\n    /// The number of window containers on-screen required to trigger this layout rule\n    at_container_count: usize,\n\n    #[clap(value_enum)]\n    layout: DefaultLayout,\n}\n\n#[derive(Parser)]\npub struct NamedWorkspaceLayoutRule {\n    /// Target workspace name\n    workspace: String,\n\n    /// The number of window containers on-screen required to trigger this layout rule\n    at_container_count: usize,\n\n    #[clap(value_enum)]\n    layout: DefaultLayout,\n}\n\n#[derive(Parser)]\npub struct WorkspaceCustomLayoutRule {\n    /// Monitor index (zero-indexed)\n    monitor: usize,\n\n    /// Workspace index on the specified monitor (zero-indexed)\n    workspace: usize,\n\n    /// The number of window containers on-screen required to trigger this layout rule\n    at_container_count: usize,\n\n    /// JSON or YAML file from which the custom layout definition should be loaded\n    #[clap(value_parser = replace_env_in_path)]\n    path: PathBuf,\n}\n\n#[derive(Parser)]\npub struct NamedWorkspaceCustomLayoutRule {\n    /// Target workspace name\n    workspace: String,\n\n    /// The number of window containers on-screen required to trigger this layout rule\n    at_container_count: usize,\n\n    /// JSON or YAML file from which the custom layout definition should be loaded\n    #[clap(value_parser = replace_env_in_path)]\n    path: PathBuf,\n}\n\n#[derive(Parser)]\nstruct Resize {\n    #[clap(value_enum)]\n    edge: OperationDirection,\n    #[clap(value_enum)]\n    sizing: Sizing,\n}\n\n#[derive(Parser)]\nstruct ResizeAxis {\n    #[clap(value_enum)]\n    axis: Axis,\n    #[clap(value_enum)]\n    sizing: Sizing,\n}\n\n#[derive(Parser)]\nstruct ResizeDelta {\n    /// The delta of pixels by which to increase or decrease window dimensions when resizing\n    pixels: i32,\n}\n\n#[derive(Parser)]\nstruct InvisibleBorders {\n    /// Size of the left invisible border\n    left: i32,\n    /// Size of the top invisible border (usually 0)\n    top: i32,\n    /// Size of the right invisible border (usually left * 2)\n    right: i32,\n    /// Size of the bottom invisible border (usually the same as left)\n    bottom: i32,\n}\n\n#[derive(Parser)]\nstruct GlobalWorkAreaOffset {\n    /// Size of the left work area offset (set right to left * 2 to maintain right padding)\n    left: i32,\n    /// Size of the top work area offset (set bottom to the same value to maintain bottom padding)\n    top: i32,\n    /// Size of the right work area offset\n    right: i32,\n    /// Size of the bottom work area offset\n    bottom: i32,\n}\n\n#[derive(Parser)]\nstruct MonitorWorkAreaOffset {\n    /// Monitor index (zero-indexed)\n    monitor: usize,\n    /// Size of the left work area offset (set right to left * 2 to maintain right padding)\n    left: i32,\n    /// Size of the top work area offset (set bottom to the same value to maintain bottom padding)\n    top: i32,\n    /// Size of the right work area offset\n    right: i32,\n    /// Size of the bottom work area offset\n    bottom: i32,\n}\n\n#[derive(Parser)]\nstruct WorkspaceWorkAreaOffset {\n    /// Monitor index (zero-indexed)\n    monitor: usize,\n    /// Workspace index (zero-indexed)\n    workspace: usize,\n    /// Size of the left work area offset (set right to left * 2 to maintain right padding)\n    left: i32,\n    /// Size of the top work area offset (set bottom to the same value to maintain bottom padding)\n    top: i32,\n    /// Size of the right work area offset\n    right: i32,\n    /// Size of the bottom work area offset\n    bottom: i32,\n}\n\n#[derive(Parser)]\nstruct MonitorIndexPreference {\n    /// Preferred monitor index (zero-indexed)\n    index_preference: usize,\n    /// Left value of the monitor's size Rect\n    left: i32,\n    /// Top value of the monitor's size Rect\n    top: i32,\n    /// Right value of the monitor's size Rect\n    right: i32,\n    /// Bottom value of the monitor's size Rect\n    bottom: i32,\n}\n\n#[derive(Parser)]\nstruct DisplayIndexPreference {\n    /// Preferred monitor index (zero-indexed)\n    index_preference: usize,\n    /// Display name as identified in komorebic state\n    display: String,\n}\n\n#[derive(Parser)]\nstruct EnsureWorkspaces {\n    /// Monitor index (zero-indexed)\n    monitor: usize,\n    /// Number of desired workspaces\n    workspace_count: usize,\n}\n\n#[derive(Parser)]\nstruct EnsureNamedWorkspaces {\n    /// Monitor index (zero-indexed)\n    monitor: usize,\n    /// Names of desired workspaces\n    names: Vec<String>,\n}\n\n#[derive(Parser)]\nstruct FocusMonitorWorkspace {\n    /// Target monitor index (zero-indexed)\n    target_monitor: usize,\n    /// Workspace index on the target monitor (zero-indexed)\n    target_workspace: usize,\n}\n\n#[derive(Parser)]\npub struct SendToMonitorWorkspace {\n    /// Target monitor index (zero-indexed)\n    target_monitor: usize,\n    /// Workspace index on the target monitor (zero-indexed)\n    target_workspace: usize,\n}\n\n#[derive(Parser)]\npub struct MoveToMonitorWorkspace {\n    /// Target monitor index (zero-indexed)\n    target_monitor: usize,\n    /// Workspace index on the target monitor (zero-indexed)\n    target_workspace: usize,\n}\n\nmacro_rules! gen_focused_workspace_padding_subcommand_args {\n    // SubCommand Pattern\n    ( $( $name:ident ),+ $(,)? ) => {\n        $(\n            #[derive(clap::Parser)]\n            pub struct $name {\n                /// Pixels size to set as an integer\n                size: i32,\n            }\n        )+\n    };\n}\n\ngen_focused_workspace_padding_subcommand_args! {\n    FocusedWorkspaceContainerPadding,\n    FocusedWorkspacePadding,\n}\n\nmacro_rules! gen_padding_subcommand_args {\n    // SubCommand Pattern\n    ( $( $name:ident ),+ $(,)? ) => {\n        $(\n            #[derive(clap::Parser)]\n            pub struct $name {\n                /// Monitor index (zero-indexed)\n                monitor: usize,\n                /// Workspace index on the specified monitor (zero-indexed)\n                workspace: usize,\n                /// Pixels to pad with as an integer\n                size: i32,\n            }\n        )+\n    };\n}\n\ngen_padding_subcommand_args! {\n    ContainerPadding,\n    WorkspacePadding,\n}\n\nmacro_rules! gen_named_padding_subcommand_args {\n    // SubCommand Pattern\n    ( $( $name:ident ),+ $(,)? ) => {\n        $(\n            #[derive(clap::Parser)]\n            pub struct $name {\n                /// Target workspace name\n                workspace: String,\n\n                /// Pixels to pad with as an integer\n                size: i32,\n            }\n        )+\n    };\n}\n\ngen_named_padding_subcommand_args! {\n    NamedWorkspaceContainerPadding,\n    NamedWorkspacePadding,\n}\n\nmacro_rules! gen_padding_adjustment_subcommand_args {\n    // SubCommand Pattern\n    ( $( $name:ident ),+ $(,)? ) => {\n        $(\n            #[derive(clap::Parser)]\n            pub struct $name {\n                #[clap(value_enum)]\n                sizing: Sizing,\n                /// Pixels to adjust by as an integer\n                adjustment: i32,\n            }\n        )+\n    };\n}\n\ngen_padding_adjustment_subcommand_args! {\n    AdjustContainerPadding,\n    AdjustWorkspacePadding,\n}\n\nmacro_rules! gen_application_target_subcommand_args {\n    // SubCommand Pattern\n    ( $( $name:ident ),+ $(,)? ) => {\n        $(\n            #[derive(clap::Parser)]\n            pub struct $name {\n                #[clap(value_enum)]\n                identifier: ApplicationIdentifier,\n                /// Identifier as a string\n                id: String,\n            }\n        )+\n    };\n}\n\ngen_application_target_subcommand_args! {\n    IgnoreRule,\n    ManageRule,\n    IdentifyTrayApplication,\n    IdentifyLayeredApplication,\n    IdentifyObjectNameChangeApplication,\n    IdentifyBorderOverflowApplication,\n    RemoveTitleBar,\n}\n\n#[derive(Parser)]\nstruct InitialWorkspaceRule {\n    #[clap(value_enum)]\n    identifier: ApplicationIdentifier,\n    /// Identifier as a string\n    id: String,\n    /// Monitor index (zero-indexed)\n    monitor: usize,\n    /// Workspace index on the specified monitor (zero-indexed)\n    workspace: usize,\n}\n\n#[derive(Parser)]\nstruct InitialNamedWorkspaceRule {\n    #[clap(value_enum)]\n    identifier: ApplicationIdentifier,\n    /// Identifier as a string\n    id: String,\n    /// Name of a workspace\n    workspace: String,\n}\n\n#[derive(Parser)]\nstruct WorkspaceRule {\n    #[clap(value_enum)]\n    identifier: ApplicationIdentifier,\n    /// Identifier as a string\n    id: String,\n    /// Monitor index (zero-indexed)\n    monitor: usize,\n    /// Workspace index on the specified monitor (zero-indexed)\n    workspace: usize,\n}\n\n#[derive(Parser)]\nstruct NamedWorkspaceRule {\n    #[clap(value_enum)]\n    identifier: ApplicationIdentifier,\n    /// Identifier as a string\n    id: String,\n    /// Name of a workspace\n    workspace: String,\n}\n\n#[derive(Parser)]\nstruct ClearWorkspaceRules {\n    /// Monitor index (zero-indexed)\n    monitor: usize,\n    /// Workspace index on the specified monitor (zero-indexed)\n    workspace: usize,\n}\n\n#[derive(Parser)]\nstruct ClearNamedWorkspaceRules {\n    /// Name of a workspace\n    workspace: String,\n}\n\n#[derive(Parser)]\nstruct ToggleFocusFollowsMouse {\n    #[clap(value_enum, short, long, default_value = \"windows\")]\n    implementation: FocusFollowsMouseImplementation,\n}\n\n#[derive(Parser)]\nstruct FocusFollowsMouse {\n    #[clap(value_enum, short, long, default_value = \"windows\")]\n    implementation: FocusFollowsMouseImplementation,\n    #[clap(value_enum)]\n    boolean_state: BooleanState,\n}\n\n#[derive(Parser)]\nstruct Border {\n    #[clap(value_enum)]\n    boolean_state: BooleanState,\n}\n\n#[derive(Parser)]\nstruct Transparency {\n    #[clap(value_enum)]\n    boolean_state: BooleanState,\n}\n\n#[derive(Parser)]\nstruct TransparencyAlpha {\n    /// Alpha\n    alpha: u8,\n}\n\n#[derive(Parser)]\nstruct BorderColour {\n    #[clap(value_enum, short, long, default_value = \"single\")]\n    window_kind: WindowKind,\n    /// Red\n    r: u32,\n    /// Green\n    g: u32,\n    /// Blue\n    b: u32,\n}\n\n#[derive(Parser)]\nstruct BorderWidth {\n    /// Desired width of the window border\n    width: i32,\n}\n\n#[derive(Parser)]\nstruct BorderOffset {\n    /// Desired offset of the window border\n    offset: i32,\n}\n#[derive(Parser)]\nstruct BorderStyle {\n    /// Desired border style\n    #[clap(value_enum)]\n    style: komorebi_client::BorderStyle,\n}\n\n#[derive(Parser)]\nstruct BorderImplementation {\n    /// Desired border implementation\n    #[clap(value_enum)]\n    style: komorebi_client::BorderImplementation,\n}\n\n#[derive(Parser)]\nstruct StackbarMode {\n    /// Desired stackbar mode\n    #[clap(value_enum)]\n    mode: komorebi_client::StackbarMode,\n}\n\n#[derive(Parser)]\nstruct Animation {\n    #[clap(value_enum)]\n    boolean_state: BooleanState,\n    /// Animation type to apply the state to. If not specified, sets global state\n    #[clap(value_enum, short, long)]\n    animation_type: Option<komorebi_client::AnimationPrefix>,\n}\n\n#[derive(Parser)]\nstruct AnimationDuration {\n    /// Desired animation durations in ms\n    duration: u64,\n    /// Animation type to apply the duration to. If not specified, sets global duration\n    #[clap(value_enum, short, long)]\n    animation_type: Option<komorebi_client::AnimationPrefix>,\n}\n\n#[derive(Parser)]\nstruct AnimationFps {\n    /// Desired animation frames per second\n    fps: u64,\n}\n\n#[derive(Parser)]\nstruct AnimationStyle {\n    /// Desired ease function for animation\n    #[clap(value_enum, short, long, default_value = \"linear\")]\n    style: komorebi_client::AnimationStyle,\n    /// Animation type to apply the style to. If not specified, sets global style\n    #[clap(value_enum, short, long)]\n    animation_type: Option<komorebi_client::AnimationPrefix>,\n}\n\n#[derive(Parser)]\nstruct Docgen {\n    /// Output directory for generated documentation files\n    #[clap(short, long)]\n    output: Option<PathBuf>,\n}\n\n#[derive(Parser)]\n#[allow(clippy::struct_excessive_bools)]\nstruct Start {\n    /// Allow the use of komorebi's custom focus-follows-mouse implementation\n    #[clap(hide = true)]\n    #[clap(short, long = \"ffm\")]\n    ffm: bool,\n    /// Path to a static configuration JSON file\n    #[clap(short, long)]\n    #[clap(value_parser = replace_env_in_path)]\n    config: Option<PathBuf>,\n    /// Wait for `komorebic complete-configuration` to be sent before processing events\n    #[clap(short, long)]\n    await_configuration: bool,\n    /// Start a TCP server on the given port to allow the direct sending of SocketMessages\n    #[clap(short, long)]\n    tcp_port: Option<usize>,\n    /// Start whkd in a background process\n    #[clap(long)]\n    whkd: bool,\n    /// Start autohotkey configuration file\n    #[clap(hide = true)]\n    #[clap(long)]\n    ahk: bool,\n    /// Start komorebi-bar in a background process\n    #[clap(long)]\n    bar: bool,\n    /// Start masir in a background process for focus-follows-mouse\n    #[clap(long)]\n    masir: bool,\n    /// Do not attempt to auto-apply a dumped state temp file from a previously running instance of komorebi\n    #[clap(long)]\n    clean_state: bool,\n}\n\n#[derive(Parser)]\nstruct Stop {\n    /// Stop whkd if it is running as a background process\n    #[clap(long)]\n    whkd: bool,\n    /// Stop ahk if it is running as a background process\n    #[clap(hide = true)]\n    #[clap(long)]\n    ahk: bool,\n    /// Stop komorebi-bar if it is running as a background process\n    #[clap(long)]\n    bar: bool,\n    /// Stop masir if it is running as a background process\n    #[clap(long)]\n    masir: bool,\n    /// Do not restore windows after stopping komorebi\n    #[clap(long, hide = true)]\n    ignore_restore: bool,\n}\n\n#[derive(Parser)]\nstruct Kill {\n    /// Kill whkd if it is running as a background process\n    #[clap(long)]\n    whkd: bool,\n    /// Kill ahk if it is running as a background process\n    #[clap(hide = true)]\n    #[clap(long)]\n    ahk: bool,\n    /// Kill komorebi-bar if it is running as a background process\n    #[clap(long)]\n    bar: bool,\n    /// Kill masir if it is running as a background process\n    #[clap(long)]\n    masir: bool,\n}\n\n#[derive(Parser)]\nstruct SaveResize {\n    /// File to which the resize layout dimensions should be saved\n    #[clap(value_parser = replace_env_in_path)]\n    path: PathBuf,\n}\n\n#[derive(Parser)]\nstruct LoadResize {\n    /// File from which the resize layout dimensions should be loaded\n    #[clap(value_parser = replace_env_in_path)]\n    path: PathBuf,\n}\n\n#[derive(Parser)]\nstruct LoadCustomLayout {\n    /// JSON or YAML file from which the custom layout definition should be loaded\n    #[clap(value_parser = replace_env_in_path)]\n    path: PathBuf,\n}\n\n#[derive(Parser)]\nstruct SubscribeSocket {\n    /// Name of the socket to send event notifications to\n    socket: String,\n}\n\n#[derive(Parser)]\nstruct UnsubscribeSocket {\n    /// Name of the socket to stop sending event notifications to\n    socket: String,\n}\n\n#[derive(Parser)]\nstruct SubscribePipe {\n    /// Name of the pipe to send event notifications to (without \"\\\\.\\pipe\\\" prepended)\n    named_pipe: String,\n}\n\n#[derive(Parser)]\nstruct UnsubscribePipe {\n    /// Name of the pipe to stop sending event notifications to (without \"\\\\.\\pipe\\\" prepended)\n    named_pipe: String,\n}\n\n#[derive(Parser)]\nstruct AhkAppSpecificConfiguration {\n    /// YAML file from which the application-specific configurations should be loaded\n    #[clap(value_parser = replace_env_in_path)]\n    path: PathBuf,\n    /// Optional YAML file of overrides to apply over the first file\n    #[clap(value_parser = replace_env_in_path)]\n    override_path: Option<PathBuf>,\n}\n\n#[derive(Parser)]\nstruct PwshAppSpecificConfiguration {\n    /// YAML file from which the application-specific configurations should be loaded\n    #[clap(value_parser = replace_env_in_path)]\n    path: PathBuf,\n    /// Optional YAML file of overrides to apply over the first file\n    #[clap(value_parser = replace_env_in_path)]\n    override_path: Option<PathBuf>,\n}\n\n#[derive(Parser)]\nstruct FormatAppSpecificConfiguration {\n    /// YAML file from which the application-specific configurations should be loaded\n    #[clap(value_parser = replace_env_in_path)]\n    path: PathBuf,\n}\n\n#[derive(Parser)]\nstruct ConvertAppSpecificConfiguration {\n    /// YAML file from which the application-specific configurations should be loaded\n    #[clap(value_parser = replace_env_in_path)]\n    path: PathBuf,\n}\n\n#[derive(Parser)]\nstruct AltFocusHack {\n    #[clap(value_enum)]\n    boolean_state: BooleanState,\n}\n\n#[derive(Parser)]\nstruct EnableAutostart {\n    /// Path to a static configuration JSON file\n    #[clap(action, short, long)]\n    #[clap(value_parser = replace_env_in_path)]\n    config: Option<PathBuf>,\n    /// Enable komorebi's custom focus-follows-mouse implementation\n    #[clap(hide = true)]\n    #[clap(short, long = \"ffm\")]\n    ffm: bool,\n    /// Enable autostart of whkd\n    #[clap(long)]\n    whkd: bool,\n    /// Enable autostart of ahk\n    #[clap(hide = true)]\n    #[clap(long)]\n    ahk: bool,\n    /// Enable autostart of komorebi-bar\n    #[clap(long)]\n    bar: bool,\n    /// Enable autostart of masir\n    #[clap(long)]\n    masir: bool,\n}\n\n#[derive(Parser)]\nstruct Check {\n    /// Path to a static configuration JSON file\n    #[clap(action, short, long)]\n    #[clap(value_parser = replace_env_in_path)]\n    komorebi_config: Option<PathBuf>,\n}\n\n#[derive(Parser)]\nstruct ReplaceConfiguration {\n    /// Static configuration JSON file from which the configuration should be loaded\n    #[clap(value_parser = replace_env_in_path)]\n    path: PathBuf,\n}\n\n#[derive(Parser)]\nstruct EagerFocus {\n    /// Case-sensitive exe identifier\n    exe: String,\n}\n\n#[derive(Parser)]\nstruct ScrollingLayoutColumns {\n    /// Desired number of visible columns\n    count: NonZeroUsize,\n}\n\n#[derive(Parser)]\nstruct LayoutRatios {\n    /// Column width ratios (space-separated values between 0.1 and 0.9)\n    #[clap(short, long, num_args = 1..)]\n    columns: Option<Vec<f32>>,\n    /// Row height ratios (space-separated values between 0.1 and 0.9)\n    #[clap(short, long, num_args = 1..)]\n    rows: Option<Vec<f32>>,\n}\n\n#[derive(Parser)]\nstruct License {\n    /// Email address associated with an Individual Commercial Use License\n    email: String,\n}\n\n#[derive(Parser)]\nstruct Splash {\n    mdm_server: Option<String>,\n}\n\n#[derive(Parser)]\n#[clap(author, about, version = build::CLAP_LONG_VERSION)]\nstruct Opts {\n    #[clap(subcommand)]\n    subcmd: SubCommand,\n}\n\n#[derive(Parser)]\nenum SubCommand {\n    #[clap(hide = true)]\n    Docgen(Docgen),\n    #[clap(hide = true)]\n    Splash(Splash),\n    /// Gather example configurations for a new-user quickstart\n    Quickstart,\n    /// Specify an email associated with an Individual Commercial Use License\n    #[clap(arg_required_else_help = true)]\n    License(License),\n    /// Start komorebi.exe as a background process\n    Start(Start),\n    /// Stop the komorebi.exe process and restore all hidden windows\n    Stop(Stop),\n    /// Kill background processes started by komorebic\n    Kill(Kill),\n    /// Check komorebi configuration and related files for common errors\n    Check(Check),\n    /// Show the path to komorebi.json\n    #[clap(alias = \"config\")]\n    Configuration,\n    /// Show the path to komorebi.bar.json\n    #[clap(alias = \"bar-config\")]\n    #[clap(alias = \"bconfig\")]\n    BarConfiguration,\n    /// Show the path to whkdrc\n    #[clap(alias = \"whkd\")]\n    Whkdrc,\n    /// Show the path to komorebi's data directory in %LOCALAPPDATA%\n    #[clap(alias = \"datadir\")]\n    DataDirectory,\n    /// Show a JSON representation of the current window manager state\n    State,\n    /// Show a JSON representation of the current global state\n    GlobalState,\n    /// Launch the komorebi-gui debugging tool\n    Gui,\n    /// Toggle the komorebi-shortcuts helper\n    ToggleShortcuts,\n    /// Show a JSON representation of visible windows\n    VisibleWindows,\n    /// Show information about connected monitors\n    #[clap(alias = \"monitor-info\")]\n    MonitorInformation,\n    /// Query the current window manager state\n    #[clap(arg_required_else_help = true)]\n    Query(Query),\n    /// Subscribe to komorebi events using a Unix Domain Socket\n    #[clap(arg_required_else_help = true)]\n    SubscribeSocket(SubscribeSocket),\n    /// Unsubscribe from komorebi events\n    #[clap(arg_required_else_help = true)]\n    UnsubscribeSocket(UnsubscribeSocket),\n    /// Subscribe to komorebi events using a Named Pipe\n    #[clap(arg_required_else_help = true)]\n    #[clap(alias = \"subscribe\")]\n    SubscribePipe(SubscribePipe),\n    /// Unsubscribe from komorebi events\n    #[clap(arg_required_else_help = true)]\n    #[clap(alias = \"unsubscribe\")]\n    UnsubscribePipe(UnsubscribePipe),\n    /// Tail komorebi.exe's process logs (cancel with Ctrl-C)\n    Log,\n    /// Quicksave the current resize layout dimensions\n    #[clap(alias = \"quick-save\")]\n    QuickSaveResize,\n    /// Load the last quicksaved resize layout dimensions\n    #[clap(alias = \"quick-load\")]\n    QuickLoadResize,\n    /// Save the current resize layout dimensions to a file\n    #[clap(arg_required_else_help = true)]\n    #[clap(alias = \"save\")]\n    SaveResize(SaveResize),\n    /// Load the resize layout dimensions from a file\n    #[clap(arg_required_else_help = true)]\n    #[clap(alias = \"load\")]\n    LoadResize(LoadResize),\n    /// Change focus to the window in the specified direction\n    #[clap(arg_required_else_help = true)]\n    Focus(Focus),\n    /// Move the focused window in the specified direction\n    #[clap(arg_required_else_help = true)]\n    Move(Move),\n    /// Preselect the specified direction for the next window to be spawned on supported layouts\n    #[clap(arg_required_else_help = true)]\n    PreselectDirection(PreselectDirection),\n    /// Cancel a workspace preselect set by the preselect-direction command, if one exists\n    CancelPreselect,\n    /// Minimize the focused window\n    Minimize,\n    /// Close the focused window\n    Close,\n    /// Forcibly focus the window at the cursor with a left mouse click\n    ForceFocus,\n    /// Change focus to the window in the specified cycle direction\n    #[clap(arg_required_else_help = true)]\n    CycleFocus(CycleFocus),\n    /// Move the focused window in the specified cycle direction\n    #[clap(arg_required_else_help = true)]\n    CycleMove(CycleMove),\n    /// Focus the first managed window matching the given exe\n    #[clap(arg_required_else_help = true)]\n    EagerFocus(EagerFocus),\n    /// Stack the focused window in the specified direction\n    #[clap(arg_required_else_help = true)]\n    Stack(Stack),\n    /// Unstack the focused window\n    Unstack,\n    /// Cycle the focused stack in the specified cycle direction\n    #[clap(arg_required_else_help = true)]\n    CycleStack(CycleStack),\n    /// Cycle the index of the focused window in the focused stack in the specified cycle direction\n    #[clap(arg_required_else_help = true)]\n    CycleStackIndex(CycleStackIndex),\n    /// Focus the specified window index in the focused stack\n    #[clap(arg_required_else_help = true)]\n    FocusStackWindow(FocusStackWindow),\n    /// Stack all windows on the focused workspace\n    StackAll,\n    /// Unstack all windows in the focused container\n    UnstackAll,\n    /// Resize the focused window in the specified direction\n    #[clap(arg_required_else_help = true)]\n    #[clap(alias = \"resize\")]\n    ResizeEdge(Resize),\n    /// Resize the focused window or primary column along the specified axis\n    #[clap(arg_required_else_help = true)]\n    ResizeAxis(ResizeAxis),\n    /// Move the focused window to the specified monitor\n    #[clap(arg_required_else_help = true)]\n    MoveToMonitor(MoveToMonitor),\n    /// Move the focused window to the monitor in the given cycle direction\n    #[clap(arg_required_else_help = true)]\n    CycleMoveToMonitor(CycleMoveToMonitor),\n    /// Move the focused window to the specified workspace\n    #[clap(arg_required_else_help = true)]\n    MoveToWorkspace(MoveToWorkspace),\n    /// Move the focused window to the specified workspace\n    #[clap(arg_required_else_help = true)]\n    MoveToNamedWorkspace(MoveToNamedWorkspace),\n    /// Move the focused window to the workspace in the given cycle direction\n    #[clap(arg_required_else_help = true)]\n    CycleMoveToWorkspace(CycleMoveToWorkspace),\n    /// Send the focused window to the specified monitor\n    #[clap(arg_required_else_help = true)]\n    SendToMonitor(SendToMonitor),\n    /// Send the focused window to the monitor in the given cycle direction\n    #[clap(arg_required_else_help = true)]\n    CycleSendToMonitor(CycleSendToMonitor),\n    /// Send the focused window to the specified workspace\n    #[clap(arg_required_else_help = true)]\n    SendToWorkspace(SendToWorkspace),\n    /// Send the focused window to the specified workspace\n    #[clap(arg_required_else_help = true)]\n    SendToNamedWorkspace(SendToNamedWorkspace),\n    /// Send the focused window to the workspace in the given cycle direction\n    #[clap(arg_required_else_help = true)]\n    CycleSendToWorkspace(CycleSendToWorkspace),\n    /// Send the focused window to the specified monitor workspace\n    #[clap(arg_required_else_help = true)]\n    SendToMonitorWorkspace(SendToMonitorWorkspace),\n    /// Move the focused window to the specified monitor workspace\n    #[clap(arg_required_else_help = true)]\n    MoveToMonitorWorkspace(MoveToMonitorWorkspace),\n    /// Send the focused window to the last focused monitor workspace\n    SendToLastWorkspace,\n    /// Move the focused window to the last focused monitor workspace\n    MoveToLastWorkspace,\n    /// Focus the specified monitor\n    #[clap(arg_required_else_help = true)]\n    FocusMonitor(FocusMonitor),\n    /// Focus the monitor at the current cursor location\n    FocusMonitorAtCursor,\n    /// Focus the last focused workspace on the focused monitor\n    FocusLastWorkspace,\n    /// Focus the specified workspace on the focused monitor\n    #[clap(arg_required_else_help = true)]\n    FocusWorkspace(FocusWorkspace),\n    /// Focus the specified workspace on all monitors\n    #[clap(arg_required_else_help = true)]\n    FocusWorkspaces(FocusWorkspaces),\n    /// Focus the specified workspace on the target monitor\n    #[clap(arg_required_else_help = true)]\n    FocusMonitorWorkspace(FocusMonitorWorkspace),\n    /// Focus the specified workspace\n    #[clap(arg_required_else_help = true)]\n    FocusNamedWorkspace(FocusNamedWorkspace),\n    /// Close the focused workspace (must be empty and unnamed)\n    CloseWorkspace,\n    /// Focus the monitor in the given cycle direction\n    #[clap(arg_required_else_help = true)]\n    CycleMonitor(CycleMonitor),\n    /// Focus the workspace in the given cycle direction\n    #[clap(arg_required_else_help = true)]\n    CycleWorkspace(CycleWorkspace),\n    /// Focus the next empty workspace in the given cycle direction (if one exists)\n    #[clap(arg_required_else_help = true)]\n    CycleEmptyWorkspace(CycleWorkspace),\n    /// Move the focused workspace to the specified monitor\n    #[clap(arg_required_else_help = true)]\n    MoveWorkspaceToMonitor(MoveWorkspaceToMonitor),\n    /// Move the focused workspace monitor in the given cycle direction\n    #[clap(arg_required_else_help = true)]\n    CycleMoveWorkspaceToMonitor(CycleMoveWorkspaceToMonitor),\n    /// Swap focused monitor workspaces with specified monitor\n    #[clap(arg_required_else_help = true)]\n    SwapWorkspacesWithMonitor(SwapWorkspacesWithMonitor),\n    /// Create and append a new workspace on the focused monitor\n    NewWorkspace,\n    /// Set the resize delta (used by resize-edge and resize-axis)\n    #[clap(arg_required_else_help = true)]\n    ResizeDelta(ResizeDelta),\n    /// Set the invisible border dimensions around each window\n    #[clap(arg_required_else_help = true)]\n    InvisibleBorders(InvisibleBorders),\n    /// Set offsets to exclude parts of the work area from tiling\n    #[clap(arg_required_else_help = true)]\n    GlobalWorkAreaOffset(GlobalWorkAreaOffset),\n    /// Set offsets for a monitor to exclude parts of the work area from tiling\n    #[clap(arg_required_else_help = true)]\n    MonitorWorkAreaOffset(MonitorWorkAreaOffset),\n    /// Set offsets for a workspace to exclude parts of the work area from tiling\n    #[clap(arg_required_else_help = true)]\n    WorkspaceWorkAreaOffset(WorkspaceWorkAreaOffset),\n    /// Toggle application of the window-based work area offset for the focused workspace\n    ToggleWindowBasedWorkAreaOffset,\n    /// Set container padding on the focused workspace\n    #[clap(arg_required_else_help = true)]\n    FocusedWorkspaceContainerPadding(FocusedWorkspaceContainerPadding),\n    /// Set workspace padding on the focused workspace\n    #[clap(arg_required_else_help = true)]\n    FocusedWorkspacePadding(FocusedWorkspacePadding),\n    /// Adjust container padding on the focused workspace\n    #[clap(arg_required_else_help = true)]\n    AdjustContainerPadding(AdjustContainerPadding),\n    /// Adjust workspace padding on the focused workspace\n    #[clap(arg_required_else_help = true)]\n    AdjustWorkspacePadding(AdjustWorkspacePadding),\n    /// Set the layout on the focused workspace\n    #[clap(arg_required_else_help = true)]\n    ChangeLayout(ChangeLayout),\n    /// Cycle between available layouts\n    #[clap(arg_required_else_help = true)]\n    CycleLayout(CycleLayout),\n    /// Set the number of visible columns for the Scrolling layout on the focused workspace\n    #[clap(arg_required_else_help = true)]\n    ScrollingLayoutColumns(ScrollingLayoutColumns),\n    /// Set the layout column and row ratios for the focused workspace\n    LayoutRatios(LayoutRatios),\n    /// Load a custom layout from file for the focused workspace\n    #[clap(hide = true)]\n    #[clap(arg_required_else_help = true)]\n    LoadCustomLayout(LoadCustomLayout),\n    /// Flip the layout on the focused workspace\n    #[clap(arg_required_else_help = true)]\n    FlipLayout(FlipLayout),\n    /// Promote the focused window to the largest tile via container removal and re-insertion\n    Promote,\n    /// Promote the focused window to the largest tile by swapping container indices with the largest tile\n    PromoteSwap,\n    /// Promote the user focus to the top of the tree\n    PromoteFocus,\n    /// Promote the window in the specified direction\n    PromoteWindow(PromoteWindow),\n    /// Force the retiling of all managed windows\n    Retile,\n    /// Set the monitor index preference for a monitor identified using its size\n    #[clap(arg_required_else_help = true)]\n    MonitorIndexPreference(MonitorIndexPreference),\n    /// Set the display index preference for a monitor identified using its display name\n    #[clap(arg_required_else_help = true)]\n    DisplayIndexPreference(DisplayIndexPreference),\n    /// Create at least this many workspaces for the specified monitor\n    #[clap(arg_required_else_help = true)]\n    EnsureWorkspaces(EnsureWorkspaces),\n    /// Create these many named workspaces for the specified monitor\n    #[clap(arg_required_else_help = true)]\n    EnsureNamedWorkspaces(EnsureNamedWorkspaces),\n    /// Set the container padding for the specified workspace\n    #[clap(arg_required_else_help = true)]\n    ContainerPadding(ContainerPadding),\n    /// Set the container padding for the specified workspace\n    #[clap(arg_required_else_help = true)]\n    NamedWorkspaceContainerPadding(NamedWorkspaceContainerPadding),\n    /// Set the workspace padding for the specified workspace\n    #[clap(arg_required_else_help = true)]\n    WorkspacePadding(WorkspacePadding),\n    /// Set the workspace padding for the specified workspace\n    #[clap(arg_required_else_help = true)]\n    NamedWorkspacePadding(NamedWorkspacePadding),\n    /// Set the layout for the specified workspace\n    #[clap(arg_required_else_help = true)]\n    WorkspaceLayout(WorkspaceLayout),\n    /// Set the layout for the specified workspace\n    #[clap(arg_required_else_help = true)]\n    NamedWorkspaceLayout(NamedWorkspaceLayout),\n    /// Set a custom layout for the specified workspace\n    #[clap(hide = true)]\n    #[clap(arg_required_else_help = true)]\n    WorkspaceCustomLayout(WorkspaceCustomLayout),\n    /// Set a custom layout for the specified workspace\n    #[clap(hide = true)]\n    #[clap(arg_required_else_help = true)]\n    NamedWorkspaceCustomLayout(NamedWorkspaceCustomLayout),\n    /// Add a dynamic layout rule for the specified workspace\n    #[clap(arg_required_else_help = true)]\n    WorkspaceLayoutRule(WorkspaceLayoutRule),\n    /// Add a dynamic layout rule for the specified workspace\n    #[clap(arg_required_else_help = true)]\n    NamedWorkspaceLayoutRule(NamedWorkspaceLayoutRule),\n    /// Add a dynamic custom layout for the specified workspace\n    #[clap(hide = true)]\n    #[clap(arg_required_else_help = true)]\n    WorkspaceCustomLayoutRule(WorkspaceCustomLayoutRule),\n    /// Add a dynamic custom layout for the specified workspace\n    #[clap(hide = true)]\n    #[clap(arg_required_else_help = true)]\n    NamedWorkspaceCustomLayoutRule(NamedWorkspaceCustomLayoutRule),\n    /// Clear all dynamic layout rules for the specified workspace\n    #[clap(arg_required_else_help = true)]\n    ClearWorkspaceLayoutRules(ClearWorkspaceLayoutRules),\n    /// Clear all dynamic layout rules for the specified workspace\n    #[clap(arg_required_else_help = true)]\n    ClearNamedWorkspaceLayoutRules(ClearNamedWorkspaceLayoutRules),\n    /// Enable or disable window tiling for the specified workspace\n    #[clap(arg_required_else_help = true)]\n    WorkspaceTiling(WorkspaceTiling),\n    /// Enable or disable window tiling for the specified workspace\n    #[clap(arg_required_else_help = true)]\n    NamedWorkspaceTiling(NamedWorkspaceTiling),\n    /// Set the workspace name for the specified workspace\n    #[clap(arg_required_else_help = true)]\n    WorkspaceName(WorkspaceName),\n    /// Toggle the behaviour for new windows (stacking or dynamic tiling)\n    ToggleWindowContainerBehaviour,\n    /// Enable or disable float override, which makes it so every new window opens in floating mode\n    ToggleFloatOverride,\n    /// Toggle the behaviour for new windows (stacking or dynamic tiling) for currently focused\n    /// workspace. If there was no behaviour set for the workspace previously it takes the opposite\n    /// of the global value.\n    ToggleWorkspaceWindowContainerBehaviour,\n    /// Enable or disable float override, which makes it so every new window opens in floating\n    /// mode, for the currently focused workspace. If there was no override value set for the\n    /// workspace previously it takes the opposite of the global value.\n    ToggleWorkspaceFloatOverride,\n    /// Toggle between the Tiling and Floating layers on the focused workspace\n    ToggleWorkspaceLayer,\n    /// Toggle the paused state for all window tiling\n    TogglePause,\n    /// Toggle window tiling on the focused workspace\n    ToggleTiling,\n    /// Toggle floating mode for the focused window\n    ToggleFloat,\n    /// Toggle monocle mode for the focused container\n    ToggleMonocle,\n    /// Toggle native maximization for the focused window\n    ToggleMaximize,\n    /// Toggle a lock for the focused container, ensuring it will not be displaced by any new windows\n    ToggleLock,\n    /// Restore all hidden windows (debugging command)\n    RestoreWindows,\n    /// Force komorebi to manage the focused window\n    Manage,\n    /// Unmanage a window that was forcibly managed\n    Unmanage,\n    /// Replace the configuration of a running instance of komorebi from a static configuration file\n    #[clap(arg_required_else_help = true)]\n    ReplaceConfiguration(ReplaceConfiguration),\n    /// Reload legacy komorebi.ahk or komorebi.ps1 configurations (if they exist)\n    ReloadConfiguration,\n    /// Enable or disable watching of legacy komorebi.ahk or komorebi.ps1 configurations (if they exist)\n    #[clap(arg_required_else_help = true)]\n    WatchConfiguration(WatchConfiguration),\n    /// For legacy komorebi.ahk or komorebi.ps1 configurations, signal that the final configuration option has been sent\n    CompleteConfiguration,\n    /// DEPRECATED since v0.1.22\n    #[deprecated(note = \"No longer required\")]\n    #[clap(arg_required_else_help = true)]\n    #[clap(hide = true)]\n    AltFocusHack(AltFocusHack),\n    /// Set the window behaviour when switching workspaces / cycling stacks\n    #[clap(arg_required_else_help = true)]\n    WindowHidingBehaviour(WindowHidingBehaviour),\n    /// Set the behaviour when moving windows across monitor boundaries\n    #[clap(arg_required_else_help = true)]\n    CrossMonitorMoveBehaviour(CrossMonitorMoveBehaviour),\n    /// Toggle the behaviour when moving windows across monitor boundaries\n    ToggleCrossMonitorMoveBehaviour,\n    /// Set the operation behaviour when the focused window is not managed\n    #[clap(arg_required_else_help = true)]\n    UnmanagedWindowOperationBehaviour(UnmanagedWindowOperationBehaviour),\n    /// Add a rule to float the foreground window for the rest of this session\n    SessionFloatRule,\n    /// Show all session float rules\n    SessionFloatRules,\n    /// Clear all session float rules\n    ClearSessionFloatRules,\n    /// Add a rule to ignore the specified application\n    #[clap(arg_required_else_help = true)]\n    #[clap(alias = \"float-rule\")]\n    IgnoreRule(IgnoreRule),\n    /// Add a rule to always manage the specified application\n    #[clap(arg_required_else_help = true)]\n    ManageRule(ManageRule),\n    /// Add a rule to associate an application with a workspace on first show\n    #[clap(arg_required_else_help = true)]\n    InitialWorkspaceRule(InitialWorkspaceRule),\n    /// Add a rule to associate an application with a named workspace on first show\n    #[clap(arg_required_else_help = true)]\n    InitialNamedWorkspaceRule(InitialNamedWorkspaceRule),\n    /// Add a rule to associate an application with a workspace\n    #[clap(arg_required_else_help = true)]\n    WorkspaceRule(WorkspaceRule),\n    /// Add a rule to associate an application with a named workspace\n    #[clap(arg_required_else_help = true)]\n    NamedWorkspaceRule(NamedWorkspaceRule),\n    /// Remove all application association rules for a workspace by monitor and workspace index\n    #[clap(arg_required_else_help = true)]\n    ClearWorkspaceRules(ClearWorkspaceRules),\n    /// Remove all application association rules for a named workspace\n    #[clap(arg_required_else_help = true)]\n    ClearNamedWorkspaceRules(ClearNamedWorkspaceRules),\n    /// Remove all application association rules for all workspaces\n    ClearAllWorkspaceRules,\n    /// Enforce all workspace rules, including initial workspace rules that have already been applied\n    EnforceWorkspaceRules,\n    /// Identify an application that sends EVENT_OBJECT_NAMECHANGE on launch\n    #[clap(arg_required_else_help = true)]\n    IdentifyObjectNameChangeApplication(IdentifyObjectNameChangeApplication),\n    /// Identify an application that closes to the system tray\n    #[clap(arg_required_else_help = true)]\n    IdentifyTrayApplication(IdentifyTrayApplication),\n    /// Identify an application that has WS_EX_LAYERED, but should still be managed\n    #[clap(arg_required_else_help = true)]\n    IdentifyLayeredApplication(IdentifyLayeredApplication),\n    /// Whitelist an application for title bar removal\n    #[clap(arg_required_else_help = true)]\n    RemoveTitleBar(RemoveTitleBar),\n    /// Toggle title bars for whitelisted applications\n    ToggleTitleBars,\n    /// Identify an application that has overflowing borders\n    #[clap(hide = true)]\n    #[clap(alias = \"identify-border-overflow\")]\n    IdentifyBorderOverflowApplication(IdentifyBorderOverflowApplication),\n    /// Enable or disable borders\n    #[clap(arg_required_else_help = true)]\n    #[clap(alias = \"active-window-border\")]\n    Border(Border),\n    /// Set the colour for a window border kind\n    #[clap(arg_required_else_help = true)]\n    #[clap(alias = \"active-window-border-colour\")]\n    BorderColour(BorderColour),\n    /// Set the border width\n    #[clap(arg_required_else_help = true)]\n    #[clap(alias = \"active-window-border-width\")]\n    BorderWidth(BorderWidth),\n    /// Set the border offset\n    #[clap(arg_required_else_help = true)]\n    #[clap(alias = \"active-window-border-offset\")]\n    BorderOffset(BorderOffset),\n    /// Set the border style\n    #[clap(arg_required_else_help = true)]\n    BorderStyle(BorderStyle),\n    /// Set the border implementation\n    #[clap(arg_required_else_help = true)]\n    BorderImplementation(BorderImplementation),\n    /// Set the stackbar mode\n    #[clap(arg_required_else_help = true)]\n    StackbarMode(StackbarMode),\n    /// Enable or disable transparency for unfocused windows\n    #[clap(arg_required_else_help = true)]\n    Transparency(Transparency),\n    /// Set the alpha value for unfocused window transparency\n    #[clap(arg_required_else_help = true)]\n    TransparencyAlpha(TransparencyAlpha),\n    /// Toggle transparency for unfocused windows\n    ToggleTransparency,\n    /// Enable or disable movement animations\n    #[clap(arg_required_else_help = true)]\n    Animation(Animation),\n    /// Set the duration for movement animations in ms\n    #[clap(arg_required_else_help = true)]\n    AnimationDuration(AnimationDuration),\n    /// Set the frames per second for movement animations\n    #[clap(arg_required_else_help = true)]\n    AnimationFps(AnimationFps),\n    /// Set the ease function for movement animations\n    #[clap(arg_required_else_help = true)]\n    AnimationStyle(AnimationStyle),\n    /// Enable or disable focus follows mouse for the operating system\n    #[clap(hide = true)]\n    #[clap(arg_required_else_help = true)]\n    FocusFollowsMouse(FocusFollowsMouse),\n    /// Toggle focus follows mouse for the operating system\n    #[clap(hide = true)]\n    #[clap(arg_required_else_help = true)]\n    ToggleFocusFollowsMouse(ToggleFocusFollowsMouse),\n    /// Enable or disable mouse follows focus on all workspaces\n    #[clap(arg_required_else_help = true)]\n    MouseFollowsFocus(MouseFollowsFocus),\n    /// Toggle mouse follows focus on all workspaces\n    ToggleMouseFollowsFocus,\n    /// Generate common app-specific configurations and fixes to use in komorebi.ahk\n    #[clap(arg_required_else_help = true)]\n    #[clap(alias = \"ahk-asc\")]\n    AhkAppSpecificConfiguration(AhkAppSpecificConfiguration),\n    /// Generate common app-specific configurations and fixes in a PowerShell script\n    #[clap(arg_required_else_help = true)]\n    #[clap(alias = \"pwsh-asc\")]\n    PwshAppSpecificConfiguration(PwshAppSpecificConfiguration),\n    /// Convert a v1 ASC YAML file to a v2 ASC JSON file\n    #[clap(arg_required_else_help = true)]\n    #[clap(alias = \"convert-asc\")]\n    ConvertAppSpecificConfiguration(ConvertAppSpecificConfiguration),\n    /// Format a YAML file for use with the `app-specific-configuration` command\n    #[clap(arg_required_else_help = true)]\n    #[clap(alias = \"fmt-asc\")]\n    #[clap(hide = true)]\n    FormatAppSpecificConfiguration(FormatAppSpecificConfiguration),\n    /// Fetch the latest version of applications.json from komorebi-application-specific-configuration\n    #[clap(alias = \"fetch-asc\")]\n    FetchAppSpecificConfiguration,\n    /// Generate a JSON Schema for applications.json\n    #[clap(alias = \"asc-schema\")]\n    ApplicationSpecificConfigurationSchema,\n    /// Generate a JSON Schema of subscription notifications\n    NotificationSchema,\n    /// Generate a JSON Schema of socket messages\n    SocketSchema,\n    /// Generate a JSON Schema of the static configuration file\n    StaticConfigSchema,\n    /// Generates a static configuration JSON file based on the current window manager state\n    GenerateStaticConfig,\n    /// Generates the komorebi.lnk shortcut in shell:startup to autostart komorebi\n    EnableAutostart(EnableAutostart),\n    /// Deletes the komorebi.lnk shortcut in shell:startup to disable autostart\n    DisableAutostart,\n}\n\n// print_query is a helper that queries komorebi and prints the response.\n// panics on error.\nfn print_query(message: &SocketMessage) {\n    match send_query(message) {\n        Ok(response) => println!(\"{response}\"),\n        Err(error) => panic!(\"{}\", error),\n    }\n}\n\nfn startup_dir() -> eyre::Result<PathBuf> {\n    let startup = dirs::home_dir()\n        .expect(\"unable to obtain user's home folder\")\n        .join(\"AppData\")\n        .join(\"Roaming\")\n        .join(\"Microsoft\")\n        .join(\"Windows\")\n        .join(\"Start Menu\")\n        .join(\"Programs\")\n        .join(\"Startup\");\n\n    if !startup.is_dir() {\n        std::fs::create_dir_all(&startup)?;\n    }\n\n    Ok(startup)\n}\n\n#[allow(clippy::too_many_lines, clippy::cognitive_complexity)]\nfn main() -> eyre::Result<()> {\n    let opts: Opts = Opts::parse();\n\n    match opts.subcmd {\n        SubCommand::Docgen(args) => {\n            let mut cli = Opts::command();\n            let subcommands = cli.get_subcommands_mut();\n\n            let output_dir = args.output.unwrap_or_else(|| PathBuf::from(\"docs/cli\"));\n            std::fs::create_dir_all(&output_dir)?;\n\n            let ignore = [\n                \"docgen\",\n                \"splash\",\n                \"alt-focus-hack\",\n                \"identify-border-overflow-application\",\n                \"load-custom-layout\",\n                \"workspace-custom-layout\",\n                \"named-workspace-custom-layout\",\n                \"workspace-custom-layout-rule\",\n                \"named-workspace-custom-layout-rule\",\n                \"focus-follows-mouse\",\n                \"toggle-focus-follows-mouse\",\n                \"format-app-specific-configuration\",\n            ];\n\n            for cmd in subcommands {\n                let name = cmd.get_name().to_string();\n                if !ignore.contains(&name.as_str()) {\n                    let help_text = cmd.render_long_help().to_string();\n                    let outpath = output_dir.join(format!(\"{name}.md\"));\n                    let markdown = format!(\"```\\n{help_text}\\n```\");\n                    std::fs::write(&outpath, markdown)?;\n                    println!(\"{}\", outpath.display());\n                }\n            }\n        }\n        SubCommand::Splash(args) => {\n            let informative_text = match args.mdm_server {\n                None => {\n                    \"It looks like you are using a corporate device enrolled in mobile device management\\n\\n\\\n                         The Komorebi License does not permit any kind of commercial use\\n\\n\\\n                         A dedicated Individual Commercial Use License is available if you wish to use this software at work\\n\\n\\\n                         You are strongly encouraged to make your employer pay for your license, either directly or via reimbursement\\n\\n\\\n                         To remove this popup in the future, run \\\"komorebic license <email>\\\" using the email address associated with your license\\n\\n\\\n                         If you are a student using komorebi on a laptop provided by your school, you can email me from your educational email account to remove this popup\".to_string()\n                }\n                Some(server) => {\n                    format!(\n                        \"It looks like you are using a corporate device enrolled in mobile device management ({server})\\n\\n\\\n                             The Komorebi License does not permit any kind of commercial use\\n\\n\\\n                             A dedicated Individual Commercial Use License is available if you wish to use this software at work\\n\\n\\\n                             You are strongly encouraged to make your employer pay for your license, either directly or via reimbursement\\n\\n\\\n                             To remove this popup in the future you can run \\\"komorebic license <email>\\\" using the email address associated with your license\\n\\n\n                             If you are a student using komorebi on a laptop provided by your school, you can email me from your educational email account to remove this popup\"\n                    )\n                }\n            };\n\n            if let Ok(response) = win_msgbox::error::<OkayCancel>(&informative_text)\n                .title(\"MDM Enrollment Detected\")\n                .topmost()\n                .icon(win_msgbox::Icon::Warning)\n                .set_foreground()\n                .show()\n            {\n                match response {\n                    OkayCancel::Okay => {\n                        open::that(\"https://lgug2z.com/software/komorebi\")?;\n                    }\n                    OkayCancel::Cancel => {}\n                }\n            }\n        }\n        SubCommand::License(args) => {\n            let _ = std::fs::remove_file(DATA_DIR.join(\"icul.validation\"));\n            std::fs::write(DATA_DIR.join(\"icul\"), &args.email)?;\n            match splash::should()? {\n                ValidationFeedback::Successful(icul_validation) => {\n                    println!(\"Individual commercial use license validation successful\");\n                    println!(\n                        \"Local validation file saved to {}\",\n                        icul_validation.display()\n                    );\n                    println!(\"\\n{}\", std::fs::read_to_string(&icul_validation)?);\n                }\n                ValidationFeedback::Unsuccessful(invalid_payload) => {\n                    println!(\n                        \"No active individual commercial use license found for {}\",\n                        args.email\n                    );\n                    println!(\"\\n{invalid_payload}\");\n                    println!(\n                        \"\\nYou can purchase an individual commercial use license at https://lgug2z.com/software/komorebi\"\n                    );\n                }\n                ValidationFeedback::NoEmail => {}\n                ValidationFeedback::NoConnectivity => {\n                    println!(\n                        \"Could not make a connection to validate an individual commercial use license for {}\",\n                        args.email\n                    );\n                }\n            }\n        }\n        SubCommand::Quickstart => {\n            fn write_file_with_prompt(\n                path: &PathBuf,\n                content: &str,\n                created_files: &mut Vec<String>,\n            ) -> eyre::Result<()> {\n                if path.exists() {\n                    print!(\n                        \"{} will be overwritten, do you want to continue? (y/N): \",\n                        path.display()\n                    );\n                    io::stdout().flush()?;\n                    let mut input = String::new();\n                    io::stdin().read_line(&mut input)?;\n                    let trimmed = input.trim().to_lowercase();\n                    if trimmed == \"y\" || trimmed == \"yes\" {\n                        std::fs::write(path, content)?;\n                        created_files.push(path.display().to_string());\n                    } else {\n                        println!(\"Skipping {}\", path.display());\n                    }\n                } else {\n                    std::fs::write(path, content)?;\n                    created_files.push(path.display().to_string());\n                }\n                Ok(())\n            }\n\n            let local_appdata_dir = dirs::data_local_dir().expect(\"could not find localdata dir\");\n            let data_dir = local_appdata_dir.join(\"komorebi\");\n            std::fs::create_dir_all(&*WHKD_CONFIG_DIR)?;\n            std::fs::create_dir_all(&*HOME_DIR)?;\n            std::fs::create_dir_all(data_dir)?;\n\n            let mut komorebi_json = include_str!(\"../../docs/komorebi.example.json\").to_string();\n            let komorebi_bar_json =\n                include_str!(\"../../docs/komorebi.bar.example.json\").to_string();\n\n            if std::env::var(\"KOMOREBI_CONFIG_HOME\").is_ok() {\n                komorebi_json =\n                    komorebi_json.replace(\"Env:USERPROFILE\", \"Env:KOMOREBI_CONFIG_HOME\");\n            }\n\n            let komorebi_path = HOME_DIR.join(\"komorebi.json\");\n            let bar_path = HOME_DIR.join(\"komorebi.bar.json\");\n            let applications_path = HOME_DIR.join(\"applications.json\");\n            let whkdrc_path = WHKD_CONFIG_DIR.join(\"whkdrc\");\n\n            let mut written_files = Vec::new();\n\n            write_file_with_prompt(&komorebi_path, &komorebi_json, &mut written_files)?;\n            write_file_with_prompt(&bar_path, &komorebi_bar_json, &mut written_files)?;\n\n            let applications_json = include_str!(\"../applications.json\");\n            write_file_with_prompt(&applications_path, applications_json, &mut written_files)?;\n\n            let whkdrc = include_str!(\"../../docs/whkdrc.sample\");\n            write_file_with_prompt(&whkdrc_path, whkdrc, &mut written_files)?;\n            if written_files.is_empty() {\n                println!(\"\\nNo files were written.\")\n            } else {\n                println!(\n                    \"\\nThe following example files were written:\\n{}\",\n                    written_files.join(\"\\n\")\n                );\n            }\n\n            if let Ok((mdm, server)) = splash::mdm_enrollment()\n                && mdm\n            {\n                if let Some(server) = server {\n                    println!(\n                        \"\\nIt looks like you are using a corporate device enrolled in mobile device management ({server})\"\n                    );\n                } else {\n                    println!(\n                        \"\\nIt looks like you are using a corporate device enrolled in mobile device management\"\n                    );\n                }\n                println!(\"The Komorebi License does not permit any kind of commercial use\");\n                println!(\n                    \"A dedicated Individual Commercial Use License is available if you wish to use this software at work\"\n                );\n                println!(\n                    \"You are strongly encouraged to make your employer pay for your license, either directly or via reimbursement\"\n                );\n                println!(\n                    \"If you already have a license, you can run \\\"komorebic license <email>\\\" with the email address your license is associated with\"\n                );\n            }\n\n            println!(\"\\nYou can now run komorebic start --whkd --bar\");\n        }\n        SubCommand::EnableAutostart(args) => {\n            if args.ahk {\n                println!(\n                    \"EOL: The --ahk flag is now end-of-life and will not receive any further updates or bug fixes\"\n                );\n            }\n\n            let mut current_exe = std::env::current_exe().expect(\"unable to get exec path\");\n            current_exe.pop();\n            let komorebic_exe = current_exe.join(\"komorebic-no-console.exe\");\n            let komorebic_exe = dunce::simplified(&komorebic_exe);\n\n            let startup_dir = startup_dir()?;\n            let shortcut_file = startup_dir.join(\"komorebi.lnk\");\n            let shortcut_file = dunce::simplified(&shortcut_file);\n\n            let mut arguments = String::from(\"start\");\n\n            if let Some(config) = args.config {\n                arguments.push_str(\" --config \");\n                arguments.push_str(&config.to_string_lossy());\n            }\n\n            if args.ffm {\n                arguments.push_str(\" --ffm\");\n            }\n\n            if args.bar {\n                arguments.push_str(\" --bar\");\n            }\n\n            if args.whkd {\n                arguments.push_str(\" --whkd\");\n            } else if args.ahk {\n                arguments.push_str(\" --ahk\");\n            }\n\n            if args.masir {\n                arguments.push_str(\" --masir\");\n            }\n\n            Command::new(\"powershell\")\n                .arg(\"-c\")\n                .arg(\"$WshShell = New-Object -comObject WScript.Shell; $Shortcut = $WshShell.CreateShortcut($env:SHORTCUT_PATH); $Shortcut.TargetPath = $env:TARGET_PATH; $Shortcut.Arguments = $env:TARGET_ARGS; $Shortcut.Save()\")\n                .env(\"SHORTCUT_PATH\", shortcut_file.as_os_str())\n                .env(\"TARGET_PATH\", komorebic_exe.as_os_str())\n                .env(\"TARGET_ARGS\", arguments)\n                .output()?;\n\n            println!(\n                \"NOTE: If your komorebi.json file contains a reference to $Env:KOMOREBI_CONFIG_HOME,\"\n            );\n            println!(\n                \"you need to add this to System Properties > Environment Variables > User Variables\"\n            );\n            println!(\"in order for the autostart command to work properly\");\n        }\n        SubCommand::DisableAutostart => {\n            let startup_dir = startup_dir()?;\n            let shortcut_file = startup_dir.join(\"komorebi.lnk\");\n\n            if shortcut_file.is_file() {\n                std::fs::remove_file(shortcut_file)?;\n            }\n        }\n        SubCommand::Check(args) => {\n            let home_display = HOME_DIR.display();\n            if HAS_CUSTOM_CONFIG_HOME.load(Ordering::SeqCst) {\n                println!(\"KOMOREBI_CONFIG_HOME detected: {home_display}\\n\");\n            } else {\n                println!(\n                    \"No KOMOREBI_CONFIG_HOME detected, defaulting to {}\\n\",\n                    dirs::home_dir()\n                        .expect(\"could not find home dir\")\n                        .to_string_lossy()\n                );\n            }\n\n            println!(\"Looking for configuration files in {home_display}\\n\");\n\n            let static_config = if let Some(static_config) = args.komorebi_config {\n                println!(\n                    \"Using an arbitrary configuration file passed to --komorebi-config flag\\n\"\n                );\n                static_config\n            } else {\n                HOME_DIR.join(\"komorebi.json\")\n            };\n\n            let config_pwsh = HOME_DIR.join(\"komorebi.ps1\");\n            let config_ahk = HOME_DIR.join(\"komorebi.ahk\");\n            let config_whkd = WHKD_CONFIG_DIR.join(\"whkdrc\");\n\n            if static_config.exists() {\n                let config_source = std::fs::read_to_string(&static_config)?;\n                let lines: Vec<_> = config_source.lines().collect();\n                let parsed_config = serde_json::from_str::<serde_json::Value>(&config_source);\n                if let Err(serde_error) = &parsed_config {\n                    let line = lines[serde_error.line() - 2];\n\n                    let offset = SourceOffset::from_location(\n                        config_source.clone(),\n                        serde_error.line() - 1,\n                        line.len(),\n                    );\n\n                    let error_string = serde_error.to_string();\n                    let msgs: Vec<_> = error_string.split(\" at \").collect();\n\n                    let diagnostic = ConfigurationError {\n                        message: msgs[0].to_string(),\n                        src: NamedSource::new(\"komorebi.json\", config_source.clone()),\n                        bad_bit: SourceSpan::new(offset, 2),\n                    };\n\n                    println!(\"{:?}\", Report::new(diagnostic));\n                }\n\n                println!(\n                    \"Found komorebi.json; this file can be passed to the start command with the --config flag\\n\"\n                );\n\n                if let Ok(config) = StaticConfig::read(&static_config) {\n                    match config.app_specific_configuration_path {\n                        None => {\n                            println!(\n                                \"Application specific configuration file path has not been set. Try running 'komorebic fetch-asc'\\n\"\n                            );\n                        }\n                        Some(AppSpecificConfigurationPath::Single(path)) => {\n                            if !path.exists() {\n                                println!(\n                                    \"Application specific configuration file path '{}' does not exist. Try running 'komorebic fetch-asc'\\n\",\n                                    path.display()\n                                );\n                            }\n                        }\n                        _ => {}\n                    }\n                }\n\n                // Check that this file adheres to the schema static config schema as the last step,\n                // so that more basic errors above can be shown to the error before schema-specific\n                // errors\n                let _ = serde_json::from_str::<StaticConfig>(&config_source)?;\n\n                let raw = std::fs::read_to_string(static_config)?;\n                StaticConfig::aliases(&raw);\n                StaticConfig::deprecated(&raw);\n                StaticConfig::end_of_life(&raw);\n\n                if config_whkd.exists() {\n                    println!(\n                        \"Found {}; key bindings will be loaded from here when whkd is started, and you can start it automatically using the --whkd flag\\n\",\n                        config_whkd.to_string_lossy()\n                    );\n                } else {\n                    println!(\n                        \"No ~/.config/whkdrc found; you may not be able to control komorebi with your keyboard\\n\"\n                    );\n                }\n            } else if config_pwsh.exists() {\n                println!(\"Found komorebi.ps1; this file will be autoloaded by komorebi\\n\");\n                if config_whkd.exists() {\n                    println!(\n                        \"Found {}; key bindings will be loaded from here when whkd is started\\n\",\n                        config_whkd.to_string_lossy()\n                    );\n                } else {\n                    println!(\n                        \"No ~/.config/whkdrc found; you may not be able to control komorebi with your keyboard\\n\"\n                    );\n                }\n            } else if config_ahk.exists() {\n                println!(\"Found komorebi.ahk; this file will be autoloaded by komorebi\\n\");\n            } else {\n                println!(\"No komorebi configuration found in {home_display}\\n\");\n                println!(\n                    \"If running 'komorebic start --await-configuration', you will manually have to call the following command to begin tiling: komorebic complete-configuration\\n\"\n                );\n            }\n\n            let client = reqwest::blocking::Client::new();\n\n            if let Ok(response) = client\n                .get(\"https://api.github.com/repos/LGUG2Z/komorebi/releases/latest\")\n                .header(\"User-Agent\", \"komorebic-version-checker\")\n                .send()\n            {\n                let version = env!(\"CARGO_PKG_VERSION\");\n\n                #[derive(Deserialize)]\n                struct Release {\n                    tag_name: String,\n                }\n\n                if let Ok(release) =\n                    serde_json::from_str::<Release>(&response.text().unwrap_or_default())\n                {\n                    let trimmed = release.tag_name.trim_start_matches(\"v\");\n                    if trimmed > version {\n                        println!(\n                            \"An updated version of komorebi is available! https://github.com/LGUG2Z/komorebi/releases/v{trimmed}\"\n                        );\n                    }\n                }\n            }\n        }\n        SubCommand::Configuration => {\n            let static_config = HOME_DIR.join(\"komorebi.json\");\n\n            if static_config.exists() {\n                println!(\"{}\", static_config.display());\n            }\n        }\n        SubCommand::BarConfiguration => {\n            let static_config = HOME_DIR.join(\"komorebi.bar.json\");\n\n            if static_config.exists() {\n                println!(\"{}\", static_config.display());\n            }\n        }\n        SubCommand::Whkdrc => {\n            let whkdrc = WHKD_CONFIG_DIR.join(\"whkdrc\");\n\n            if whkdrc.exists() {\n                println!(\"{}\", whkdrc.display());\n            }\n        }\n        SubCommand::DataDirectory => {\n            let dir = &*DATA_DIR;\n            if dir.exists() {\n                println!(\"{}\", dir.display());\n            }\n        }\n        SubCommand::Log => {\n            let timestamp = Utc::now().format(\"%Y-%m-%d\").to_string();\n            let color_log = std::env::temp_dir().join(format!(\"komorebi.log.{timestamp}\"));\n            let file = TailedFile::new(File::open(color_log)?);\n            let locked = file.lock();\n            #[allow(clippy::significant_drop_in_scrutinee, clippy::lines_filter_map_ok)]\n            for line in locked.lines().flatten() {\n                println!(\"{line}\");\n            }\n        }\n        SubCommand::Focus(args) => {\n            send_message(&SocketMessage::FocusWindow(args.operation_direction))?;\n        }\n        SubCommand::ForceFocus => {\n            send_message(&SocketMessage::ForceFocus)?;\n        }\n        SubCommand::Close => {\n            send_message(&SocketMessage::Close)?;\n        }\n        SubCommand::Minimize => {\n            send_message(&SocketMessage::Minimize)?;\n        }\n        SubCommand::Promote => {\n            send_message(&SocketMessage::Promote)?;\n        }\n        SubCommand::PromoteSwap => {\n            send_message(&SocketMessage::PromoteSwap)?;\n        }\n        SubCommand::PromoteFocus => {\n            send_message(&SocketMessage::PromoteFocus)?;\n        }\n        SubCommand::PromoteWindow(args) => {\n            send_message(&SocketMessage::PromoteWindow(args.operation_direction))?;\n        }\n        SubCommand::TogglePause => {\n            send_message(&SocketMessage::TogglePause)?;\n        }\n        SubCommand::Retile => {\n            send_message(&SocketMessage::Retile)?;\n        }\n        SubCommand::Move(args) => {\n            send_message(&SocketMessage::MoveWindow(args.operation_direction))?;\n        }\n        SubCommand::PreselectDirection(args) => {\n            send_message(&SocketMessage::PreselectDirection(args.operation_direction))?;\n        }\n        SubCommand::CancelPreselect => {\n            send_message(&SocketMessage::CancelPreselect)?;\n        }\n        SubCommand::CycleFocus(args) => {\n            send_message(&SocketMessage::CycleFocusWindow(args.cycle_direction))?;\n        }\n        SubCommand::CycleMove(args) => {\n            send_message(&SocketMessage::CycleMoveWindow(args.cycle_direction))?;\n        }\n        SubCommand::EagerFocus(args) => {\n            send_message(&SocketMessage::EagerFocus(args.exe))?;\n        }\n        SubCommand::MoveToMonitor(args) => {\n            send_message(&SocketMessage::MoveContainerToMonitorNumber(args.target))?;\n        }\n        SubCommand::CycleMoveToMonitor(args) => {\n            send_message(&SocketMessage::CycleMoveContainerToMonitor(\n                args.cycle_direction,\n            ))?;\n        }\n        SubCommand::MoveToWorkspace(args) => {\n            send_message(&SocketMessage::MoveContainerToWorkspaceNumber(args.target))?;\n        }\n        SubCommand::MoveToNamedWorkspace(args) => {\n            send_message(&SocketMessage::MoveContainerToNamedWorkspace(\n                args.workspace,\n            ))?;\n        }\n        SubCommand::CycleMoveToWorkspace(args) => {\n            send_message(&SocketMessage::CycleMoveContainerToWorkspace(\n                args.cycle_direction,\n            ))?;\n        }\n        SubCommand::SendToMonitor(args) => {\n            send_message(&SocketMessage::SendContainerToMonitorNumber(args.target))?;\n        }\n        SubCommand::CycleSendToMonitor(args) => {\n            send_message(&SocketMessage::CycleSendContainerToMonitor(\n                args.cycle_direction,\n            ))?;\n        }\n        SubCommand::SendToWorkspace(args) => {\n            send_message(&SocketMessage::SendContainerToWorkspaceNumber(args.target))?;\n        }\n        SubCommand::SendToNamedWorkspace(args) => {\n            send_message(&SocketMessage::SendContainerToNamedWorkspace(\n                args.workspace,\n            ))?;\n        }\n        SubCommand::CycleSendToWorkspace(args) => {\n            send_message(&SocketMessage::CycleSendContainerToWorkspace(\n                args.cycle_direction,\n            ))?;\n        }\n        SubCommand::SendToMonitorWorkspace(args) => {\n            send_message(&SocketMessage::SendContainerToMonitorWorkspaceNumber(\n                args.target_monitor,\n                args.target_workspace,\n            ))?;\n        }\n        SubCommand::MoveToMonitorWorkspace(args) => {\n            send_message(&SocketMessage::MoveContainerToMonitorWorkspaceNumber(\n                args.target_monitor,\n                args.target_workspace,\n            ))?;\n        }\n        SubCommand::MoveWorkspaceToMonitor(args) => {\n            send_message(&SocketMessage::MoveWorkspaceToMonitorNumber(args.target))?;\n        }\n        SubCommand::CycleMoveWorkspaceToMonitor(args) => {\n            send_message(&SocketMessage::CycleMoveWorkspaceToMonitor(\n                args.cycle_direction,\n            ))?;\n        }\n        SubCommand::MoveToLastWorkspace => {\n            send_message(&SocketMessage::MoveContainerToLastWorkspace)?;\n        }\n        SubCommand::SendToLastWorkspace => {\n            send_message(&SocketMessage::SendContainerToLastWorkspace)?;\n        }\n        SubCommand::SwapWorkspacesWithMonitor(args) => {\n            send_message(&SocketMessage::SwapWorkspacesToMonitorNumber(args.target))?;\n        }\n        SubCommand::InvisibleBorders(args) => {\n            send_message(&SocketMessage::InvisibleBorders(Rect {\n                left: args.left,\n                top: args.top,\n                right: args.right,\n                bottom: args.bottom,\n            }))?;\n        }\n        SubCommand::MonitorWorkAreaOffset(args) => {\n            send_message(&SocketMessage::MonitorWorkAreaOffset(\n                args.monitor,\n                Rect {\n                    left: args.left,\n                    top: args.top,\n                    right: args.right,\n                    bottom: args.bottom,\n                },\n            ))?;\n        }\n        SubCommand::GlobalWorkAreaOffset(args) => {\n            send_message(&SocketMessage::WorkAreaOffset(Rect {\n                left: args.left,\n                top: args.top,\n                right: args.right,\n                bottom: args.bottom,\n            }))?;\n        }\n\n        SubCommand::WorkspaceWorkAreaOffset(args) => {\n            send_message(&SocketMessage::WorkspaceWorkAreaOffset(\n                args.monitor,\n                args.workspace,\n                Rect {\n                    left: args.left,\n                    top: args.top,\n                    right: args.right,\n                    bottom: args.bottom,\n                },\n            ))?;\n        }\n\n        SubCommand::ToggleWindowBasedWorkAreaOffset => {\n            send_message(&SocketMessage::ToggleWindowBasedWorkAreaOffset)?;\n        }\n        SubCommand::ContainerPadding(args) => {\n            send_message(&SocketMessage::ContainerPadding(\n                args.monitor,\n                args.workspace,\n                args.size,\n            ))?;\n        }\n        SubCommand::NamedWorkspaceContainerPadding(args) => {\n            send_message(&SocketMessage::NamedWorkspaceContainerPadding(\n                args.workspace,\n                args.size,\n            ))?;\n        }\n        SubCommand::WorkspacePadding(args) => {\n            send_message(&SocketMessage::WorkspacePadding(\n                args.monitor,\n                args.workspace,\n                args.size,\n            ))?;\n        }\n        SubCommand::NamedWorkspacePadding(args) => {\n            send_message(&SocketMessage::NamedWorkspacePadding(\n                args.workspace,\n                args.size,\n            ))?;\n        }\n        SubCommand::FocusedWorkspacePadding(args) => {\n            send_message(&SocketMessage::FocusedWorkspacePadding(args.size))?;\n        }\n        SubCommand::FocusedWorkspaceContainerPadding(args) => {\n            send_message(&SocketMessage::FocusedWorkspaceContainerPadding(args.size))?;\n        }\n        SubCommand::AdjustWorkspacePadding(args) => {\n            send_message(&SocketMessage::AdjustWorkspacePadding(\n                args.sizing,\n                args.adjustment,\n            ))?;\n        }\n        SubCommand::AdjustContainerPadding(args) => {\n            send_message(&SocketMessage::AdjustContainerPadding(\n                args.sizing,\n                args.adjustment,\n            ))?;\n        }\n        SubCommand::ToggleFocusFollowsMouse(args) => {\n            send_message(&SocketMessage::ToggleFocusFollowsMouse(args.implementation))?;\n        }\n        SubCommand::ToggleTiling => {\n            send_message(&SocketMessage::ToggleTiling)?;\n        }\n        SubCommand::ToggleFloat => {\n            send_message(&SocketMessage::ToggleFloat)?;\n        }\n        SubCommand::ToggleMonocle => {\n            send_message(&SocketMessage::ToggleMonocle)?;\n        }\n        SubCommand::ToggleMaximize => {\n            send_message(&SocketMessage::ToggleMaximize)?;\n        }\n        SubCommand::ToggleLock => {\n            send_message(&SocketMessage::ToggleLock)?;\n        }\n        SubCommand::WorkspaceLayout(args) => {\n            send_message(&SocketMessage::WorkspaceLayout(\n                args.monitor,\n                args.workspace,\n                args.value,\n            ))?;\n        }\n        SubCommand::NamedWorkspaceLayout(args) => {\n            send_message(&SocketMessage::NamedWorkspaceLayout(\n                args.workspace,\n                args.value,\n            ))?;\n        }\n        SubCommand::WorkspaceCustomLayout(args) => {\n            send_message(&SocketMessage::WorkspaceLayoutCustom(\n                args.monitor,\n                args.workspace,\n                args.path,\n            ))?;\n        }\n        SubCommand::NamedWorkspaceCustomLayout(args) => {\n            send_message(&SocketMessage::NamedWorkspaceLayoutCustom(\n                args.workspace,\n                args.path,\n            ))?;\n        }\n        SubCommand::WorkspaceLayoutRule(args) => {\n            send_message(&SocketMessage::WorkspaceLayoutRule(\n                args.monitor,\n                args.workspace,\n                args.at_container_count,\n                args.layout,\n            ))?;\n        }\n        SubCommand::NamedWorkspaceLayoutRule(args) => {\n            send_message(&SocketMessage::NamedWorkspaceLayoutRule(\n                args.workspace,\n                args.at_container_count,\n                args.layout,\n            ))?;\n        }\n        SubCommand::WorkspaceCustomLayoutRule(args) => {\n            send_message(&SocketMessage::WorkspaceLayoutCustomRule(\n                args.monitor,\n                args.workspace,\n                args.at_container_count,\n                args.path,\n            ))?;\n        }\n        SubCommand::NamedWorkspaceCustomLayoutRule(args) => {\n            send_message(&SocketMessage::NamedWorkspaceLayoutCustomRule(\n                args.workspace,\n                args.at_container_count,\n                args.path,\n            ))?;\n        }\n        SubCommand::ClearWorkspaceLayoutRules(args) => {\n            send_message(&SocketMessage::ClearWorkspaceLayoutRules(\n                args.monitor,\n                args.workspace,\n            ))?;\n        }\n        SubCommand::ClearNamedWorkspaceLayoutRules(args) => {\n            send_message(&SocketMessage::ClearNamedWorkspaceLayoutRules(\n                args.workspace,\n            ))?;\n        }\n        SubCommand::WorkspaceTiling(args) => {\n            send_message(&SocketMessage::WorkspaceTiling(\n                args.monitor,\n                args.workspace,\n                args.value.into(),\n            ))?;\n        }\n        SubCommand::NamedWorkspaceTiling(args) => {\n            send_message(&SocketMessage::NamedWorkspaceTiling(\n                args.workspace,\n                args.value.into(),\n            ))?;\n        }\n        SubCommand::Start(args) => {\n            if args.ahk {\n                println!(\n                    \"EOL: The --ahk flag is now end-of-life and will not receive any further updates or bug fixes\"\n                );\n            }\n\n            let mut ahk: String = String::from(\"autohotkey.exe\");\n\n            if let Ok(komorebi_ahk_exe) = std::env::var(\"KOMOREBI_AHK_EXE\")\n                && which(&komorebi_ahk_exe).is_ok()\n            {\n                ahk = komorebi_ahk_exe;\n            }\n\n            if args.whkd && which(\"whkd\").is_err() {\n                bail!(\n                    \"could not find whkd, please make sure it is installed before using the --whkd flag\"\n                );\n            }\n\n            if args.masir && which(\"masir\").is_err() {\n                bail!(\n                    \"could not find masir, please make sure it is installed before using the --masir flag\"\n                );\n            }\n\n            if args.ahk && which(&ahk).is_err() {\n                bail!(\n                    \"could not find autohotkey, please make sure it is installed before using the --ahk flag\"\n                );\n            }\n\n            let mut buf: PathBuf;\n\n            // The komorebi.ps1 shim will only exist in the Path if installed by Scoop\n            let exec =\n                if let Ok(output) = Command::new(\"where.exe\").arg(\"komorebi.ps1\").output() {\n                    let stdout = String::from_utf8(output.stdout)?;\n                    match stdout.trim() {\n                        \"\" => None,\n                        // It's possible that a komorebi.ps1 config will be in %USERPROFILE% - ignore this\n                        stdout if !stdout.contains(\"scoop\") => None,\n                        stdout => {\n                            buf = PathBuf::from(stdout);\n                            buf.pop(); // %USERPROFILE%\\scoop\\shims\n                            buf.pop(); // %USERPROFILE%\\scoop\n                            buf.push(\"apps\\\\komorebi\\\\current\\\\komorebi.exe\"); //%USERPROFILE%\\scoop\\komorebi\\current\\komorebi.exe\n                            Some(buf.to_str().ok_or_eyre(\n                                \"cannot create a string from the scoop komorebi path\",\n                            )?)\n                        }\n                    }\n                } else {\n                    None\n                };\n\n            let mut flags = vec![];\n            if let Some(config) = &args.config {\n                if !config.is_file() {\n                    bail!(\"could not find file: {}\", config.display());\n                }\n\n                let config = dunce::simplified(config);\n                flags.push(format!(\"'--config=\\\"{}\\\"'\", config.display()));\n            }\n\n            if args.ffm {\n                flags.push(\"'--ffm'\".to_string());\n            }\n\n            if args.await_configuration {\n                flags.push(\"'--await-configuration'\".to_string());\n            }\n\n            if let Some(port) = args.tcp_port {\n                flags.push(format!(\"'--tcp-port={port}'\"));\n            }\n\n            if args.clean_state {\n                flags.push(\"'--clean-state'\".to_string());\n            }\n\n            let exec = exec.unwrap_or(\"komorebi.exe\");\n            let script = if flags.is_empty() {\n                format!(\"Start-Process '{exec}' -WindowStyle hidden\",)\n            } else {\n                let argument_list = flags.join(\",\");\n                format!(\"Start-Process '{exec}' -ArgumentList {argument_list} -WindowStyle hidden\",)\n            };\n\n            let mut system = sysinfo::System::new_all();\n            system.refresh_processes(ProcessesToUpdate::All, true);\n\n            let mut attempts = 0;\n            let mut running = system\n                .processes_by_name(\"komorebi.exe\".as_ref())\n                .next()\n                .is_some();\n\n            while !running && attempts <= 2 {\n                match powershell_script::run(&script) {\n                    Ok(_) => {\n                        println!(\"{script}\");\n                    }\n                    Err(error) => {\n                        println!(\"Error: {error}\");\n                    }\n                }\n\n                print!(\"Waiting for komorebi.exe to start...\");\n                std::thread::sleep(Duration::from_secs(3));\n\n                system.refresh_processes(ProcessesToUpdate::All, true);\n\n                if system\n                    .processes_by_name(\"komorebi.exe\".as_ref())\n                    .next()\n                    .is_some()\n                {\n                    println!(\"Started!\");\n                    running = true;\n                } else {\n                    println!(\"komorebi.exe did not start... Trying again\");\n                    attempts += 1;\n                }\n            }\n\n            if !running {\n                println!(\"\\nRunning komorebi.exe directly for detailed error output\\n\");\n                if let Some(config) = args.config {\n                    if let Ok(output) = Command::new(\"komorebi.exe\")\n                        .arg(format!(\"'--config=\\\"{}\\\"'\", config.display()))\n                        .output()\n                    {\n                        println!(\"{}\", String::from_utf8(output.stderr)?);\n                    }\n                } else if let Ok(output) = Command::new(\"komorebi.exe\").output() {\n                    println!(\"{}\", String::from_utf8(output.stderr)?);\n                }\n\n                return Ok(());\n            }\n\n            if args.whkd {\n                let script = r\"\nif (!(Get-Process whkd -ErrorAction SilentlyContinue))\n{\n  Start-Process whkd -WindowStyle hidden\n}\n                \";\n                match powershell_script::run(script) {\n                    Ok(_) => {\n                        println!(\"{script}\");\n                    }\n                    Err(error) => {\n                        println!(\"Error: {error}\");\n                    }\n                }\n            }\n\n            if args.ahk {\n                let config_ahk = HOME_DIR.join(\"komorebi.ahk\");\n                let config_ahk = dunce::simplified(&config_ahk);\n\n                let script = format!(\n                    r#\"\n  Start-Process '\"{ahk}\"' '\"{config}\"' -WindowStyle hidden\n                \"#,\n                    config = config_ahk.display()\n                );\n\n                match powershell_script::run(&script) {\n                    Ok(_) => {\n                        println!(\"{script}\");\n                    }\n                    Err(error) => {\n                        println!(\"Error: {error}\");\n                    }\n                }\n            }\n\n            let static_config = args.config.clone().or_else(|| {\n                let komorebi_json = HOME_DIR.join(\"komorebi.json\");\n                komorebi_json.is_file().then_some(komorebi_json)\n            });\n\n            if args.bar {\n                let mut fallthrough = false;\n                match static_config {\n                    None => {\n                        fallthrough = true;\n                    }\n                    Some(ref config) => {\n                        let mut config = StaticConfig::read(config)?;\n                        if let Some(display_bar_configurations) = &mut config.bar_configurations {\n                            for config_file_path in &mut *display_bar_configurations {\n                                let script = format!(\n                                    r#\"Start-Process \"komorebi-bar\" '\"--config\" \"{}\"' -WindowStyle hidden\"#,\n                                    config_file_path.to_string_lossy()\n                                );\n\n                                match powershell_script::run(&script) {\n                                    Ok(_) => {\n                                        println!(\"{script}\");\n                                    }\n                                    Err(error) => {\n                                        println!(\"Error: {error}\");\n                                    }\n                                }\n                            }\n                        } else {\n                            fallthrough = true;\n                        }\n                    }\n                }\n\n                if fallthrough {\n                    let script = r\"\nif (!(Get-Process komorebi-bar -ErrorAction SilentlyContinue))\n{\n  Start-Process komorebi-bar -WindowStyle hidden\n}\n                \";\n                    match powershell_script::run(script) {\n                        Ok(_) => {\n                            println!(\"{script}\");\n                        }\n                        Err(error) => {\n                            println!(\"Error: {error}\");\n                        }\n                    }\n                }\n            }\n\n            if args.masir {\n                let script = r\"\nif (!(Get-Process masir -ErrorAction SilentlyContinue))\n{\n  Start-Process masir -WindowStyle hidden\n}\n                \";\n                match powershell_script::run(script) {\n                    Ok(_) => {\n                        println!(\"{script}\");\n                    }\n                    Err(error) => {\n                        println!(\"Error: {error}\");\n                    }\n                }\n            }\n\n            println!(\"\\nThank you for using komorebi!\\n\");\n            println!(\"# Commercial Use License\");\n            println!(\n                \"* View licensing options https://lgug2z.com/software/komorebi - A commercial use license is required to use komorebi at work\"\n            );\n            println!(\"\\n# Personal Use Sponsorship\");\n            println!(\n                \"* Become a sponsor https://github.com/sponsors/LGUG2Z - $5/month makes a big difference\"\n            );\n            println!(\"* Leave a tip https://ko-fi.com/lgug2z - An alternative to GitHub Sponsors\");\n            println!(\"\\n# Community\");\n            println!(\n                \"* Join the Discord https://discord.gg/mGkn66PHkx - Chat, ask questions, share your desktops\"\n            );\n            println!(\n                \"* Subscribe to https://youtube.com/@LGUG2Z - Development videos, feature previews and release overviews\"\n            );\n            println!(\n                \"* Explore the Awesome Komorebi list https://github.com/LGUG2Z/awesome-komorebi - Projects in the komorebi ecosystem\"\n            );\n            println!(\"\\n# Documentation\");\n            println!(\n                \"* Read the docs https://lgug2z.github.io/komorebi - Quickly search through all komorebic commands\"\n            );\n\n            let bar_config = args.config.or_else(|| {\n                let bar_json = HOME_DIR.join(\"komorebi.bar.json\");\n                bar_json.is_file().then_some(bar_json)\n            });\n\n            if let Some(config) = &static_config {\n                let raw = std::fs::read_to_string(config)?;\n                StaticConfig::aliases(&raw);\n                StaticConfig::deprecated(&raw);\n                StaticConfig::end_of_life(&raw);\n            }\n\n            if bar_config.is_some() {\n                let output = Command::new(\"komorebi-bar.exe\").arg(\"--aliases\").output()?;\n                let stdout = String::from_utf8(output.stdout)?;\n                println!(\"{stdout}\");\n            }\n\n            let client = reqwest::blocking::Client::new();\n\n            if let Ok(response) = client\n                .get(\"https://api.github.com/repos/LGUG2Z/komorebi/releases/latest\")\n                .header(\"User-Agent\", \"komorebic-version-checker\")\n                .send()\n            {\n                let version = env!(\"CARGO_PKG_VERSION\");\n\n                #[derive(Deserialize)]\n                struct Release {\n                    tag_name: String,\n                }\n\n                if let Ok(release) =\n                    serde_json::from_str::<Release>(&response.text().unwrap_or_default())\n                {\n                    let trimmed = release.tag_name.trim_start_matches(\"v\");\n                    if trimmed > version {\n                        println!(\n                            \"An updated version of komorebi is available! https://github.com/LGUG2Z/komorebi/releases/v{trimmed}\"\n                        );\n                    }\n                }\n            }\n        }\n        SubCommand::Stop(args) => {\n            if args.ahk {\n                println!(\n                    \"EOL: The --ahk flag is now end-of-life and will not receive any further updates or bug fixes\"\n                );\n            }\n\n            if args.whkd {\n                let script = r\"\nStop-Process -Name:whkd -ErrorAction SilentlyContinue\n                \";\n                match powershell_script::run(script) {\n                    Ok(_) => {\n                        println!(\"{script}\");\n                    }\n                    Err(error) => {\n                        println!(\"Error: {error}\");\n                    }\n                }\n            }\n\n            if args.bar {\n                let script = r\"\nStop-Process -Name:komorebi-bar -ErrorAction SilentlyContinue\n                \";\n                match powershell_script::run(script) {\n                    Ok(_) => {\n                        println!(\"{script}\");\n                    }\n                    Err(error) => {\n                        println!(\"Error: {error}\");\n                    }\n                }\n            }\n\n            if args.masir {\n                let script = r\"\nStop-Process -Name:masir -ErrorAction SilentlyContinue\n                \";\n                match powershell_script::run(script) {\n                    Ok(_) => {\n                        println!(\"{script}\");\n                    }\n                    Err(error) => {\n                        println!(\"Error: {error}\");\n                    }\n                }\n            }\n\n            if args.ahk {\n                let script = r#\"\nif (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) {\n    (Get-CimInstance Win32_Process | Where-Object {\n        ($_.CommandLine -like '*komorebi.ahk\"') -and\n        ($_.Name -in @('AutoHotkey.exe', 'AutoHotkey64.exe', 'AutoHotkey32.exe', 'AutoHotkeyUX.exe'))\n    } | Select-Object -First 1) | ForEach-Object {\n        Stop-Process -Id $_.ProcessId -ErrorAction SilentlyContinue\n    }\n} else {\n    (Get-WmiObject Win32_Process | Where-Object {\n        ($_.CommandLine -like '*komorebi.ahk\"') -and\n        ($_.Name -in @('AutoHotkey.exe', 'AutoHotkey64.exe', 'AutoHotkey32.exe', 'AutoHotkeyUX.exe'))\n    } | Select-Object -First 1) | ForEach-Object {\n        Stop-Process -Id $_.ProcessId -ErrorAction SilentlyContinue\n    }\n}\n\"#;\n\n                match powershell_script::run(script) {\n                    Ok(_) => {\n                        println!(\"{script}\");\n                    }\n                    Err(error) => {\n                        println!(\"Error: {error}\");\n                    }\n                }\n            }\n\n            if args.ignore_restore {\n                send_message(&SocketMessage::StopIgnoreRestore)?;\n            } else {\n                send_message(&SocketMessage::Stop)?;\n            }\n            let mut system = sysinfo::System::new_all();\n            system.refresh_processes(ProcessesToUpdate::All, true);\n\n            if system.processes_by_name(\"komorebi.exe\".as_ref()).count() >= 1 {\n                println!(\"komorebi is still running, attempting to force-quit\");\n\n                let script = r\"\nStop-Process -Name:komorebi -ErrorAction SilentlyContinue\n                \";\n                match powershell_script::run(script) {\n                    Ok(_) => {\n                        println!(\"{script}\");\n\n                        let hwnd_json = DATA_DIR.join(\"komorebi.hwnd.json\");\n\n                        let file = File::open(hwnd_json)?;\n                        let reader = BufReader::new(file);\n                        let hwnds: Vec<isize> = serde_json::from_reader(reader)?;\n\n                        for hwnd in hwnds {\n                            restore_window(hwnd);\n                        }\n                    }\n                    Err(error) => {\n                        println!(\"Error: {error}\");\n                    }\n                }\n            }\n        }\n        SubCommand::Kill(args) => {\n            if args.ahk {\n                println!(\n                    \"EOL: The --ahk flag is now end-of-life and will not receive any further updates or bug fixes\"\n                );\n            }\n\n            if args.whkd {\n                let script = r\"\nStop-Process -Name:whkd -ErrorAction SilentlyContinue\n                \";\n                match powershell_script::run(script) {\n                    Ok(_) => {\n                        println!(\"{script}\");\n                    }\n                    Err(error) => {\n                        println!(\"Error: {error}\");\n                    }\n                }\n            }\n\n            if args.bar {\n                let script = r\"\nStop-Process -Name:komorebi-bar -ErrorAction SilentlyContinue\n                \";\n                match powershell_script::run(script) {\n                    Ok(_) => {\n                        println!(\"{script}\");\n                    }\n                    Err(error) => {\n                        println!(\"Error: {error}\");\n                    }\n                }\n            }\n\n            if args.masir {\n                let script = r\"\nStop-Process -Name:masir -ErrorAction SilentlyContinue\n                \";\n                match powershell_script::run(script) {\n                    Ok(_) => {\n                        println!(\"{script}\");\n                    }\n                    Err(error) => {\n                        println!(\"Error: {error}\");\n                    }\n                }\n            }\n\n            if args.ahk {\n                let script = r#\"\nif (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) {\n    (Get-CimInstance Win32_Process | Where-Object {\n        ($_.CommandLine -like '*komorebi.ahk\"') -and\n        ($_.Name -in @('AutoHotkey.exe', 'AutoHotkey64.exe', 'AutoHotkey32.exe', 'AutoHotkeyUX.exe'))\n    } | Select-Object -First 1) | ForEach-Object {\n        Stop-Process -Id $_.ProcessId -ErrorAction SilentlyContinue\n    }\n} else {\n    (Get-WmiObject Win32_Process | Where-Object {\n        ($_.CommandLine -like '*komorebi.ahk\"') -and\n        ($_.Name -in @('AutoHotkey.exe', 'AutoHotkey64.exe', 'AutoHotkey32.exe', 'AutoHotkeyUX.exe'))\n    } | Select-Object -First 1) | ForEach-Object {\n        Stop-Process -Id $_.ProcessId -ErrorAction SilentlyContinue\n    }\n}\n\"#;\n\n                match powershell_script::run(script) {\n                    Ok(_) => {\n                        println!(\"{script}\");\n                    }\n                    Err(error) => {\n                        println!(\"Error: {error}\");\n                    }\n                }\n            }\n        }\n        SubCommand::SessionFloatRule => {\n            send_message(&SocketMessage::SessionFloatRule)?;\n        }\n        SubCommand::SessionFloatRules => {\n            print_query(&SocketMessage::SessionFloatRules);\n        }\n        SubCommand::ClearSessionFloatRules => {\n            send_message(&SocketMessage::ClearSessionFloatRules)?;\n        }\n        SubCommand::IgnoreRule(args) => {\n            send_message(&SocketMessage::IgnoreRule(args.identifier, args.id))?;\n        }\n        SubCommand::ManageRule(args) => {\n            send_message(&SocketMessage::ManageRule(args.identifier, args.id))?;\n        }\n        SubCommand::InitialWorkspaceRule(args) => {\n            send_message(&SocketMessage::InitialWorkspaceRule(\n                args.identifier,\n                args.id,\n                args.monitor,\n                args.workspace,\n            ))?;\n        }\n        SubCommand::InitialNamedWorkspaceRule(args) => {\n            send_message(&SocketMessage::InitialNamedWorkspaceRule(\n                args.identifier,\n                args.id,\n                args.workspace,\n            ))?;\n        }\n        SubCommand::WorkspaceRule(args) => {\n            send_message(&SocketMessage::WorkspaceRule(\n                args.identifier,\n                args.id,\n                args.monitor,\n                args.workspace,\n            ))?;\n        }\n        SubCommand::NamedWorkspaceRule(args) => {\n            send_message(&SocketMessage::NamedWorkspaceRule(\n                args.identifier,\n                args.id,\n                args.workspace,\n            ))?;\n        }\n        SubCommand::ClearWorkspaceRules(args) => {\n            send_message(&SocketMessage::ClearWorkspaceRules(\n                args.monitor,\n                args.workspace,\n            ))?;\n        }\n        SubCommand::ClearNamedWorkspaceRules(args) => {\n            send_message(&SocketMessage::ClearNamedWorkspaceRules(args.workspace))?;\n        }\n        SubCommand::ClearAllWorkspaceRules => {\n            send_message(&SocketMessage::ClearAllWorkspaceRules)?;\n        }\n        SubCommand::EnforceWorkspaceRules => {\n            send_message(&SocketMessage::EnforceWorkspaceRules)?;\n        }\n        SubCommand::Stack(args) => {\n            send_message(&SocketMessage::StackWindow(args.operation_direction))?;\n        }\n        SubCommand::StackAll => {\n            send_message(&SocketMessage::StackAll)?;\n        }\n        SubCommand::Unstack => {\n            send_message(&SocketMessage::UnstackWindow)?;\n        }\n        SubCommand::UnstackAll => {\n            send_message(&SocketMessage::UnstackAll)?;\n        }\n        SubCommand::FocusStackWindow(args) => {\n            send_message(&SocketMessage::FocusStackWindow(args.target))?;\n        }\n        SubCommand::CycleStack(args) => {\n            send_message(&SocketMessage::CycleStack(args.cycle_direction))?;\n        }\n        SubCommand::CycleStackIndex(args) => {\n            send_message(&SocketMessage::CycleStackIndex(args.cycle_direction))?;\n        }\n        SubCommand::ChangeLayout(args) => {\n            send_message(&SocketMessage::ChangeLayout(args.default_layout))?;\n        }\n        SubCommand::CycleLayout(args) => {\n            send_message(&SocketMessage::CycleLayout(args.cycle_direction))?;\n        }\n        SubCommand::ScrollingLayoutColumns(args) => {\n            send_message(&SocketMessage::ScrollingLayoutColumns(args.count))?;\n        }\n        SubCommand::LayoutRatios(args) => {\n            if args.columns.is_none() && args.rows.is_none() {\n                println!(\n                    \"No ratios provided, nothing to change. Use --columns or --rows to specify ratios.\"\n                );\n            } else {\n                send_message(&SocketMessage::LayoutRatios(args.columns, args.rows))?;\n            }\n        }\n        SubCommand::LoadCustomLayout(args) => {\n            send_message(&SocketMessage::ChangeLayoutCustom(args.path))?;\n        }\n        SubCommand::FlipLayout(args) => {\n            send_message(&SocketMessage::FlipLayout(args.axis))?;\n        }\n        SubCommand::FocusMonitor(args) => {\n            send_message(&SocketMessage::FocusMonitorNumber(args.target))?;\n        }\n        SubCommand::FocusMonitorAtCursor => {\n            send_message(&SocketMessage::FocusMonitorAtCursor)?;\n        }\n        SubCommand::FocusLastWorkspace => {\n            send_message(&SocketMessage::FocusLastWorkspace)?;\n        }\n        SubCommand::FocusWorkspace(args) => {\n            send_message(&SocketMessage::FocusWorkspaceNumber(args.target))?;\n        }\n        SubCommand::FocusWorkspaces(args) => {\n            send_message(&SocketMessage::FocusWorkspaceNumbers(args.target))?;\n        }\n        SubCommand::FocusMonitorWorkspace(args) => {\n            send_message(&SocketMessage::FocusMonitorWorkspaceNumber(\n                args.target_monitor,\n                args.target_workspace,\n            ))?;\n        }\n        SubCommand::FocusNamedWorkspace(args) => {\n            send_message(&SocketMessage::FocusNamedWorkspace(args.workspace))?;\n        }\n        SubCommand::CloseWorkspace => {\n            send_message(&SocketMessage::CloseWorkspace)?;\n        }\n        SubCommand::CycleMonitor(args) => {\n            send_message(&SocketMessage::CycleFocusMonitor(args.cycle_direction))?;\n        }\n        SubCommand::CycleWorkspace(args) => {\n            send_message(&SocketMessage::CycleFocusWorkspace(args.cycle_direction))?;\n        }\n        SubCommand::CycleEmptyWorkspace(args) => {\n            send_message(&SocketMessage::CycleFocusEmptyWorkspace(\n                args.cycle_direction,\n            ))?;\n        }\n        SubCommand::NewWorkspace => {\n            send_message(&SocketMessage::NewWorkspace)?;\n        }\n        SubCommand::WorkspaceName(name) => {\n            send_message(&SocketMessage::WorkspaceName(\n                name.monitor,\n                name.workspace,\n                name.value,\n            ))?;\n        }\n        SubCommand::MonitorIndexPreference(args) => {\n            send_message(&SocketMessage::MonitorIndexPreference(\n                args.index_preference,\n                args.left,\n                args.top,\n                args.right,\n                args.bottom,\n            ))?;\n        }\n        SubCommand::DisplayIndexPreference(args) => {\n            send_message(&SocketMessage::DisplayIndexPreference(\n                args.index_preference,\n                args.display,\n            ))?;\n        }\n        SubCommand::EnsureWorkspaces(workspaces) => {\n            send_message(&SocketMessage::EnsureWorkspaces(\n                workspaces.monitor,\n                workspaces.workspace_count,\n            ))?;\n        }\n        SubCommand::EnsureNamedWorkspaces(args) => {\n            send_message(&SocketMessage::EnsureNamedWorkspaces(\n                args.monitor,\n                args.names,\n            ))?;\n        }\n        SubCommand::State => {\n            print_query(&SocketMessage::State);\n        }\n        SubCommand::GlobalState => {\n            print_query(&SocketMessage::GlobalState);\n        }\n        SubCommand::Gui => {\n            Command::new(\"komorebi-gui\").spawn()?;\n        }\n        SubCommand::ToggleShortcuts => {\n            let output = Command::new(\"taskkill\")\n                .args([\"/F\", \"/IM\", \"komorebi-shortcuts.exe\"])\n                .output()?;\n\n            if !output.status.success() {\n                Command::new(\"komorebi-shortcuts.exe\").spawn()?;\n            }\n        }\n        SubCommand::VisibleWindows => {\n            print_query(&SocketMessage::VisibleWindows);\n        }\n        SubCommand::MonitorInformation => {\n            print_query(&SocketMessage::MonitorInformation);\n        }\n        SubCommand::Query(args) => {\n            print_query(&SocketMessage::Query(args.state_query));\n        }\n        SubCommand::RestoreWindows => {\n            let hwnd_json = DATA_DIR.join(\"komorebi.hwnd.json\");\n\n            let file = File::open(hwnd_json)?;\n            let reader = BufReader::new(file);\n            let hwnds: Vec<isize> = serde_json::from_reader(reader)?;\n\n            for hwnd in hwnds {\n                restore_window(hwnd);\n            }\n        }\n        SubCommand::ResizeEdge(resize) => {\n            send_message(&SocketMessage::ResizeWindowEdge(resize.edge, resize.sizing))?;\n        }\n        SubCommand::ResizeAxis(args) => {\n            send_message(&SocketMessage::ResizeWindowAxis(args.axis, args.sizing))?;\n        }\n        SubCommand::FocusFollowsMouse(args) => {\n            send_message(&SocketMessage::FocusFollowsMouse(\n                args.implementation,\n                args.boolean_state.into(),\n            ))?;\n        }\n        SubCommand::ReplaceConfiguration(args) => {\n            send_message(&SocketMessage::ReplaceConfiguration(args.path))?;\n        }\n        SubCommand::ReloadConfiguration => {\n            send_message(&SocketMessage::ReloadConfiguration)?;\n        }\n        SubCommand::WatchConfiguration(args) => {\n            send_message(&SocketMessage::WatchConfiguration(\n                args.boolean_state.into(),\n            ))?;\n        }\n        SubCommand::CompleteConfiguration => {\n            send_message(&SocketMessage::CompleteConfiguration)?;\n        }\n        SubCommand::IdentifyObjectNameChangeApplication(target) => {\n            send_message(&SocketMessage::IdentifyObjectNameChangeApplication(\n                target.identifier,\n                target.id,\n            ))?;\n        }\n        SubCommand::IdentifyTrayApplication(target) => {\n            send_message(&SocketMessage::IdentifyTrayApplication(\n                target.identifier,\n                target.id,\n            ))?;\n        }\n        SubCommand::IdentifyLayeredApplication(target) => {\n            send_message(&SocketMessage::IdentifyLayeredApplication(\n                target.identifier,\n                target.id,\n            ))?;\n        }\n        SubCommand::RemoveTitleBar(target) => {\n            match target.identifier {\n                ApplicationIdentifier::Exe => {}\n                _ => {\n                    bail!(\"this command requires applications to be identified by their exe\");\n                }\n            }\n\n            send_message(&SocketMessage::RemoveTitleBar(target.identifier, target.id))?;\n        }\n        SubCommand::ToggleTitleBars => {\n            send_message(&SocketMessage::ToggleTitleBars)?;\n        }\n        SubCommand::Manage => {\n            send_message(&SocketMessage::ManageFocusedWindow)?;\n        }\n        SubCommand::Unmanage => {\n            send_message(&SocketMessage::UnmanageFocusedWindow)?;\n        }\n        SubCommand::QuickSaveResize => {\n            send_message(&SocketMessage::QuickSave)?;\n        }\n        SubCommand::QuickLoadResize => {\n            send_message(&SocketMessage::QuickLoad)?;\n        }\n        SubCommand::SaveResize(args) => {\n            send_message(&SocketMessage::Save(args.path))?;\n        }\n        SubCommand::LoadResize(args) => {\n            send_message(&SocketMessage::Load(args.path))?;\n        }\n        SubCommand::SubscribeSocket(args) => {\n            send_message(&SocketMessage::AddSubscriberSocket(args.socket))?;\n        }\n        SubCommand::UnsubscribeSocket(args) => {\n            send_message(&SocketMessage::RemoveSubscriberSocket(args.socket))?;\n        }\n        SubCommand::SubscribePipe(args) => {\n            send_message(&SocketMessage::AddSubscriberPipe(args.named_pipe))?;\n        }\n        SubCommand::UnsubscribePipe(args) => {\n            send_message(&SocketMessage::RemoveSubscriberPipe(args.named_pipe))?;\n        }\n        SubCommand::ToggleMouseFollowsFocus => {\n            send_message(&SocketMessage::ToggleMouseFollowsFocus)?;\n        }\n        SubCommand::MouseFollowsFocus(args) => {\n            send_message(&SocketMessage::MouseFollowsFocus(args.boolean_state.into()))?;\n        }\n        SubCommand::Border(args) => {\n            send_message(&SocketMessage::Border(args.boolean_state.into()))?;\n        }\n        SubCommand::BorderColour(args) => {\n            send_message(&SocketMessage::BorderColour(\n                args.window_kind,\n                args.r,\n                args.g,\n                args.b,\n            ))?;\n        }\n        SubCommand::BorderWidth(args) => {\n            send_message(&SocketMessage::BorderWidth(args.width))?;\n        }\n        SubCommand::BorderOffset(args) => {\n            send_message(&SocketMessage::BorderOffset(args.offset))?;\n        }\n        SubCommand::BorderStyle(args) => {\n            send_message(&SocketMessage::BorderStyle(args.style))?;\n        }\n        SubCommand::BorderImplementation(args) => {\n            send_message(&SocketMessage::BorderImplementation(args.style))?;\n        }\n        SubCommand::StackbarMode(args) => {\n            send_message(&SocketMessage::StackbarMode(args.mode))?;\n        }\n        SubCommand::Transparency(args) => {\n            send_message(&SocketMessage::Transparency(args.boolean_state.into()))?;\n        }\n        SubCommand::TransparencyAlpha(args) => {\n            send_message(&SocketMessage::TransparencyAlpha(args.alpha))?;\n        }\n        SubCommand::ToggleTransparency => {\n            send_message(&SocketMessage::ToggleTransparency)?;\n        }\n        SubCommand::Animation(args) => {\n            send_message(&SocketMessage::Animation(\n                args.boolean_state.into(),\n                args.animation_type,\n            ))?;\n        }\n        SubCommand::AnimationDuration(args) => {\n            send_message(&SocketMessage::AnimationDuration(\n                args.duration,\n                args.animation_type,\n            ))?;\n        }\n        SubCommand::AnimationFps(args) => {\n            send_message(&SocketMessage::AnimationFps(args.fps))?;\n        }\n        SubCommand::AnimationStyle(args) => {\n            send_message(&SocketMessage::AnimationStyle(\n                args.style,\n                args.animation_type,\n            ))?;\n        }\n\n        SubCommand::ResizeDelta(args) => {\n            send_message(&SocketMessage::ResizeDelta(args.pixels))?;\n        }\n        SubCommand::ToggleWindowContainerBehaviour => {\n            send_message(&SocketMessage::ToggleWindowContainerBehaviour)?;\n        }\n        SubCommand::ToggleFloatOverride => {\n            send_message(&SocketMessage::ToggleFloatOverride)?;\n        }\n        SubCommand::ToggleWorkspaceWindowContainerBehaviour => {\n            send_message(&SocketMessage::ToggleWorkspaceWindowContainerBehaviour)?;\n        }\n        SubCommand::ToggleWorkspaceFloatOverride => {\n            send_message(&SocketMessage::ToggleWorkspaceFloatOverride)?;\n        }\n        SubCommand::ToggleWorkspaceLayer => {\n            send_message(&SocketMessage::ToggleWorkspaceLayer)?;\n        }\n        SubCommand::WindowHidingBehaviour(args) => {\n            send_message(&SocketMessage::WindowHidingBehaviour(args.hiding_behaviour))?;\n        }\n        SubCommand::CrossMonitorMoveBehaviour(args) => {\n            send_message(&SocketMessage::CrossMonitorMoveBehaviour(\n                args.move_behaviour,\n            ))?;\n        }\n        SubCommand::ToggleCrossMonitorMoveBehaviour => {\n            send_message(&SocketMessage::ToggleCrossMonitorMoveBehaviour)?;\n        }\n        SubCommand::UnmanagedWindowOperationBehaviour(args) => {\n            send_message(&SocketMessage::UnmanagedWindowOperationBehaviour(\n                args.operation_behaviour,\n            ))?;\n        }\n        SubCommand::AhkAppSpecificConfiguration(args) => {\n            let content = std::fs::read_to_string(args.path)?;\n            let lines = if let Some(override_path) = args.override_path {\n                let override_content = std::fs::read_to_string(override_path)?;\n\n                ApplicationConfigurationGenerator::generate_ahk(\n                    &content,\n                    Option::from(override_content.as_str()),\n                )?\n            } else {\n                ApplicationConfigurationGenerator::generate_ahk(&content, None)?\n            };\n\n            let generated_config = HOME_DIR.join(\"komorebi.generated.ahk\");\n            let mut file = OpenOptions::new()\n                .write(true)\n                .create(true)\n                .truncate(true)\n                .open(&generated_config)?;\n\n            file.write_all(lines.join(\"\\n\").as_bytes())?;\n\n            println!(\n                \"\\nApplication-specific generated configuration written to {}\",\n                generated_config.display()\n            );\n        }\n        SubCommand::PwshAppSpecificConfiguration(args) => {\n            let content = std::fs::read_to_string(args.path)?;\n            let lines = if let Some(override_path) = args.override_path {\n                let override_content = std::fs::read_to_string(override_path)?;\n\n                ApplicationConfigurationGenerator::generate_pwsh(\n                    &content,\n                    Option::from(override_content.as_str()),\n                )?\n            } else {\n                ApplicationConfigurationGenerator::generate_pwsh(&content, None)?\n            };\n\n            let generated_config = HOME_DIR.join(\"komorebi.generated.ps1\");\n            let mut file = OpenOptions::new()\n                .write(true)\n                .create(true)\n                .truncate(true)\n                .open(&generated_config)?;\n\n            file.write_all(lines.join(\"\\n\").as_bytes())?;\n\n            println!(\n                \"\\nApplication-specific generated configuration written to {}\",\n                generated_config.display()\n            );\n        }\n        SubCommand::ConvertAppSpecificConfiguration(args) => {\n            let content = std::fs::read_to_string(args.path)?;\n            let mut asc = ApplicationConfigurationGenerator::load(&content)?;\n            asc.sort_by(|a, b| a.name.cmp(&b.name));\n            let v2 = ApplicationSpecificConfiguration::from(asc);\n            println!(\"{}\", serde_json::to_string_pretty(&v2)?);\n        }\n        SubCommand::FormatAppSpecificConfiguration(args) => {\n            let content = std::fs::read_to_string(&args.path)?;\n            let formatted_content = ApplicationConfigurationGenerator::format(&content)?;\n\n            let mut file = OpenOptions::new()\n                .write(true)\n                .create(true)\n                .truncate(true)\n                .open(args.path)?;\n\n            file.write_all(formatted_content.as_bytes())?;\n\n            println!(\n                \"File successfully formatted for PRs to https://github.com/LGUG2Z/komorebi-application-specific-configuration\"\n            );\n        }\n        SubCommand::FetchAppSpecificConfiguration => {\n            let content = reqwest::blocking::get(\"https://raw.githubusercontent.com/LGUG2Z/komorebi-application-specific-configuration/master/applications.json\")?\n                .text()?;\n\n            let output_file = HOME_DIR.join(\"applications.json\");\n\n            let mut file = OpenOptions::new()\n                .write(true)\n                .create(true)\n                .truncate(true)\n                .open(&output_file)?;\n\n            file.write_all(content.as_bytes())?;\n\n            println!(\n                \"Latest version of applications.json from https://github.com/LGUG2Z/komorebi-application-specific-configuration downloaded\\n\"\n            );\n            println!(\n                \"You can add this to your komorebi.json static configuration file like this: \\n\\n\\\"app_specific_configuration_path\\\": \\\"{}\\\"\",\n                output_file.display().to_string().replace(\"\\\\\", \"/\")\n            );\n        }\n        SubCommand::ApplicationSpecificConfigurationSchema => {\n            #[cfg(feature = \"schemars\")]\n            {\n                let asc = schemars::schema_for!(ApplicationSpecificConfiguration);\n                let schema = serde_json::to_string_pretty(&asc)?;\n                println!(\"{schema}\");\n            }\n        }\n        SubCommand::NotificationSchema => {\n            #[cfg(feature = \"schemars\")]\n            {\n                let notification = schemars::schema_for!(komorebi_client::Notification);\n                let schema = serde_json::to_string_pretty(&notification)?;\n                println!(\"{schema}\");\n            }\n        }\n        SubCommand::SocketSchema => {\n            #[cfg(feature = \"schemars\")]\n            {\n                let socket_message = schemars::schema_for!(SocketMessage);\n                let schema = serde_json::to_string_pretty(&socket_message)?;\n                println!(\"{schema}\");\n            }\n        }\n        SubCommand::StaticConfigSchema => {\n            #[cfg(feature = \"schemars\")]\n            {\n                let static_config = schemars::schema_for!(StaticConfig);\n                let schema = serde_json::to_string_pretty(&static_config)?;\n                println!(\"{schema}\");\n            }\n        }\n        SubCommand::GenerateStaticConfig => {\n            print_query(&SocketMessage::GenerateStaticConfig);\n        }\n        // Deprecated\n        #[allow(deprecated)]\n        SubCommand::AltFocusHack(_) | SubCommand::IdentifyBorderOverflowApplication(_) => {\n            println!(\"Command deprecated - this is now automatically handled by komorebi! 🎉\");\n        }\n    }\n\n    Ok(())\n}\n\nfn show_window(hwnd: HWND, command: SHOW_WINDOW_CMD) {\n    // BOOL is returned but does not signify whether or not the operation was succesful\n    // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-showwindow\n    // TODO: error handling\n    unsafe {\n        let _ = ShowWindow(hwnd, command);\n    };\n}\n\nfn remove_transparency(hwnd: isize) {\n    let _ = komorebi_client::Window::from(hwnd).opaque();\n}\n\nfn restore_window(hwnd: isize) {\n    show_window(HWND(hwnd as *mut core::ffi::c_void), SW_RESTORE);\n    remove_transparency(hwnd);\n}\n"
  },
  {
    "path": "komorebic-no-console/Cargo.toml",
    "content": "[package]\nname = \"komorebic-no-console\"\nversion = \"0.1.41\"\ndescription = \"The command-line interface (without a console) for Komorebi, a tiling window manager for Windows\"\nrepository = \"https://github.com/LGUG2Z/komorebi\"\nedition = \"2024\"\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\n"
  },
  {
    "path": "komorebic-no-console/src/main.rs",
    "content": "#![windows_subsystem = \"windows\"]\n\nuse std::io;\nuse std::os::windows::process::CommandExt;\nuse std::process::Command;\n\nconst CREATE_NO_WINDOW: u32 = 0x08000000;\n\nfn main() -> io::Result<()> {\n    let mut current_exe = std::env::current_exe().expect(\"unable to get exec path\");\n    current_exe.pop();\n    let komorebic_exe = current_exe.join(\"komorebic.exe\");\n\n    Command::new(komorebic_exe)\n        .args(std::env::args_os().skip(1))\n        .creation_flags(CREATE_NO_WINDOW)\n        .status()\n        .map(|_| ())\n}\n"
  },
  {
    "path": "komorebic.lib.ahk",
    "content": "#Requires AutoHotkey v2.0.2\n\nStart(ffm, await_configuration, tcp_port) {\n    RunWait(\"komorebic.exe start \" ffm \" --await-configuration \" await_configuration \" --tcp-port \" tcp_port, , \"Hide\")\n}\n\nStop() {\n    RunWait(\"komorebic.exe stop\", , \"Hide\")\n}\n\nState() {\n    RunWait(\"komorebic.exe state\", , \"Hide\")\n}\n\nQuery(state_query) {\n    RunWait(\"komorebic.exe query \" state_query, , \"Hide\")\n}\n\nSubscribe(named_pipe) {\n    RunWait(\"komorebic.exe subscribe \" named_pipe, , \"Hide\")\n}\n\nUnsubscribe(named_pipe) {\n    RunWait(\"komorebic.exe unsubscribe \" named_pipe, , \"Hide\")\n}\n\nLog() {\n    RunWait(\"komorebic.exe log\", , \"Hide\")\n}\n\nQuickSaveResize() {\n    RunWait(\"komorebic.exe quick-save-resize\", , \"Hide\")\n}\n\nQuickLoadResize() {\n    RunWait(\"komorebic.exe quick-load-resize\", , \"Hide\")\n}\n\nSaveResize(path) {\n    RunWait(\"komorebic.exe save-resize \" path, , \"Hide\")\n}\n\nLoadResize(path) {\n    RunWait(\"komorebic.exe load-resize \" path, , \"Hide\")\n}\n\nFocus(operation_direction) {\n    RunWait(\"komorebic.exe focus \" operation_direction, , \"Hide\")\n}\n\nMove(operation_direction) {\n    RunWait(\"komorebic.exe move \" operation_direction, , \"Hide\")\n}\n\nMinimize() {\n    RunWait(\"komorebic.exe minimize\", , \"Hide\")\n}\n\nClose() {\n    RunWait(\"komorebic.exe close\", , \"Hide\")\n}\n\nForceFocus() {\n    RunWait(\"komorebic.exe force-focus\", , \"Hide\")\n}\n\nCycleFocus(cycle_direction) {\n    RunWait(\"komorebic.exe cycle-focus \" cycle_direction, , \"Hide\")\n}\n\nCycleMove(cycle_direction) {\n    RunWait(\"komorebic.exe cycle-move \" cycle_direction, , \"Hide\")\n}\n\nStack(operation_direction) {\n    RunWait(\"komorebic.exe stack \" operation_direction, , \"Hide\")\n}\n\nResize(edge, sizing) {\n    RunWait(\"komorebic.exe resize \" edge \" \" sizing, , \"Hide\")\n}\n\nResizeAxis(axis, sizing) {\n    RunWait(\"komorebic.exe resize-axis \" axis \" \" sizing, , \"Hide\")\n}\n\nUnstack() {\n    RunWait(\"komorebic.exe unstack\", , \"Hide\")\n}\n\nCycleStack(cycle_direction) {\n    RunWait(\"komorebic.exe cycle-stack \" cycle_direction, , \"Hide\")\n}\n\nMoveToMonitor(target) {\n    RunWait(\"komorebic.exe move-to-monitor \" target, , \"Hide\")\n}\n\nCycleMoveToMonitor(cycle_direction) {\n    RunWait(\"komorebic.exe cycle-move-to-monitor \" cycle_direction, , \"Hide\")\n}\n\nMoveToWorkspace(target) {\n    RunWait(\"komorebic.exe move-to-workspace \" target, , \"Hide\")\n}\n\nMoveToNamedWorkspace(workspace) {\n    RunWait(\"komorebic.exe move-to-named-workspace \" workspace, , \"Hide\")\n}\n\nCycleMoveToWorkspace(cycle_direction) {\n    RunWait(\"komorebic.exe cycle-move-to-workspace \" cycle_direction, , \"Hide\")\n}\n\nSendToMonitor(target) {\n    RunWait(\"komorebic.exe send-to-monitor \" target, , \"Hide\")\n}\n\nCycleSendToMonitor(cycle_direction) {\n    RunWait(\"komorebic.exe cycle-send-to-monitor \" cycle_direction, , \"Hide\")\n}\n\nSendToWorkspace(target) {\n    RunWait(\"komorebic.exe send-to-workspace \" target, , \"Hide\")\n}\n\nSendToNamedWorkspace(workspace) {\n    RunWait(\"komorebic.exe send-to-named-workspace \" workspace, , \"Hide\")\n}\n\nCycleSendToWorkspace(cycle_direction) {\n    RunWait(\"komorebic.exe cycle-send-to-workspace \" cycle_direction, , \"Hide\")\n}\n\nSendToMonitorWorkspace(target_monitor, target_workspace) {\n    RunWait(\"komorebic.exe send-to-monitor-workspace \" target_monitor \" \" target_workspace, , \"Hide\")\n}\n\nFocusMonitor(target) {\n    RunWait(\"komorebic.exe focus-monitor \" target, , \"Hide\")\n}\n\nFocusWorkspace(target) {\n    RunWait(\"komorebic.exe focus-workspace \" target, , \"Hide\")\n}\n\nFocusMonitorWorkspace(target_monitor, target_workspace) {\n    RunWait(\"komorebic.exe focus-monitor-workspace \" target_monitor \" \" target_workspace, , \"Hide\")\n}\n\nFocusNamedWorkspace(workspace) {\n    RunWait(\"komorebic.exe focus-named-workspace \" workspace, , \"Hide\")\n}\n\nCycleMonitor(cycle_direction) {\n    RunWait(\"komorebic.exe cycle-monitor \" cycle_direction, , \"Hide\")\n}\n\nCycleWorkspace(cycle_direction) {\n    RunWait(\"komorebic.exe cycle-workspace \" cycle_direction, , \"Hide\")\n}\n\nMoveWorkspaceToMonitor(target) {\n    RunWait(\"komorebic.exe move-workspace-to-monitor \" target, , \"Hide\")\n}\n\nNewWorkspace() {\n    RunWait(\"komorebic.exe new-workspace\", , \"Hide\")\n}\n\nResizeDelta(pixels) {\n    RunWait(\"komorebic.exe resize-delta \" pixels, , \"Hide\")\n}\n\nInvisibleBorders(left, top, right, bottom) {\n    RunWait(\"komorebic.exe invisible-borders \" left \" \" top \" \" right \" \" bottom, , \"Hide\")\n}\n\nGlobalWorkAreaOffset(left, top, right, bottom) {\n    RunWait(\"komorebic.exe global-work-area-offset \" left \" \" top \" \" right \" \" bottom, , \"Hide\")\n}\n\nMonitorWorkAreaOffset(monitor, left, top, right, bottom) {\n    RunWait(\"komorebic.exe monitor-work-area-offset \" monitor \" \" left \" \" top \" \" right \" \" bottom, , \"Hide\")\n}\n\nWorkspaceWorkAreaOffset(monitor, workspace, left, top, right, bottom) {\n    RunWait(\"komorebic.exe workspace-work-area-offset \" monitor \" \"workspace\" \" left \" \" top \" \" right \" \" bottom, , \"Hide\")\n}\n\nAdjustContainerPadding(sizing, adjustment) {\n    RunWait(\"komorebic.exe adjust-container-padding \" sizing \" \" adjustment, , \"Hide\")\n}\n\nAdjustWorkspacePadding(sizing, adjustment) {\n    RunWait(\"komorebic.exe adjust-workspace-padding \" sizing \" \" adjustment, , \"Hide\")\n}\n\nChangeLayout(default_layout) {\n    RunWait(\"komorebic.exe change-layout \" default_layout, , \"Hide\")\n}\n\nCycleLayout(operation_direction) {\n    RunWait(\"komorebic.exe cycle-layout \" operation_direction, , \"Hide\")\n}\n\nLoadCustomLayout(path) {\n    RunWait(\"komorebic.exe load-custom-layout \" path, , \"Hide\")\n}\n\nFlipLayout(axis) {\n    RunWait(\"komorebic.exe flip-layout \" axis, , \"Hide\")\n}\n\nPromote() {\n    RunWait(\"komorebic.exe promote\", , \"Hide\")\n}\n\nPromoteFocus() {\n    RunWait(\"komorebic.exe promote-focus\", , \"Hide\")\n}\n\nRetile() {\n    RunWait(\"komorebic.exe retile\", , \"Hide\")\n}\n\nMonitorIndexPreference(index_preference, left, top, right, bottom) {\n    RunWait(\"komorebic.exe monitor-index-preference \" index_preference \" \" left \" \" top \" \" right \" \" bottom, , \"Hide\")\n}\n\nEnsureWorkspaces(monitor, workspace_count) {\n    RunWait(\"komorebic.exe ensure-workspaces \" monitor \" \" workspace_count, , \"Hide\")\n}\n\nEnsureNamedWorkspaces(monitor, names) {\n    RunWait(\"komorebic.exe ensure-named-workspaces \" monitor \" \" names, , \"Hide\")\n}\n\nContainerPadding(monitor, workspace, size) {\n    RunWait(\"komorebic.exe container-padding \" monitor \" \" workspace \" \" size, , \"Hide\")\n}\n\nNamedWorkspaceContainerPadding(workspace, size) {\n    RunWait(\"komorebic.exe named-workspace-container-padding \" workspace \" \" size, , \"Hide\")\n}\n\nWorkspacePadding(monitor, workspace, size) {\n    RunWait(\"komorebic.exe workspace-padding \" monitor \" \" workspace \" \" size, , \"Hide\")\n}\n\nNamedWorkspacePadding(workspace, size) {\n    RunWait(\"komorebic.exe named-workspace-padding \" workspace \" \" size, , \"Hide\")\n}\n\nWorkspaceLayout(monitor, workspace, value) {\n    RunWait(\"komorebic.exe workspace-layout \" monitor \" \" workspace \" \" value, , \"Hide\")\n}\n\nNamedWorkspaceLayout(workspace, value) {\n    RunWait(\"komorebic.exe named-workspace-layout \" workspace \" \" value, , \"Hide\")\n}\n\nWorkspaceCustomLayout(monitor, workspace, path) {\n    RunWait(\"komorebic.exe workspace-custom-layout \" monitor \" \" workspace \" \" path, , \"Hide\")\n}\n\nNamedWorkspaceCustomLayout(workspace, path) {\n    RunWait(\"komorebic.exe named-workspace-custom-layout \" workspace \" \" path, , \"Hide\")\n}\n\nWorkspaceLayoutRule(monitor, workspace, at_container_count, layout) {\n    RunWait(\"komorebic.exe workspace-layout-rule \" monitor \" \" workspace \" \" at_container_count \" \" layout, , \"Hide\")\n}\n\nNamedWorkspaceLayoutRule(workspace, at_container_count, layout) {\n    RunWait(\"komorebic.exe named-workspace-layout-rule \" workspace \" \" at_container_count \" \" layout, , \"Hide\")\n}\n\nWorkspaceCustomLayoutRule(monitor, workspace, at_container_count, path) {\n    RunWait(\"komorebic.exe workspace-custom-layout-rule \" monitor \" \" workspace \" \" at_container_count \" \" path, , \"Hide\")\n}\n\nNamedWorkspaceCustomLayoutRule(workspace, at_container_count, path) {\n    RunWait(\"komorebic.exe named-workspace-custom-layout-rule \" workspace \" \" at_container_count \" \" path, , \"Hide\")\n}\n\nClearWorkspaceLayoutRules(monitor, workspace) {\n    RunWait(\"komorebic.exe clear-workspace-layout-rules \" monitor \" \" workspace, , \"Hide\")\n}\n\nClearNamedWorkspaceLayoutRules(workspace) {\n    RunWait(\"komorebic.exe clear-named-workspace-layout-rules \" workspace, , \"Hide\")\n}\n\nWorkspaceTiling(monitor, workspace, value) {\n    RunWait(\"komorebic.exe workspace-tiling \" monitor \" \" workspace \" \" value, , \"Hide\")\n}\n\nNamedWorkspaceTiling(workspace, value) {\n    RunWait(\"komorebic.exe named-workspace-tiling \" workspace \" \" value, , \"Hide\")\n}\n\nWorkspaceName(monitor, workspace, value) {\n    RunWait(\"komorebic.exe workspace-name \" monitor \" \" workspace \" \" value, , \"Hide\")\n}\n\nToggleWindowContainerBehaviour() {\n    RunWait(\"komorebic.exe toggle-window-container-behaviour\", , \"Hide\")\n}\n\nTogglePause() {\n    RunWait(\"komorebic.exe toggle-pause\", , \"Hide\")\n}\n\nToggleTiling() {\n    RunWait(\"komorebic.exe toggle-tiling\", , \"Hide\")\n}\n\nToggleFloat() {\n    RunWait(\"komorebic.exe toggle-float\", , \"Hide\")\n}\n\nToggleMonocle() {\n    RunWait(\"komorebic.exe toggle-monocle\", , \"Hide\")\n}\n\nToggleMaximize() {\n    RunWait(\"komorebic.exe toggle-maximize\", , \"Hide\")\n}\n\nRestoreWindows() {\n    RunWait(\"komorebic.exe restore-windows\", , \"Hide\")\n}\n\nManage() {\n    RunWait(\"komorebic.exe manage\", , \"Hide\")\n}\n\nUnmanage() {\n    RunWait(\"komorebic.exe unmanage\", , \"Hide\")\n}\n\nReloadConfiguration() {\n    RunWait(\"komorebic.exe reload-configuration\", , \"Hide\")\n}\n\nWatchConfiguration(boolean_state) {\n    RunWait(\"komorebic.exe watch-configuration \" boolean_state, , \"Hide\")\n}\n\nCompleteConfiguration() {\n    RunWait(\"komorebic.exe complete-configuration\", , \"Hide\")\n}\n\nAltFocusHack(boolean_state) {\n    RunWait(\"komorebic.exe alt-focus-hack \" boolean_state, , \"Hide\")\n}\n\nWindowHidingBehaviour(hiding_behaviour) {\n    RunWait(\"komorebic.exe window-hiding-behaviour \" hiding_behaviour, , \"Hide\")\n}\n\nCrossMonitorMoveBehaviour(move_behaviour) {\n    RunWait(\"komorebic.exe cross-monitor-move-behaviour \" move_behaviour, , \"Hide\")\n}\n\nToggleCrossMonitorMoveBehaviour() {\n    RunWait(\"komorebic.exe toggle-cross-monitor-move-behaviour\", , \"Hide\")\n}\n\nUnmanagedWindowOperationBehaviour(operation_behaviour) {\n    RunWait(\"komorebic.exe unmanaged-window-operation-behaviour \" operation_behaviour, , \"Hide\")\n}\n\nFloatRule(identifier, id) {\n    RunWait(\"komorebic.exe float-rule \" identifier \" `\"\" id \"`\"\", , \"Hide\")\n}\n\nManageRule(identifier, id) {\n    RunWait(\"komorebic.exe manage-rule \" identifier \" `\"\" id \"`\"\", , \"Hide\")\n}\n\nWorkspaceRule(identifier, id, monitor, workspace) {\n    RunWait(\"komorebic.exe workspace-rule \" identifier \" `\"\" id \"`\" \" monitor \" \" workspace, , \"Hide\")\n}\n\nNamedWorkspaceRule(identifier, id, workspace) {\n    RunWait(\"komorebic.exe named-workspace-rule \" identifier \" `\"\" id \"`\" \" workspace, , \"Hide\")\n}\n\nIdentifyObjectNameChangeApplication(identifier, id) {\n    RunWait(\"komorebic.exe identify-object-name-change-application \" identifier \" `\"\" id \"`\"\", , \"Hide\")\n}\n\nIdentifyTrayApplication(identifier, id) {\n    RunWait(\"komorebic.exe identify-tray-application \" identifier \" `\"\" id \"`\"\", , \"Hide\")\n}\n\nIdentifyLayeredApplication(identifier, id) {\n    RunWait(\"komorebic.exe identify-layered-application \" identifier \" `\"\" id \"`\"\", , \"Hide\")\n}\n\nIdentifyBorderOverflowApplication(identifier, id) {\n    RunWait(\"komorebic.exe identify-border-overflow-application \" identifier \" `\"\" id \"`\"\", , \"Hide\")\n}\n\nActiveWindowBorder(boolean_state) {\n    RunWait(\"komorebic.exe active-window-border \" boolean_state, , \"Hide\")\n}\n\nActiveWindowBorderColour(r, g, b, window_kind) {\n    RunWait(\"komorebic.exe active-window-border-colour \" r \" \" g \" \" b \" --window-kind \" window_kind, , \"Hide\")\n}\n\nActiveWindowBorderWidth(width) {\n    RunWait(\"komorebic.exe active-window-border-width \" width, , \"Hide\")\n}\n\nActiveWindowBorderOffset(offset) {\n    RunWait(\"komorebic.exe active-window-border-offset \" offset, , \"Hide\")\n}\n\nFocusFollowsMouse(boolean_state, implementation) {\n    RunWait(\"komorebic.exe focus-follows-mouse \" boolean_state \" --implementation \" implementation, , \"Hide\")\n}\n\nToggleFocusFollowsMouse(implementation) {\n    RunWait(\"komorebic.exe toggle-focus-follows-mouse  --implementation \" implementation, , \"Hide\")\n}\n\nMouseFollowsFocus(boolean_state) {\n    RunWait(\"komorebic.exe mouse-follows-focus \" boolean_state, , \"Hide\")\n}\n\nToggleMouseFollowsFocus() {\n    RunWait(\"komorebic.exe toggle-mouse-follows-focus\", , \"Hide\")\n}\n\nAhkLibrary() {\n    RunWait(\"komorebic.exe ahk-library\", , \"Hide\")\n}\n\nAhkAppSpecificConfiguration(path, override_path) {\n    RunWait(\"komorebic.exe ahk-app-specific-configuration \" path \" \" override_path, , \"Hide\")\n}\n\nPwshAppSpecificConfiguration(path, override_path) {\n    RunWait(\"komorebic.exe pwsh-app-specific-configuration \" path \" \" override_path, , \"Hide\")\n}\n\nFormatAppSpecificConfiguration(path) {\n    RunWait(\"komorebic.exe format-app-specific-configuration \" path, , \"Hide\")\n}\n\nNotificationSchema() {\n    RunWait(\"komorebic.exe notification-schema\", , \"Hide\")\n}\n\nSocketSchema() {\n    RunWait(\"komorebic.exe socket-schema\", , \"Hide\")\n}\n"
  },
  {
    "path": "mkdocs.yml",
    "content": "copyright: Copyright &copy; 2020-Present LGUG2Z\nuse_directory_urls: false\nsite_name: Komorebi\nsite_description: The Tiling Window Manager for Windows\nrepo_url: https://github.com/LGUG2Z/komorebi\nrepo_name: LGUG2Z/komorebi\ndocs_dir: docs\ntheme:\n  name: material\n  palette:\n    - media: \"(prefers-color-scheme: light)\"\n      scheme: default\n      primary: deep purple\n      accent: deep purple\n      toggle:\n        icon: material/weather-sunny\n        name: Switch to dark mode\n\n    # Palette toggle for dark mode\n    - media: \"(prefers-color-scheme: dark)\"\n      scheme: slate\n      primary: deep purple\n      accent: purple\n      toggle:\n        icon: material/weather-night\n        name: Switch to light mode\n  features:\n    - content.action.edit\n    - content.action.view\n    - content.code.copy\n    - content.tabs.link\n    - navigation.footer\n    - navigation.indexes\n    - navigation.sections\n    - navigation.tabs\n    - navigation.top\n    - navigation.tracking\n    - search.highlight\n    - search.share\n    - search.suggest\n    - toc.follow\nmarkdown_extensions:\n  - admonition\n  - pymdownx.highlight\n  - pymdownx.superfences\nplugins:\n  - macros\n  - search\n\nnav:\n  - Komorebi:\n      - About:\n          - index.md\n          - design.md\n      - Getting started:\n          - Installation: installation.md\n          - Example configurations: example-configurations.md\n          - Troubleshooting: troubleshooting.md\n      - Usage:\n          - usage/focusing-windows.md\n          - usage/moving-windows.md\n          - usage/stacking-windows.md\n          - usage/focusing-workspaces.md\n          - usage/moving-windows-across-workspaces.md\n      - Komorebi Configuration:\n          - Schema: https://komorebi.lgug2z.com/schema\n      - Komorebi Bar Configuration:\n          - Schema: https://komorebi-bar.lgug2z.com/schema\n      - Common workflows:\n          - common-workflows/komorebi-config-home.md\n          - common-workflows/autostart.md\n          - common-workflows/animations.md\n          - common-workflows/autohotkey.md\n          - common-workflows/borders.md\n          - common-workflows/stackbar.md\n          - common-workflows/remove-gaps.md\n          - common-workflows/ignore-windows.md\n          - common-workflows/force-manage-windows.md\n          - common-workflows/floating-applications.md\n          - common-workflows/tray-and-multi-window-applications.md\n          - common-workflows/mouse-follows-focus.md\n          - common-workflows/dynamic-layout-switching.md\n          - common-workflows/multiple-bar-instances.md\n          - common-workflows/multi-monitor-setup.md\n  - CLI reference:\n      - cli/quickstart.md\n      - cli/license.md\n      - cli/start.md\n      - cli/stop.md\n      - cli/kill.md\n      - cli/check.md\n      - cli/configuration.md\n      - cli/bar-configuration.md\n      - cli/whkdrc.md\n      - cli/data-directory.md\n      - cli/state.md\n      - cli/global-state.md\n      - cli/gui.md\n      - cli/toggle-shortcuts.md\n      - cli/visible-windows.md\n      - cli/monitor-information.md\n      - cli/query.md\n      - cli/subscribe-socket.md\n      - cli/unsubscribe-socket.md\n      - cli/subscribe-pipe.md\n      - cli/unsubscribe-pipe.md\n      - cli/log.md\n      - cli/quick-save-resize.md\n      - cli/quick-load-resize.md\n      - cli/save-resize.md\n      - cli/load-resize.md\n      - cli/focus.md\n      - cli/move.md\n      - cli/preselect-direction.md\n      - cli/cancel-preselect.md\n      - cli/minimize.md\n      - cli/close.md\n      - cli/force-focus.md\n      - cli/cycle-focus.md\n      - cli/cycle-move.md\n      - cli/eager-focus.md\n      - cli/stack.md\n      - cli/unstack.md\n      - cli/cycle-stack.md\n      - cli/cycle-stack-index.md\n      - cli/focus-stack-window.md\n      - cli/stack-all.md\n      - cli/unstack-all.md\n      - cli/resize-edge.md\n      - cli/resize-axis.md\n      - cli/move-to-monitor.md\n      - cli/cycle-move-to-monitor.md\n      - cli/move-to-workspace.md\n      - cli/move-to-named-workspace.md\n      - cli/cycle-move-to-workspace.md\n      - cli/send-to-monitor.md\n      - cli/cycle-send-to-monitor.md\n      - cli/send-to-workspace.md\n      - cli/send-to-named-workspace.md\n      - cli/cycle-send-to-workspace.md\n      - cli/send-to-monitor-workspace.md\n      - cli/move-to-monitor-workspace.md\n      - cli/send-to-last-workspace.md\n      - cli/move-to-last-workspace.md\n      - cli/focus-monitor.md\n      - cli/focus-monitor-at-cursor.md\n      - cli/focus-last-workspace.md\n      - cli/focus-workspace.md\n      - cli/focus-workspaces.md\n      - cli/focus-monitor-workspace.md\n      - cli/focus-named-workspace.md\n      - cli/close-workspace.md\n      - cli/cycle-monitor.md\n      - cli/cycle-workspace.md\n      - cli/cycle-empty-workspace.md\n      - cli/move-workspace-to-monitor.md\n      - cli/cycle-move-workspace-to-monitor.md\n      - cli/swap-workspaces-with-monitor.md\n      - cli/new-workspace.md\n      - cli/resize-delta.md\n      - cli/invisible-borders.md\n      - cli/global-work-area-offset.md\n      - cli/monitor-work-area-offset.md\n      - cli/workspace-work-area-offset.md\n      - cli/toggle-window-based-work-area-offset.md\n      - cli/focused-workspace-container-padding.md\n      - cli/focused-workspace-padding.md\n      - cli/adjust-container-padding.md\n      - cli/adjust-workspace-padding.md\n      - cli/change-layout.md\n      - cli/cycle-layout.md\n      - cli/scrolling-layout-columns.md\n      - cli/flip-layout.md\n      - cli/promote.md\n      - cli/promote-swap.md\n      - cli/promote-focus.md\n      - cli/promote-window.md\n      - cli/retile.md\n      - cli/monitor-index-preference.md\n      - cli/display-index-preference.md\n      - cli/ensure-workspaces.md\n      - cli/ensure-named-workspaces.md\n      - cli/container-padding.md\n      - cli/named-workspace-container-padding.md\n      - cli/workspace-padding.md\n      - cli/named-workspace-padding.md\n      - cli/workspace-layout.md\n      - cli/named-workspace-layout.md\n      - cli/workspace-layout-rule.md\n      - cli/named-workspace-layout-rule.md\n      - cli/clear-workspace-layout-rules.md\n      - cli/clear-named-workspace-layout-rules.md\n      - cli/workspace-tiling.md\n      - cli/named-workspace-tiling.md\n      - cli/workspace-name.md\n      - cli/toggle-window-container-behaviour.md\n      - cli/toggle-float-override.md\n      - cli/toggle-workspace-window-container-behaviour.md\n      - cli/toggle-workspace-float-override.md\n      - cli/toggle-workspace-layer.md\n      - cli/toggle-pause.md\n      - cli/toggle-tiling.md\n      - cli/toggle-float.md\n      - cli/toggle-monocle.md\n      - cli/toggle-maximize.md\n      - cli/toggle-lock.md\n      - cli/restore-windows.md\n      - cli/manage.md\n      - cli/unmanage.md\n      - cli/replace-configuration.md\n      - cli/reload-configuration.md\n      - cli/watch-configuration.md\n      - cli/complete-configuration.md\n      - cli/window-hiding-behaviour.md\n      - cli/cross-monitor-move-behaviour.md\n      - cli/toggle-cross-monitor-move-behaviour.md\n      - cli/unmanaged-window-operation-behaviour.md\n      - cli/session-float-rule.md\n      - cli/session-float-rules.md\n      - cli/clear-session-float-rules.md\n      - cli/ignore-rule.md\n      - cli/manage-rule.md\n      - cli/initial-workspace-rule.md\n      - cli/initial-named-workspace-rule.md\n      - cli/workspace-rule.md\n      - cli/named-workspace-rule.md\n      - cli/clear-workspace-rules.md\n      - cli/clear-named-workspace-rules.md\n      - cli/clear-all-workspace-rules.md\n      - cli/enforce-workspace-rules.md\n      - cli/identify-object-name-change-application.md\n      - cli/identify-tray-application.md\n      - cli/identify-layered-application.md\n      - cli/remove-title-bar.md\n      - cli/toggle-title-bars.md\n      - cli/border.md\n      - cli/border-colour.md\n      - cli/border-width.md\n      - cli/border-offset.md\n      - cli/border-style.md\n      - cli/border-implementation.md\n      - cli/stackbar-mode.md\n      - cli/transparency.md\n      - cli/transparency-alpha.md\n      - cli/toggle-transparency.md\n      - cli/animation.md\n      - cli/animation-duration.md\n      - cli/animation-fps.md\n      - cli/animation-style.md\n      - cli/mouse-follows-focus.md\n      - cli/toggle-mouse-follows-focus.md\n      - cli/ahk-app-specific-configuration.md\n      - cli/pwsh-app-specific-configuration.md\n      - cli/convert-app-specific-configuration.md\n      - cli/fetch-app-specific-configuration.md\n      - cli/application-specific-configuration-schema.md\n      - cli/notification-schema.md\n      - cli/socket-schema.md\n      - cli/static-config-schema.md\n      - cli/generate-static-config.md\n      - cli/enable-autostart.md\n      - cli/disable-autostart.md"
  },
  {
    "path": "rust-toolchain.toml",
    "content": "[toolchain]\nchannel = \"stable\"\n"
  },
  {
    "path": "rustfmt.toml",
    "content": "imports_granularity = \"Item\"\n"
  },
  {
    "path": "schema.asc.json",
    "content": "{\n  \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n  \"title\": \"ApplicationSpecificConfiguration\",\n  \"type\": \"object\",\n  \"additionalProperties\": {\n    \"$ref\": \"#/$defs/AscApplicationRulesOrSchema\"\n  },\n  \"$defs\": {\n    \"ApplicationIdentifier\": {\n      \"description\": \"Application identifier\",\n      \"oneOf\": [\n        {\n          \"description\": \"Executable name\",\n          \"type\": \"string\",\n          \"const\": \"Exe\"\n        },\n        {\n          \"description\": \"Class\",\n          \"type\": \"string\",\n          \"const\": \"Class\"\n        },\n        {\n          \"description\": \"Window title\",\n          \"type\": \"string\",\n          \"const\": \"Title\"\n        },\n        {\n          \"description\": \"Executable path\",\n          \"type\": \"string\",\n          \"const\": \"Path\"\n        }\n      ]\n    },\n    \"AscApplicationRules\": {\n      \"description\": \"Rules that determine how an application is handled\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"floating\": {\n          \"description\": \"Rules to manage specific windows as floating windows\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"$ref\": \"#/$defs/MatchingRule\"\n          }\n        },\n        \"ignore\": {\n          \"description\": \"Rules to ignore specific windows\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"$ref\": \"#/$defs/MatchingRule\"\n          }\n        },\n        \"layered\": {\n          \"description\": \"Rules to identify applications which have the `WS_EX_LAYERED` Extended Window Style\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"$ref\": \"#/$defs/MatchingRule\"\n          }\n        },\n        \"manage\": {\n          \"description\": \"Rules to forcibly manage specific windows\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"$ref\": \"#/$defs/MatchingRule\"\n          }\n        },\n        \"object_name_change\": {\n          \"description\": \"Rules to identify applications which send the `EVENT_OBJECT_NAMECHANGE` event on launch\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"$ref\": \"#/$defs/MatchingRule\"\n          }\n        },\n        \"slow_application\": {\n          \"description\": \"Rules to identify applications which are slow to send initial event notifications\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"$ref\": \"#/$defs/MatchingRule\"\n          }\n        },\n        \"transparency_ignore\": {\n          \"description\": \"Rules to ignore specific windows from the transparency feature\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"$ref\": \"#/$defs/MatchingRule\"\n          }\n        },\n        \"tray_and_multi_window\": {\n          \"description\": \"Rules to identify applications which minimize to the tray or have multiple windows\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"$ref\": \"#/$defs/MatchingRule\"\n          }\n        }\n      }\n    },\n    \"AscApplicationRulesOrSchema\": {\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/$defs/AscApplicationRules\"\n        },\n        {\n          \"type\": \"string\"\n        }\n      ]\n    },\n    \"IdWithIdentifier\": {\n      \"description\": \"Rule for matching applications\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"id\": {\n          \"description\": \"Target identifier\",\n          \"type\": \"string\"\n        },\n        \"kind\": {\n          \"description\": \"Kind of identifier to target\",\n          \"$ref\": \"#/$defs/ApplicationIdentifier\"\n        },\n        \"matching_strategy\": {\n          \"description\": \"Matching strategy to use\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/MatchingStrategy\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        }\n      },\n      \"required\": [\n        \"kind\",\n        \"id\"\n      ]\n    },\n    \"MatchingRule\": {\n      \"description\": \"Rule for matching applications\",\n      \"anyOf\": [\n        {\n          \"description\": \"Simple matching rule which must evaluate to true\",\n          \"$ref\": \"#/$defs/IdWithIdentifier\"\n        },\n        {\n          \"description\": \"Composite matching rule where all conditions must evaluate to true\",\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/$defs/IdWithIdentifier\"\n          }\n        }\n      ]\n    },\n    \"MatchingStrategy\": {\n      \"description\": \"Strategy for matching identifiers\",\n      \"oneOf\": [\n        {\n          \"description\": \"Should not be used, only kept for backward compatibility\",\n          \"type\": \"string\",\n          \"const\": \"Legacy\"\n        },\n        {\n          \"description\": \"Equals\",\n          \"type\": \"string\",\n          \"const\": \"Equals\"\n        },\n        {\n          \"description\": \"Starts With\",\n          \"type\": \"string\",\n          \"const\": \"StartsWith\"\n        },\n        {\n          \"description\": \"Ends With\",\n          \"type\": \"string\",\n          \"const\": \"EndsWith\"\n        },\n        {\n          \"description\": \"Contains\",\n          \"type\": \"string\",\n          \"const\": \"Contains\"\n        },\n        {\n          \"description\": \"Regex\",\n          \"type\": \"string\",\n          \"const\": \"Regex\"\n        },\n        {\n          \"description\": \"Does not end with\",\n          \"type\": \"string\",\n          \"const\": \"DoesNotEndWith\"\n        },\n        {\n          \"description\": \"Does not start with\",\n          \"type\": \"string\",\n          \"const\": \"DoesNotStartWith\"\n        },\n        {\n          \"description\": \"Does not equal\",\n          \"type\": \"string\",\n          \"const\": \"DoesNotEqual\"\n        },\n        {\n          \"description\": \"Does not contain\",\n          \"type\": \"string\",\n          \"const\": \"DoesNotContain\"\n        }\n      ]\n    }\n  }\n}\n"
  },
  {
    "path": "schema.bar.json",
    "content": "{\n  \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n  \"title\": \"KomobarConfig\",\n  \"description\": \"The `komorebi.bar.json` configuration file reference for `v0.1.40`\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"center_widgets\": {\n      \"description\": \"Center widgets (ordered left-to-right)\",\n      \"type\": [\n        \"array\",\n        \"null\"\n      ],\n      \"items\": {\n        \"$ref\": \"#/$defs/WidgetConfig\"\n      }\n    },\n    \"font_family\": {\n      \"description\": \"Font family\",\n      \"type\": [\n        \"string\",\n        \"null\"\n      ]\n    },\n    \"font_size\": {\n      \"description\": \"Font size\",\n      \"type\": [\n        \"number\",\n        \"null\"\n      ],\n      \"format\": \"float\",\n      \"default\": 12.5\n    },\n    \"frame\": {\n      \"description\": \"Frame options (see: https://docs.rs/egui/latest/egui/containers/frame/struct.Frame.html)\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/$defs/FrameConfig\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ]\n    },\n    \"grouping\": {\n      \"description\": \"Visual grouping for widgets\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/$defs/Grouping\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ]\n    },\n    \"height\": {\n      \"description\": \"Bar height\",\n      \"type\": [\n        \"number\",\n        \"null\"\n      ],\n      \"format\": \"float\",\n      \"default\": 50\n    },\n    \"icon_scale\": {\n      \"description\": \"Scale of the icons relative to the font_size [[1.0-2.0]]\",\n      \"type\": [\n        \"number\",\n        \"null\"\n      ],\n      \"format\": \"float\",\n      \"default\": 1.4\n    },\n    \"left_widgets\": {\n      \"description\": \"Left side widgets (ordered left-to-right)\",\n      \"type\": \"array\",\n      \"items\": {\n        \"$ref\": \"#/$defs/WidgetConfig\"\n      }\n    },\n    \"margin\": {\n      \"description\": \"Bar margin. Use one value for all sides or use a grouped margin for horizontal and/or\\nvertical definition which can each take a single value for a symmetric margin or two\\nvalues for each side, i.e.:\\n```json\\n\\\"margin\\\": {\\n    \\\"horizontal\\\": 10\\n}\\n```\\nor:\\n```json\\n\\\"margin\\\": {\\n    \\\"vertical\\\": [top, bottom]\\n}\\n```\\nYou can also set individual margin on each side like this:\\n```json\\n\\\"margin\\\": {\\n    \\\"top\\\": 10,\\n    \\\"bottom\\\": 10,\\n    \\\"left\\\": 10,\\n    \\\"right\\\": 10,\\n}\\n```\\nBy default, margin is set to 0 on all sides.\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/$defs/SpacingKind\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ]\n    },\n    \"max_label_width\": {\n      \"description\": \"Max label width before text truncation\",\n      \"type\": [\n        \"number\",\n        \"null\"\n      ],\n      \"format\": \"float\",\n      \"default\": 400.0\n    },\n    \"monitor\": {\n      \"description\": \"The monitor index or the full monitor options\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/$defs/MonitorConfigOrIndex\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ],\n      \"default\": 0\n    },\n    \"mouse\": {\n      \"description\": \"Options for mouse interaction on the bar\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/$defs/MouseConfig\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ]\n    },\n    \"padding\": {\n      \"description\": \"Bar padding. Use one value for all sides or use a grouped padding for horizontal and/or\\nvertical definition which can each take a single value for a symmetric padding or two\\nvalues for each side, i.e.:\\n```json\\n\\\"padding\\\": {\\n    \\\"horizontal\\\": 10\\n}\\n```\\nor:\\n```json\\n\\\"padding\\\": {\\n    \\\"horizontal\\\": [left, right]\\n}\\n```\\nYou can also set individual padding on each side like this:\\n```json\\n\\\"padding\\\": {\\n    \\\"top\\\": 10,\\n    \\\"bottom\\\": 10,\\n    \\\"left\\\": 10,\\n    \\\"right\\\": 10,\\n}\\n```\\nBy default, padding is set to 10 on all sides.\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/$defs/SpacingKind\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ]\n    },\n    \"position\": {\n      \"description\": \"Bar positioning options\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/$defs/PositionConfig\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ]\n    },\n    \"right_widgets\": {\n      \"description\": \"Right side widgets (ordered left-to-right)\",\n      \"type\": \"array\",\n      \"items\": {\n        \"$ref\": \"#/$defs/WidgetConfig\"\n      }\n    },\n    \"theme\": {\n      \"description\": \"Theme\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/$defs/KomobarTheme\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ]\n    },\n    \"transparency_alpha\": {\n      \"description\": \"Alpha value for the color transparency [[0-255]]\",\n      \"type\": [\n        \"integer\",\n        \"null\"\n      ],\n      \"format\": \"uint8\",\n      \"default\": 200,\n      \"maximum\": 255,\n      \"minimum\": 0\n    },\n    \"widget_spacing\": {\n      \"description\": \"Spacing between widgets\",\n      \"type\": [\n        \"number\",\n        \"null\"\n      ],\n      \"format\": \"float\",\n      \"default\": 10.0\n    }\n  },\n  \"required\": [\n    \"left_widgets\",\n    \"right_widgets\"\n  ],\n  \"$defs\": {\n    \"AnimationPrefix\": {\n      \"type\": \"string\",\n      \"enum\": [\n        \"movement\",\n        \"transparency\"\n      ]\n    },\n    \"AnimationStyle\": {\n      \"description\": \"Mathematical function which describes the rate at which a value changes\",\n      \"oneOf\": [\n        {\n          \"description\": \"Linear\",\n          \"type\": \"string\",\n          \"const\": \"Linear\"\n        },\n        {\n          \"description\": \"Ease in sine\",\n          \"type\": \"string\",\n          \"const\": \"EaseInSine\"\n        },\n        {\n          \"description\": \"Ease out sine\",\n          \"type\": \"string\",\n          \"const\": \"EaseOutSine\"\n        },\n        {\n          \"description\": \"Ease in out sine\",\n          \"type\": \"string\",\n          \"const\": \"EaseInOutSine\"\n        },\n        {\n          \"description\": \"Ease in quad\",\n          \"type\": \"string\",\n          \"const\": \"EaseInQuad\"\n        },\n        {\n          \"description\": \"Ease out quad\",\n          \"type\": \"string\",\n          \"const\": \"EaseOutQuad\"\n        },\n        {\n          \"description\": \"Ease in out quad\",\n          \"type\": \"string\",\n          \"const\": \"EaseInOutQuad\"\n        },\n        {\n          \"description\": \"Ease in cubic\",\n          \"type\": \"string\",\n          \"const\": \"EaseInCubic\"\n        },\n        {\n          \"description\": \"Ease out cubic\",\n          \"type\": \"string\",\n          \"const\": \"EaseOutCubic\"\n        },\n        {\n          \"description\": \"Ease in out cubic\",\n          \"type\": \"string\",\n          \"const\": \"EaseInOutCubic\"\n        },\n        {\n          \"description\": \"Ease in quart\",\n          \"type\": \"string\",\n          \"const\": \"EaseInQuart\"\n        },\n        {\n          \"description\": \"Ease out quart\",\n          \"type\": \"string\",\n          \"const\": \"EaseOutQuart\"\n        },\n        {\n          \"description\": \"Ease in out quart\",\n          \"type\": \"string\",\n          \"const\": \"EaseInOutQuart\"\n        },\n        {\n          \"description\": \"Ease in quint\",\n          \"type\": \"string\",\n          \"const\": \"EaseInQuint\"\n        },\n        {\n          \"description\": \"Ease out quint\",\n          \"type\": \"string\",\n          \"const\": \"EaseOutQuint\"\n        },\n        {\n          \"description\": \"Ease in out quint\",\n          \"type\": \"string\",\n          \"const\": \"EaseInOutQuint\"\n        },\n        {\n          \"description\": \"Ease in expo\",\n          \"type\": \"string\",\n          \"const\": \"EaseInExpo\"\n        },\n        {\n          \"description\": \"Ease out expo\",\n          \"type\": \"string\",\n          \"const\": \"EaseOutExpo\"\n        },\n        {\n          \"description\": \"Ease in out expo\",\n          \"type\": \"string\",\n          \"const\": \"EaseInOutExpo\"\n        },\n        {\n          \"description\": \"Ease in circ\",\n          \"type\": \"string\",\n          \"const\": \"EaseInCirc\"\n        },\n        {\n          \"description\": \"Ease out circ\",\n          \"type\": \"string\",\n          \"const\": \"EaseOutCirc\"\n        },\n        {\n          \"description\": \"Ease in out circ\",\n          \"type\": \"string\",\n          \"const\": \"EaseInOutCirc\"\n        },\n        {\n          \"description\": \"Ease in back\",\n          \"type\": \"string\",\n          \"const\": \"EaseInBack\"\n        },\n        {\n          \"description\": \"Ease out back\",\n          \"type\": \"string\",\n          \"const\": \"EaseOutBack\"\n        },\n        {\n          \"description\": \"Ease in out back\",\n          \"type\": \"string\",\n          \"const\": \"EaseInOutBack\"\n        },\n        {\n          \"description\": \"Ease in elastic\",\n          \"type\": \"string\",\n          \"const\": \"EaseInElastic\"\n        },\n        {\n          \"description\": \"Ease out elastic\",\n          \"type\": \"string\",\n          \"const\": \"EaseOutElastic\"\n        },\n        {\n          \"description\": \"Ease in out elastic\",\n          \"type\": \"string\",\n          \"const\": \"EaseInOutElastic\"\n        },\n        {\n          \"description\": \"Ease in bounce\",\n          \"type\": \"string\",\n          \"const\": \"EaseInBounce\"\n        },\n        {\n          \"description\": \"Ease out bounce\",\n          \"type\": \"string\",\n          \"const\": \"EaseOutBounce\"\n        },\n        {\n          \"description\": \"Ease in out bounce\",\n          \"type\": \"string\",\n          \"const\": \"EaseInOutBounce\"\n        },\n        {\n          \"title\": \"CubicBezier\",\n          \"description\": \"Custom Cubic Bézier function\",\n          \"type\": \"object\",\n          \"properties\": {\n            \"CubicBezier\": {\n              \"type\": \"array\",\n              \"maxItems\": 4,\n              \"minItems\": 4,\n              \"prefixItems\": [\n                {\n                  \"type\": \"number\",\n                  \"format\": \"double\"\n                },\n                {\n                  \"type\": \"number\",\n                  \"format\": \"double\"\n                },\n                {\n                  \"type\": \"number\",\n                  \"format\": \"double\"\n                },\n                {\n                  \"type\": \"number\",\n                  \"format\": \"double\"\n                }\n              ]\n            }\n          },\n          \"additionalProperties\": false,\n          \"required\": [\n            \"CubicBezier\"\n          ]\n        }\n      ]\n    },\n    \"AppConfig\": {\n      \"description\": \"Application button configuration\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"command\": {\n          \"description\": \"Command to execute (e.g. path to the application or shell command).\",\n          \"type\": \"string\"\n        },\n        \"display\": {\n          \"description\": \"Display format for this application button (optional). Overrides global format if set.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/ApplicationsDisplayFormat\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"enable\": {\n          \"description\": \"Whether to enable this application button (optional).\\nInherits from the global `Applications` setting if omitted.\",\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ]\n        },\n        \"icon\": {\n          \"description\": \"Optional icon: a path to an image or a text-based glyph (e.g., from Nerd Fonts).\\nIf not set, and if the `command` is a path to an executable, an icon might be extracted from it.\\nNote: glyphs require a compatible `font_family`.\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"name\": {\n          \"description\": \"Display name of the application.\",\n          \"type\": \"string\"\n        },\n        \"show_command_on_hover\": {\n          \"description\": \"Whether to show the launch command on hover (optional).\\nInherits from the global `Applications` setting if omitted.\",\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ]\n        }\n      },\n      \"required\": [\n        \"name\",\n        \"command\"\n      ]\n    },\n    \"ApplicationIdentifier\": {\n      \"description\": \"Application identifier\",\n      \"oneOf\": [\n        {\n          \"description\": \"Executable name\",\n          \"type\": \"string\",\n          \"const\": \"Exe\"\n        },\n        {\n          \"description\": \"Class\",\n          \"type\": \"string\",\n          \"const\": \"Class\"\n        },\n        {\n          \"description\": \"Window title\",\n          \"type\": \"string\",\n          \"const\": \"Title\"\n        },\n        {\n          \"description\": \"Executable path\",\n          \"type\": \"string\",\n          \"const\": \"Path\"\n        }\n      ]\n    },\n    \"ApplicationsConfig\": {\n      \"description\": \"Applications widget configuration\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"display\": {\n          \"description\": \"Default display format for all applications (optional).\\nCould be overridden per application. Defaults to `Icon`.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/ApplicationsDisplayFormat\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"enable\": {\n          \"description\": \"Enables or disables the applications widget.\",\n          \"type\": \"boolean\"\n        },\n        \"items\": {\n          \"description\": \"List of configured applications to display.\",\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/$defs/AppConfig\"\n          }\n        },\n        \"show_command_on_hover\": {\n          \"description\": \"Whether to show the launch command on hover (optional).\\nCould be overridden per application. Defaults to `false` if not set.\",\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ]\n        },\n        \"spacing\": {\n          \"description\": \"Horizontal spacing between application buttons.\",\n          \"type\": [\n            \"number\",\n            \"null\"\n          ],\n          \"format\": \"float\"\n        }\n      },\n      \"required\": [\n        \"enable\",\n        \"items\"\n      ]\n    },\n    \"ApplicationsDisplayFormat\": {\n      \"description\": \"Applications widget display format\",\n      \"oneOf\": [\n        {\n          \"description\": \"Show only the application icon.\",\n          \"type\": \"string\",\n          \"const\": \"Icon\"\n        },\n        {\n          \"description\": \"Show only the application name as text.\",\n          \"type\": \"string\",\n          \"const\": \"Text\"\n        },\n        {\n          \"description\": \"Show both the application icon and name.\",\n          \"type\": \"string\",\n          \"const\": \"IconAndText\"\n        }\n      ]\n    },\n    \"Axis\": {\n      \"description\": \"Axis on which to perform an operation\",\n      \"oneOf\": [\n        {\n          \"description\": \"Horizontal axis\",\n          \"type\": \"string\",\n          \"const\": \"Horizontal\"\n        },\n        {\n          \"description\": \"Vertical axis\",\n          \"type\": \"string\",\n          \"const\": \"Vertical\"\n        },\n        {\n          \"description\": \"Both horizontal and vertical axes\",\n          \"type\": \"string\",\n          \"const\": \"HorizontalAndVertical\"\n        }\n      ]\n    },\n    \"Base16\": {\n      \"description\": \"Base 16 colour palette\",\n      \"oneOf\": [\n        {\n          \"description\": \"3024 (https://tinted-theming.github.io/tinted-gallery/#base16-3024)\",\n          \"type\": \"string\",\n          \"const\": \"3024\"\n        },\n        {\n          \"description\": \"Apathy (https://tinted-theming.github.io/tinted-gallery/#base16-apathy)\",\n          \"type\": \"string\",\n          \"const\": \"Apathy\"\n        },\n        {\n          \"description\": \"Apprentice (https://tinted-theming.github.io/tinted-gallery/#base16-apprentice)\",\n          \"type\": \"string\",\n          \"const\": \"Apprentice\"\n        },\n        {\n          \"description\": \"Ashes (https://tinted-theming.github.io/tinted-gallery/#base16-ashes)\",\n          \"type\": \"string\",\n          \"const\": \"Ashes\"\n        },\n        {\n          \"description\": \"Atelier Cave Light (https://tinted-theming.github.io/tinted-gallery/#base16-atelier-cave-light)\",\n          \"type\": \"string\",\n          \"const\": \"AtelierCaveLight\"\n        },\n        {\n          \"description\": \"Atelier Cave (https://tinted-theming.github.io/tinted-gallery/#base16-atelier-cave)\",\n          \"type\": \"string\",\n          \"const\": \"AtelierCave\"\n        },\n        {\n          \"description\": \"Atelier Dune Light (https://tinted-theming.github.io/tinted-gallery/#base16-atelier-dune-light)\",\n          \"type\": \"string\",\n          \"const\": \"AtelierDuneLight\"\n        },\n        {\n          \"description\": \"Atelier Dune (https://tinted-theming.github.io/tinted-gallery/#base16-atelier-dune)\",\n          \"type\": \"string\",\n          \"const\": \"AtelierDune\"\n        },\n        {\n          \"description\": \"Atelier Estuary Light (https://tinted-theming.github.io/tinted-gallery/#base16-atelier-estuary-light)\",\n          \"type\": \"string\",\n          \"const\": \"AtelierEstuaryLight\"\n        },\n        {\n          \"description\": \"Atelier Estuary (https://tinted-theming.github.io/tinted-gallery/#base16-atelier-estuary)\",\n          \"type\": \"string\",\n          \"const\": \"AtelierEstuary\"\n        },\n        {\n          \"description\": \"Atelier Forest Light (https://tinted-theming.github.io/tinted-gallery/#base16-atelier-forest-light)\",\n          \"type\": \"string\",\n          \"const\": \"AtelierForestLight\"\n        },\n        {\n          \"description\": \"Atelier Forest (https://tinted-theming.github.io/tinted-gallery/#base16-atelier-forest)\",\n          \"type\": \"string\",\n          \"const\": \"AtelierForest\"\n        },\n        {\n          \"description\": \"Atelier Heath Light (https://tinted-theming.github.io/tinted-gallery/#base16-atelier-heath-light)\",\n          \"type\": \"string\",\n          \"const\": \"AtelierHeathLight\"\n        },\n        {\n          \"description\": \"Atelier Heath (https://tinted-theming.github.io/tinted-gallery/#base16-atelier-heath)\",\n          \"type\": \"string\",\n          \"const\": \"AtelierHeath\"\n        },\n        {\n          \"description\": \"Atelier Lakeside Light (https://tinted-theming.github.io/tinted-gallery/#base16-atelier-lakeside-light)\",\n          \"type\": \"string\",\n          \"const\": \"AtelierLakesideLight\"\n        },\n        {\n          \"description\": \"Atelier Lakeside (https://tinted-theming.github.io/tinted-gallery/#base16-atelier-lakeside)\",\n          \"type\": \"string\",\n          \"const\": \"AtelierLakeside\"\n        },\n        {\n          \"description\": \"Atelier Plateau Light (https://tinted-theming.github.io/tinted-gallery/#base16-atelier-plateau-light)\",\n          \"type\": \"string\",\n          \"const\": \"AtelierPlateauLight\"\n        },\n        {\n          \"description\": \"Atelier Plateau (https://tinted-theming.github.io/tinted-gallery/#base16-atelier-plateau)\",\n          \"type\": \"string\",\n          \"const\": \"AtelierPlateau\"\n        },\n        {\n          \"description\": \"Atelier Savanna Light (https://tinted-theming.github.io/tinted-gallery/#base16-atelier-savanna-light)\",\n          \"type\": \"string\",\n          \"const\": \"AtelierSavannaLight\"\n        },\n        {\n          \"description\": \"Atelier Savanna (https://tinted-theming.github.io/tinted-gallery/#base16-atelier-savanna)\",\n          \"type\": \"string\",\n          \"const\": \"AtelierSavanna\"\n        },\n        {\n          \"description\": \"Atelier Seaside Light (https://tinted-theming.github.io/tinted-gallery/#base16-atelier-seaside-light)\",\n          \"type\": \"string\",\n          \"const\": \"AtelierSeasideLight\"\n        },\n        {\n          \"description\": \"Atelier Seaside (https://tinted-theming.github.io/tinted-gallery/#base16-atelier-seaside)\",\n          \"type\": \"string\",\n          \"const\": \"AtelierSeaside\"\n        },\n        {\n          \"description\": \"Atelier Sulphurpool Light (https://tinted-theming.github.io/tinted-gallery/#base16-atelier-sulphurpool-light)\",\n          \"type\": \"string\",\n          \"const\": \"AtelierSulphurpoolLight\"\n        },\n        {\n          \"description\": \"Atelier Sulphurpool (https://tinted-theming.github.io/tinted-gallery/#base16-atelier-sulphurpool)\",\n          \"type\": \"string\",\n          \"const\": \"AtelierSulphurpool\"\n        },\n        {\n          \"description\": \"Atlas (https://tinted-theming.github.io/tinted-gallery/#base16-atlas)\",\n          \"type\": \"string\",\n          \"const\": \"Atlas\"\n        },\n        {\n          \"description\": \"Ayu Dark (https://tinted-theming.github.io/tinted-gallery/#base16-ayu-dark)\",\n          \"type\": \"string\",\n          \"const\": \"AyuDark\"\n        },\n        {\n          \"description\": \"Ayu Light (https://tinted-theming.github.io/tinted-gallery/#base16-ayu-light)\",\n          \"type\": \"string\",\n          \"const\": \"AyuLight\"\n        },\n        {\n          \"description\": \"Ayu Mirage (https://tinted-theming.github.io/tinted-gallery/#base16-ayu-mirage)\",\n          \"type\": \"string\",\n          \"const\": \"AyuMirage\"\n        },\n        {\n          \"description\": \"Aztec (https://tinted-theming.github.io/tinted-gallery/#base16-aztec)\",\n          \"type\": \"string\",\n          \"const\": \"Aztec\"\n        },\n        {\n          \"description\": \"Bespin (https://tinted-theming.github.io/tinted-gallery/#base16-bespin)\",\n          \"type\": \"string\",\n          \"const\": \"Bespin\"\n        },\n        {\n          \"description\": \"Black Metal Bathory (https://tinted-theming.github.io/tinted-gallery/#base16-black-metal-bathory)\",\n          \"type\": \"string\",\n          \"const\": \"BlackMetalBathory\"\n        },\n        {\n          \"description\": \"Black Metal Burzum (https://tinted-theming.github.io/tinted-gallery/#base16-black-metal-burzum)\",\n          \"type\": \"string\",\n          \"const\": \"BlackMetalBurzum\"\n        },\n        {\n          \"description\": \"Black Metal Dark Funeral (https://tinted-theming.github.io/tinted-gallery/#base16-black-metal-dark-funeral)\",\n          \"type\": \"string\",\n          \"const\": \"BlackMetalDarkFuneral\"\n        },\n        {\n          \"description\": \"Black Metal Gorgoroth (https://tinted-theming.github.io/tinted-gallery/#base16-black-metal-gorgoroth)\",\n          \"type\": \"string\",\n          \"const\": \"BlackMetalGorgoroth\"\n        },\n        {\n          \"description\": \"Black Metal Immortal (https://tinted-theming.github.io/tinted-gallery/#base16-black-metal-immortal)\",\n          \"type\": \"string\",\n          \"const\": \"BlackMetalImmortal\"\n        },\n        {\n          \"description\": \"Black Metal Khold (https://tinted-theming.github.io/tinted-gallery/#base16-black-metal-khold)\",\n          \"type\": \"string\",\n          \"const\": \"BlackMetalKhold\"\n        },\n        {\n          \"description\": \"Black Metal Marduk (https://tinted-theming.github.io/tinted-gallery/#base16-black-metal-marduk)\",\n          \"type\": \"string\",\n          \"const\": \"BlackMetalMarduk\"\n        },\n        {\n          \"description\": \"Black Metal Mayhem (https://tinted-theming.github.io/tinted-gallery/#base16-black-metal-mayhem)\",\n          \"type\": \"string\",\n          \"const\": \"BlackMetalMayhem\"\n        },\n        {\n          \"description\": \"Black Metal Nile (https://tinted-theming.github.io/tinted-gallery/#base16-black-metal-nile)\",\n          \"type\": \"string\",\n          \"const\": \"BlackMetalNile\"\n        },\n        {\n          \"description\": \"Black Metal Venom (https://tinted-theming.github.io/tinted-gallery/#base16-black-metal-venom)\",\n          \"type\": \"string\",\n          \"const\": \"BlackMetalVenom\"\n        },\n        {\n          \"description\": \"Black Metal (https://tinted-theming.github.io/tinted-gallery/#base16-black-metal)\",\n          \"type\": \"string\",\n          \"const\": \"BlackMetal\"\n        },\n        {\n          \"description\": \"Blueforest (https://tinted-theming.github.io/tinted-gallery/#base16-blueforest)\",\n          \"type\": \"string\",\n          \"const\": \"Blueforest\"\n        },\n        {\n          \"description\": \"Blueish (https://tinted-theming.github.io/tinted-gallery/#base16-blueish)\",\n          \"type\": \"string\",\n          \"const\": \"Blueish\"\n        },\n        {\n          \"description\": \"Brewer (https://tinted-theming.github.io/tinted-gallery/#base16-brewer)\",\n          \"type\": \"string\",\n          \"const\": \"Brewer\"\n        },\n        {\n          \"description\": \"Bright (https://tinted-theming.github.io/tinted-gallery/#base16-bright)\",\n          \"type\": \"string\",\n          \"const\": \"Bright\"\n        },\n        {\n          \"description\": \"Brogrammer (https://tinted-theming.github.io/tinted-gallery/#base16-brogrammer)\",\n          \"type\": \"string\",\n          \"const\": \"Brogrammer\"\n        },\n        {\n          \"description\": \"Brushtrees Dark (https://tinted-theming.github.io/tinted-gallery/#base16-brushtrees-dark)\",\n          \"type\": \"string\",\n          \"const\": \"BrushtreesDark\"\n        },\n        {\n          \"description\": \"Brushtrees (https://tinted-theming.github.io/tinted-gallery/#base16-brushtrees)\",\n          \"type\": \"string\",\n          \"const\": \"Brushtrees\"\n        },\n        {\n          \"description\": \"Caroline (https://tinted-theming.github.io/tinted-gallery/#base16-caroline)\",\n          \"type\": \"string\",\n          \"const\": \"Caroline\"\n        },\n        {\n          \"description\": \"Catppuccin Frappe (https://tinted-theming.github.io/tinted-gallery/#base16-catppuccin-frappe)\",\n          \"type\": \"string\",\n          \"const\": \"CatppuccinFrappe\"\n        },\n        {\n          \"description\": \"Catppuccin Latte (https://tinted-theming.github.io/tinted-gallery/#base16-catppuccin-latte)\",\n          \"type\": \"string\",\n          \"const\": \"CatppuccinLatte\"\n        },\n        {\n          \"description\": \"Catppuccin Macchiato (https://tinted-theming.github.io/tinted-gallery/#base16-catppuccin-macchiato)\",\n          \"type\": \"string\",\n          \"const\": \"CatppuccinMacchiato\"\n        },\n        {\n          \"description\": \"Catppuccin Mocha (https://tinted-theming.github.io/tinted-gallery/#base16-catppuccin-mocha)\",\n          \"type\": \"string\",\n          \"const\": \"CatppuccinMocha\"\n        },\n        {\n          \"description\": \"Chalk (https://tinted-theming.github.io/tinted-gallery/#base16-chalk)\",\n          \"type\": \"string\",\n          \"const\": \"Chalk\"\n        },\n        {\n          \"description\": \"Circus (https://tinted-theming.github.io/tinted-gallery/#base16-circus)\",\n          \"type\": \"string\",\n          \"const\": \"Circus\"\n        },\n        {\n          \"description\": \"Classic Dark (https://tinted-theming.github.io/tinted-gallery/#base16-classic-dark)\",\n          \"type\": \"string\",\n          \"const\": \"ClassicDark\"\n        },\n        {\n          \"description\": \"Classic Light (https://tinted-theming.github.io/tinted-gallery/#base16-classic-light)\",\n          \"type\": \"string\",\n          \"const\": \"ClassicLight\"\n        },\n        {\n          \"description\": \"Codeschool (https://tinted-theming.github.io/tinted-gallery/#base16-codeschool)\",\n          \"type\": \"string\",\n          \"const\": \"Codeschool\"\n        },\n        {\n          \"description\": \"Colors (https://tinted-theming.github.io/tinted-gallery/#base16-colors)\",\n          \"type\": \"string\",\n          \"const\": \"Colors\"\n        },\n        {\n          \"description\": \"Cupcake (https://tinted-theming.github.io/tinted-gallery/#base16-cupcake)\",\n          \"type\": \"string\",\n          \"const\": \"Cupcake\"\n        },\n        {\n          \"description\": \"Cupertino (https://tinted-theming.github.io/tinted-gallery/#base16-cupertino)\",\n          \"type\": \"string\",\n          \"const\": \"Cupertino\"\n        },\n        {\n          \"description\": \"Da One Black (https://tinted-theming.github.io/tinted-gallery/#base16-da-one-black)\",\n          \"type\": \"string\",\n          \"const\": \"DaOneBlack\"\n        },\n        {\n          \"description\": \"Da One Gray (https://tinted-theming.github.io/tinted-gallery/#base16-da-one-gray)\",\n          \"type\": \"string\",\n          \"const\": \"DaOneGray\"\n        },\n        {\n          \"description\": \"Da One Ocean (https://tinted-theming.github.io/tinted-gallery/#base16-da-one-ocean)\",\n          \"type\": \"string\",\n          \"const\": \"DaOneOcean\"\n        },\n        {\n          \"description\": \"Da One Paper (https://tinted-theming.github.io/tinted-gallery/#base16-da-one-paper)\",\n          \"type\": \"string\",\n          \"const\": \"DaOnePaper\"\n        },\n        {\n          \"description\": \"Da One Sea (https://tinted-theming.github.io/tinted-gallery/#base16-da-one-sea)\",\n          \"type\": \"string\",\n          \"const\": \"DaOneSea\"\n        },\n        {\n          \"description\": \"Da One White (https://tinted-theming.github.io/tinted-gallery/#base16-da-one-white)\",\n          \"type\": \"string\",\n          \"const\": \"DaOneWhite\"\n        },\n        {\n          \"description\": \"Danqing Light (https://tinted-theming.github.io/tinted-gallery/#base16-danqing-light)\",\n          \"type\": \"string\",\n          \"const\": \"DanqingLight\"\n        },\n        {\n          \"description\": \"Danqing (https://tinted-theming.github.io/tinted-gallery/#base16-danqing)\",\n          \"type\": \"string\",\n          \"const\": \"Danqing\"\n        },\n        {\n          \"description\": \"Darcula (https://tinted-theming.github.io/tinted-gallery/#base16-darcula)\",\n          \"type\": \"string\",\n          \"const\": \"Darcula\"\n        },\n        {\n          \"description\": \"Darkmoss (https://tinted-theming.github.io/tinted-gallery/#base16-darkmoss)\",\n          \"type\": \"string\",\n          \"const\": \"Darkmoss\"\n        },\n        {\n          \"description\": \"Darktooth (https://tinted-theming.github.io/tinted-gallery/#base16-darktooth)\",\n          \"type\": \"string\",\n          \"const\": \"Darktooth\"\n        },\n        {\n          \"description\": \"Darkviolet (https://tinted-theming.github.io/tinted-gallery/#base16-darkviolet)\",\n          \"type\": \"string\",\n          \"const\": \"Darkviolet\"\n        },\n        {\n          \"description\": \"Decaf (https://tinted-theming.github.io/tinted-gallery/#base16-decaf)\",\n          \"type\": \"string\",\n          \"const\": \"Decaf\"\n        },\n        {\n          \"description\": \"Default Dark (https://tinted-theming.github.io/tinted-gallery/#base16-default-dark)\",\n          \"type\": \"string\",\n          \"const\": \"DefaultDark\"\n        },\n        {\n          \"description\": \"Default Light (https://tinted-theming.github.io/tinted-gallery/#base16-default-light)\",\n          \"type\": \"string\",\n          \"const\": \"DefaultLight\"\n        },\n        {\n          \"description\": \"Dirtysea (https://tinted-theming.github.io/tinted-gallery/#base16-dirtysea)\",\n          \"type\": \"string\",\n          \"const\": \"Dirtysea\"\n        },\n        {\n          \"description\": \"Dracula (https://tinted-theming.github.io/tinted-gallery/#base16-dracula)\",\n          \"type\": \"string\",\n          \"const\": \"Dracula\"\n        },\n        {\n          \"description\": \"Edge Dark (https://tinted-theming.github.io/tinted-gallery/#base16-edge-dark)\",\n          \"type\": \"string\",\n          \"const\": \"EdgeDark\"\n        },\n        {\n          \"description\": \"Edge Light (https://tinted-theming.github.io/tinted-gallery/#base16-edge-light)\",\n          \"type\": \"string\",\n          \"const\": \"EdgeLight\"\n        },\n        {\n          \"description\": \"Eighties (https://tinted-theming.github.io/tinted-gallery/#base16-eighties)\",\n          \"type\": \"string\",\n          \"const\": \"Eighties\"\n        },\n        {\n          \"description\": \"Embers Light (https://tinted-theming.github.io/tinted-gallery/#base16-embers-light)\",\n          \"type\": \"string\",\n          \"const\": \"EmbersLight\"\n        },\n        {\n          \"description\": \"Embers (https://tinted-theming.github.io/tinted-gallery/#base16-embers)\",\n          \"type\": \"string\",\n          \"const\": \"Embers\"\n        },\n        {\n          \"description\": \"Emil (https://tinted-theming.github.io/tinted-gallery/#base16-emil)\",\n          \"type\": \"string\",\n          \"const\": \"Emil\"\n        },\n        {\n          \"description\": \"Equilibrium Dark (https://tinted-theming.github.io/tinted-gallery/#base16-equilibrium-dark)\",\n          \"type\": \"string\",\n          \"const\": \"EquilibriumDark\"\n        },\n        {\n          \"description\": \"Equilibrium Gray Dark (https://tinted-theming.github.io/tinted-gallery/#base16-equilibrium-gray-dark)\",\n          \"type\": \"string\",\n          \"const\": \"EquilibriumGrayDark\"\n        },\n        {\n          \"description\": \"Equilibrium Gray Light (https://tinted-theming.github.io/tinted-gallery/#base16-equilibrium-gray-light)\",\n          \"type\": \"string\",\n          \"const\": \"EquilibriumGrayLight\"\n        },\n        {\n          \"description\": \"Equilibrium Light (https://tinted-theming.github.io/tinted-gallery/#base16-equilibrium-light)\",\n          \"type\": \"string\",\n          \"const\": \"EquilibriumLight\"\n        },\n        {\n          \"description\": \"Eris (https://tinted-theming.github.io/tinted-gallery/#base16-eris)\",\n          \"type\": \"string\",\n          \"const\": \"Eris\"\n        },\n        {\n          \"description\": \"Espresso (https://tinted-theming.github.io/tinted-gallery/#base16-espresso)\",\n          \"type\": \"string\",\n          \"const\": \"Espresso\"\n        },\n        {\n          \"description\": \"Eva Dim (https://tinted-theming.github.io/tinted-gallery/#base16-eva-dim)\",\n          \"type\": \"string\",\n          \"const\": \"EvaDim\"\n        },\n        {\n          \"description\": \"Eva (https://tinted-theming.github.io/tinted-gallery/#base16-eva)\",\n          \"type\": \"string\",\n          \"const\": \"Eva\"\n        },\n        {\n          \"description\": \"Evenok Dark (https://tinted-theming.github.io/tinted-gallery/#base16-evenok-dark)\",\n          \"type\": \"string\",\n          \"const\": \"EvenokDark\"\n        },\n        {\n          \"description\": \"Everforest Dark Hard (https://tinted-theming.github.io/tinted-gallery/#base16-everforest-dark-hard)\",\n          \"type\": \"string\",\n          \"const\": \"EverforestDarkHard\"\n        },\n        {\n          \"description\": \"Everforest (https://tinted-theming.github.io/tinted-gallery/#base16-everforest)\",\n          \"type\": \"string\",\n          \"const\": \"Everforest\"\n        },\n        {\n          \"description\": \"Flat (https://tinted-theming.github.io/tinted-gallery/#base16-flat)\",\n          \"type\": \"string\",\n          \"const\": \"Flat\"\n        },\n        {\n          \"description\": \"Framer (https://tinted-theming.github.io/tinted-gallery/#base16-framer)\",\n          \"type\": \"string\",\n          \"const\": \"Framer\"\n        },\n        {\n          \"description\": \"Fruit Soda (https://tinted-theming.github.io/tinted-gallery/#base16-fruit-soda)\",\n          \"type\": \"string\",\n          \"const\": \"FruitSoda\"\n        },\n        {\n          \"description\": \"Gigavolt (https://tinted-theming.github.io/tinted-gallery/#base16-gigavolt)\",\n          \"type\": \"string\",\n          \"const\": \"Gigavolt\"\n        },\n        {\n          \"description\": \"Github (https://tinted-theming.github.io/tinted-gallery/#base16-github)\",\n          \"type\": \"string\",\n          \"const\": \"Github\"\n        },\n        {\n          \"description\": \"Google Dark (https://tinted-theming.github.io/tinted-gallery/#base16-google-dark)\",\n          \"type\": \"string\",\n          \"const\": \"GoogleDark\"\n        },\n        {\n          \"description\": \"Google Light (https://tinted-theming.github.io/tinted-gallery/#base16-google-light)\",\n          \"type\": \"string\",\n          \"const\": \"GoogleLight\"\n        },\n        {\n          \"description\": \"Gotham (https://tinted-theming.github.io/tinted-gallery/#base16-gotham)\",\n          \"type\": \"string\",\n          \"const\": \"Gotham\"\n        },\n        {\n          \"description\": \"Grayscale Dark (https://tinted-theming.github.io/tinted-gallery/#base16-grayscale-dark)\",\n          \"type\": \"string\",\n          \"const\": \"GrayscaleDark\"\n        },\n        {\n          \"description\": \"Grayscale Light (https://tinted-theming.github.io/tinted-gallery/#base16-grayscale-light)\",\n          \"type\": \"string\",\n          \"const\": \"GrayscaleLight\"\n        },\n        {\n          \"description\": \"Greenscreen (https://tinted-theming.github.io/tinted-gallery/#base16-greenscreen)\",\n          \"type\": \"string\",\n          \"const\": \"Greenscreen\"\n        },\n        {\n          \"description\": \"Gruber (https://tinted-theming.github.io/tinted-gallery/#base16-gruber)\",\n          \"type\": \"string\",\n          \"const\": \"Gruber\"\n        },\n        {\n          \"description\": \"Gruvbox Dark Hard (https://tinted-theming.github.io/tinted-gallery/#base16-gruvbox-dark-hard)\",\n          \"type\": \"string\",\n          \"const\": \"GruvboxDarkHard\"\n        },\n        {\n          \"description\": \"Gruvbox Dark Medium (https://tinted-theming.github.io/tinted-gallery/#base16-gruvbox-dark-medium)\",\n          \"type\": \"string\",\n          \"const\": \"GruvboxDarkMedium\"\n        },\n        {\n          \"description\": \"Gruvbox Dark Pale (https://tinted-theming.github.io/tinted-gallery/#base16-gruvbox-dark-pale)\",\n          \"type\": \"string\",\n          \"const\": \"GruvboxDarkPale\"\n        },\n        {\n          \"description\": \"Gruvbox Dark Soft (https://tinted-theming.github.io/tinted-gallery/#base16-gruvbox-dark-soft)\",\n          \"type\": \"string\",\n          \"const\": \"GruvboxDarkSoft\"\n        },\n        {\n          \"description\": \"Gruvbox Light Hard (https://tinted-theming.github.io/tinted-gallery/#base16-gruvbox-light-hard)\",\n          \"type\": \"string\",\n          \"const\": \"GruvboxLightHard\"\n        },\n        {\n          \"description\": \"Gruvbox Light Medium (https://tinted-theming.github.io/tinted-gallery/#base16-gruvbox-light-medium)\",\n          \"type\": \"string\",\n          \"const\": \"GruvboxLightMedium\"\n        },\n        {\n          \"description\": \"Gruvbox Light Soft (https://tinted-theming.github.io/tinted-gallery/#base16-gruvbox-light-soft)\",\n          \"type\": \"string\",\n          \"const\": \"GruvboxLightSoft\"\n        },\n        {\n          \"description\": \"Gruvbox Material Dark Hard (https://tinted-theming.github.io/tinted-gallery/#base16-gruvbox-material-dark-hard)\",\n          \"type\": \"string\",\n          \"const\": \"GruvboxMaterialDarkHard\"\n        },\n        {\n          \"description\": \"Gruvbox Material Dark Medium (https://tinted-theming.github.io/tinted-gallery/#base16-gruvbox-material-dark-medium)\",\n          \"type\": \"string\",\n          \"const\": \"GruvboxMaterialDarkMedium\"\n        },\n        {\n          \"description\": \"Gruvbox Material Dark Soft (https://tinted-theming.github.io/tinted-gallery/#base16-gruvbox-material-dark-soft)\",\n          \"type\": \"string\",\n          \"const\": \"GruvboxMaterialDarkSoft\"\n        },\n        {\n          \"description\": \"Gruvbox Material Light Hard (https://tinted-theming.github.io/tinted-gallery/#base16-gruvbox-material-light-hard)\",\n          \"type\": \"string\",\n          \"const\": \"GruvboxMaterialLightHard\"\n        },\n        {\n          \"description\": \"Gruvbox Material Light Medium (https://tinted-theming.github.io/tinted-gallery/#base16-gruvbox-material-light-medium)\",\n          \"type\": \"string\",\n          \"const\": \"GruvboxMaterialLightMedium\"\n        },\n        {\n          \"description\": \"Gruvbox Material Light Soft (https://tinted-theming.github.io/tinted-gallery/#base16-gruvbox-material-light-soft)\",\n          \"type\": \"string\",\n          \"const\": \"GruvboxMaterialLightSoft\"\n        },\n        {\n          \"description\": \"Hardcore (https://tinted-theming.github.io/tinted-gallery/#base16-hardcore)\",\n          \"type\": \"string\",\n          \"const\": \"Hardcore\"\n        },\n        {\n          \"description\": \"Harmonic16 Dark (https://tinted-theming.github.io/tinted-gallery/#base16-harmonic16-dark)\",\n          \"type\": \"string\",\n          \"const\": \"Harmonic16Dark\"\n        },\n        {\n          \"description\": \"Harmonic16 Light (https://tinted-theming.github.io/tinted-gallery/#base16-harmonic16-light)\",\n          \"type\": \"string\",\n          \"const\": \"Harmonic16Light\"\n        },\n        {\n          \"description\": \"Heetch Light (https://tinted-theming.github.io/tinted-gallery/#base16-heetch-light)\",\n          \"type\": \"string\",\n          \"const\": \"HeetchLight\"\n        },\n        {\n          \"description\": \"Heetch (https://tinted-theming.github.io/tinted-gallery/#base16-heetch)\",\n          \"type\": \"string\",\n          \"const\": \"Heetch\"\n        },\n        {\n          \"description\": \"Helios (https://tinted-theming.github.io/tinted-gallery/#base16-helios)\",\n          \"type\": \"string\",\n          \"const\": \"Helios\"\n        },\n        {\n          \"description\": \"Hopscotch (https://tinted-theming.github.io/tinted-gallery/#base16-hopscotch)\",\n          \"type\": \"string\",\n          \"const\": \"Hopscotch\"\n        },\n        {\n          \"description\": \"Horizon Dark (https://tinted-theming.github.io/tinted-gallery/#base16-horizon-dark)\",\n          \"type\": \"string\",\n          \"const\": \"HorizonDark\"\n        },\n        {\n          \"description\": \"Horizon Light (https://tinted-theming.github.io/tinted-gallery/#base16-horizon-light)\",\n          \"type\": \"string\",\n          \"const\": \"HorizonLight\"\n        },\n        {\n          \"description\": \"Horizon Terminal Dark (https://tinted-theming.github.io/tinted-gallery/#base16-horizon-terminal-dark)\",\n          \"type\": \"string\",\n          \"const\": \"HorizonTerminalDark\"\n        },\n        {\n          \"description\": \"Horizon Terminal Light (https://tinted-theming.github.io/tinted-gallery/#base16-horizon-terminal-light)\",\n          \"type\": \"string\",\n          \"const\": \"HorizonTerminalLight\"\n        },\n        {\n          \"description\": \"Humanoid Dark (https://tinted-theming.github.io/tinted-gallery/#base16-humanoid-dark)\",\n          \"type\": \"string\",\n          \"const\": \"HumanoidDark\"\n        },\n        {\n          \"description\": \"Humanoid Light (https://tinted-theming.github.io/tinted-gallery/#base16-humanoid-light)\",\n          \"type\": \"string\",\n          \"const\": \"HumanoidLight\"\n        },\n        {\n          \"description\": \"Ia Dark (https://tinted-theming.github.io/tinted-gallery/#base16-ia-dark)\",\n          \"type\": \"string\",\n          \"const\": \"IaDark\"\n        },\n        {\n          \"description\": \"Ia Light (https://tinted-theming.github.io/tinted-gallery/#base16-ia-light)\",\n          \"type\": \"string\",\n          \"const\": \"IaLight\"\n        },\n        {\n          \"description\": \"Icy (https://tinted-theming.github.io/tinted-gallery/#base16-icy)\",\n          \"type\": \"string\",\n          \"const\": \"Icy\"\n        },\n        {\n          \"description\": \"Irblack (https://tinted-theming.github.io/tinted-gallery/#base16-irblack)\",\n          \"type\": \"string\",\n          \"const\": \"Irblack\"\n        },\n        {\n          \"description\": \"Isotope (https://tinted-theming.github.io/tinted-gallery/#base16-isotope)\",\n          \"type\": \"string\",\n          \"const\": \"Isotope\"\n        },\n        {\n          \"description\": \"Jabuti (https://tinted-theming.github.io/tinted-gallery/#base16-jabuti)\",\n          \"type\": \"string\",\n          \"const\": \"Jabuti\"\n        },\n        {\n          \"description\": \"Kanagawa (https://tinted-theming.github.io/tinted-gallery/#base16-kanagawa)\",\n          \"type\": \"string\",\n          \"const\": \"Kanagawa\"\n        },\n        {\n          \"description\": \"Katy (https://tinted-theming.github.io/tinted-gallery/#base16-katy)\",\n          \"type\": \"string\",\n          \"const\": \"Katy\"\n        },\n        {\n          \"description\": \"Kimber (https://tinted-theming.github.io/tinted-gallery/#base16-kimber)\",\n          \"type\": \"string\",\n          \"const\": \"Kimber\"\n        },\n        {\n          \"description\": \"Lime (https://tinted-theming.github.io/tinted-gallery/#base16-lime)\",\n          \"type\": \"string\",\n          \"const\": \"Lime\"\n        },\n        {\n          \"description\": \"Macintosh (https://tinted-theming.github.io/tinted-gallery/#base16-macintosh)\",\n          \"type\": \"string\",\n          \"const\": \"Macintosh\"\n        },\n        {\n          \"description\": \"Marrakesh (https://tinted-theming.github.io/tinted-gallery/#base16-marrakesh)\",\n          \"type\": \"string\",\n          \"const\": \"Marrakesh\"\n        },\n        {\n          \"description\": \"Materia (https://tinted-theming.github.io/tinted-gallery/#base16-materia)\",\n          \"type\": \"string\",\n          \"const\": \"Materia\"\n        },\n        {\n          \"description\": \"Material Darker (https://tinted-theming.github.io/tinted-gallery/#base16-material-darker)\",\n          \"type\": \"string\",\n          \"const\": \"MaterialDarker\"\n        },\n        {\n          \"description\": \"Material Lighter (https://tinted-theming.github.io/tinted-gallery/#base16-material-lighter)\",\n          \"type\": \"string\",\n          \"const\": \"MaterialLighter\"\n        },\n        {\n          \"description\": \"Material Palenight (https://tinted-theming.github.io/tinted-gallery/#base16-material-palenight)\",\n          \"type\": \"string\",\n          \"const\": \"MaterialPalenight\"\n        },\n        {\n          \"description\": \"Material Vivid (https://tinted-theming.github.io/tinted-gallery/#base16-material-vivid)\",\n          \"type\": \"string\",\n          \"const\": \"MaterialVivid\"\n        },\n        {\n          \"description\": \"Material (https://tinted-theming.github.io/tinted-gallery/#base16-material)\",\n          \"type\": \"string\",\n          \"const\": \"Material\"\n        },\n        {\n          \"description\": \"Measured Dark (https://tinted-theming.github.io/tinted-gallery/#base16-measured-dark)\",\n          \"type\": \"string\",\n          \"const\": \"MeasuredDark\"\n        },\n        {\n          \"description\": \"Measured Light (https://tinted-theming.github.io/tinted-gallery/#base16-measured-light)\",\n          \"type\": \"string\",\n          \"const\": \"MeasuredLight\"\n        },\n        {\n          \"description\": \"Mellow Purple (https://tinted-theming.github.io/tinted-gallery/#base16-mellow-purple)\",\n          \"type\": \"string\",\n          \"const\": \"MellowPurple\"\n        },\n        {\n          \"description\": \"Mexico Light (https://tinted-theming.github.io/tinted-gallery/#base16-mexico-light)\",\n          \"type\": \"string\",\n          \"const\": \"MexicoLight\"\n        },\n        {\n          \"description\": \"Mocha (https://tinted-theming.github.io/tinted-gallery/#base16-mocha)\",\n          \"type\": \"string\",\n          \"const\": \"Mocha\"\n        },\n        {\n          \"description\": \"Monokai (https://tinted-theming.github.io/tinted-gallery/#base16-monokai)\",\n          \"type\": \"string\",\n          \"const\": \"Monokai\"\n        },\n        {\n          \"description\": \"Moonlight (https://tinted-theming.github.io/tinted-gallery/#base16-moonlight)\",\n          \"type\": \"string\",\n          \"const\": \"Moonlight\"\n        },\n        {\n          \"description\": \"Mountain (https://tinted-theming.github.io/tinted-gallery/#base16-mountain)\",\n          \"type\": \"string\",\n          \"const\": \"Mountain\"\n        },\n        {\n          \"description\": \"Nebula (https://tinted-theming.github.io/tinted-gallery/#base16-nebula)\",\n          \"type\": \"string\",\n          \"const\": \"Nebula\"\n        },\n        {\n          \"description\": \"Nord Light (https://tinted-theming.github.io/tinted-gallery/#base16-nord-light)\",\n          \"type\": \"string\",\n          \"const\": \"NordLight\"\n        },\n        {\n          \"description\": \"Nord (https://tinted-theming.github.io/tinted-gallery/#base16-nord)\",\n          \"type\": \"string\",\n          \"const\": \"Nord\"\n        },\n        {\n          \"description\": \"Nova (https://tinted-theming.github.io/tinted-gallery/#base16-nova)\",\n          \"type\": \"string\",\n          \"const\": \"Nova\"\n        },\n        {\n          \"description\": \"Ocean (https://tinted-theming.github.io/tinted-gallery/#base16-ocean)\",\n          \"type\": \"string\",\n          \"const\": \"Ocean\"\n        },\n        {\n          \"description\": \"Oceanicnext (https://tinted-theming.github.io/tinted-gallery/#base16-oceanicnext)\",\n          \"type\": \"string\",\n          \"const\": \"Oceanicnext\"\n        },\n        {\n          \"description\": \"One Light (https://tinted-theming.github.io/tinted-gallery/#base16-one-light)\",\n          \"type\": \"string\",\n          \"const\": \"OneLight\"\n        },\n        {\n          \"description\": \"Onedark Dark (https://tinted-theming.github.io/tinted-gallery/#base16-onedark-dark)\",\n          \"type\": \"string\",\n          \"const\": \"OnedarkDark\"\n        },\n        {\n          \"description\": \"Onedark (https://tinted-theming.github.io/tinted-gallery/#base16-onedark)\",\n          \"type\": \"string\",\n          \"const\": \"Onedark\"\n        },\n        {\n          \"description\": \"Outrun Dark (https://tinted-theming.github.io/tinted-gallery/#base16-outrun-dark)\",\n          \"type\": \"string\",\n          \"const\": \"OutrunDark\"\n        },\n        {\n          \"description\": \"Oxocarbon Dark (https://tinted-theming.github.io/tinted-gallery/#base16-oxocarbon-dark)\",\n          \"type\": \"string\",\n          \"const\": \"OxocarbonDark\"\n        },\n        {\n          \"description\": \"Oxocarbon Light (https://tinted-theming.github.io/tinted-gallery/#base16-oxocarbon-light)\",\n          \"type\": \"string\",\n          \"const\": \"OxocarbonLight\"\n        },\n        {\n          \"description\": \"Pandora (https://tinted-theming.github.io/tinted-gallery/#base16-pandora)\",\n          \"type\": \"string\",\n          \"const\": \"Pandora\"\n        },\n        {\n          \"description\": \"Papercolor Dark (https://tinted-theming.github.io/tinted-gallery/#base16-papercolor-dark)\",\n          \"type\": \"string\",\n          \"const\": \"PapercolorDark\"\n        },\n        {\n          \"description\": \"Papercolor Light (https://tinted-theming.github.io/tinted-gallery/#base16-papercolor-light)\",\n          \"type\": \"string\",\n          \"const\": \"PapercolorLight\"\n        },\n        {\n          \"description\": \"Paraiso (https://tinted-theming.github.io/tinted-gallery/#base16-paraiso)\",\n          \"type\": \"string\",\n          \"const\": \"Paraiso\"\n        },\n        {\n          \"description\": \"Pasque (https://tinted-theming.github.io/tinted-gallery/#base16-pasque)\",\n          \"type\": \"string\",\n          \"const\": \"Pasque\"\n        },\n        {\n          \"description\": \"Phd (https://tinted-theming.github.io/tinted-gallery/#base16-phd)\",\n          \"type\": \"string\",\n          \"const\": \"Phd\"\n        },\n        {\n          \"description\": \"Pico (https://tinted-theming.github.io/tinted-gallery/#base16-pico)\",\n          \"type\": \"string\",\n          \"const\": \"Pico\"\n        },\n        {\n          \"description\": \"Pinky (https://tinted-theming.github.io/tinted-gallery/#base16-pinky)\",\n          \"type\": \"string\",\n          \"const\": \"Pinky\"\n        },\n        {\n          \"description\": \"Pop (https://tinted-theming.github.io/tinted-gallery/#base16-pop)\",\n          \"type\": \"string\",\n          \"const\": \"Pop\"\n        },\n        {\n          \"description\": \"Porple (https://tinted-theming.github.io/tinted-gallery/#base16-porple)\",\n          \"type\": \"string\",\n          \"const\": \"Porple\"\n        },\n        {\n          \"description\": \"Precious Dark Eleven (https://tinted-theming.github.io/tinted-gallery/#base16-precious-dark-eleven)\",\n          \"type\": \"string\",\n          \"const\": \"PreciousDarkEleven\"\n        },\n        {\n          \"description\": \"Precious Dark Fifteen (https://tinted-theming.github.io/tinted-gallery/#base16-precious-dark-fifteen)\",\n          \"type\": \"string\",\n          \"const\": \"PreciousDarkFifteen\"\n        },\n        {\n          \"description\": \"Precious Light Warm (https://tinted-theming.github.io/tinted-gallery/#base16-precious-light-warm)\",\n          \"type\": \"string\",\n          \"const\": \"PreciousLightWarm\"\n        },\n        {\n          \"description\": \"Precious Light White (https://tinted-theming.github.io/tinted-gallery/#base16-precious-light-white)\",\n          \"type\": \"string\",\n          \"const\": \"PreciousLightWhite\"\n        },\n        {\n          \"description\": \"Primer Dark Dimmed (https://tinted-theming.github.io/tinted-gallery/#base16-primer-dark-dimmed)\",\n          \"type\": \"string\",\n          \"const\": \"PrimerDarkDimmed\"\n        },\n        {\n          \"description\": \"Primer Dark (https://tinted-theming.github.io/tinted-gallery/#base16-primer-dark)\",\n          \"type\": \"string\",\n          \"const\": \"PrimerDark\"\n        },\n        {\n          \"description\": \"Primer Light (https://tinted-theming.github.io/tinted-gallery/#base16-primer-light)\",\n          \"type\": \"string\",\n          \"const\": \"PrimerLight\"\n        },\n        {\n          \"description\": \"Purpledream (https://tinted-theming.github.io/tinted-gallery/#base16-purpledream)\",\n          \"type\": \"string\",\n          \"const\": \"Purpledream\"\n        },\n        {\n          \"description\": \"Qualia (https://tinted-theming.github.io/tinted-gallery/#base16-qualia)\",\n          \"type\": \"string\",\n          \"const\": \"Qualia\"\n        },\n        {\n          \"description\": \"Railscasts (https://tinted-theming.github.io/tinted-gallery/#base16-railscasts)\",\n          \"type\": \"string\",\n          \"const\": \"Railscasts\"\n        },\n        {\n          \"description\": \"Rebecca (https://tinted-theming.github.io/tinted-gallery/#base16-rebecca)\",\n          \"type\": \"string\",\n          \"const\": \"Rebecca\"\n        },\n        {\n          \"description\": \"Rose Pine Dawn (https://tinted-theming.github.io/tinted-gallery/#base16-rose-pine-dawn)\",\n          \"type\": \"string\",\n          \"const\": \"RosePineDawn\"\n        },\n        {\n          \"description\": \"Rose Pine Moon (https://tinted-theming.github.io/tinted-gallery/#base16-rose-pine-moon)\",\n          \"type\": \"string\",\n          \"const\": \"RosePineMoon\"\n        },\n        {\n          \"description\": \"Rose Pine (https://tinted-theming.github.io/tinted-gallery/#base16-rose-pine)\",\n          \"type\": \"string\",\n          \"const\": \"RosePine\"\n        },\n        {\n          \"description\": \"Saga (https://tinted-theming.github.io/tinted-gallery/#base16-saga)\",\n          \"type\": \"string\",\n          \"const\": \"Saga\"\n        },\n        {\n          \"description\": \"Sagelight (https://tinted-theming.github.io/tinted-gallery/#base16-sagelight)\",\n          \"type\": \"string\",\n          \"const\": \"Sagelight\"\n        },\n        {\n          \"description\": \"Sakura (https://tinted-theming.github.io/tinted-gallery/#base16-sakura)\",\n          \"type\": \"string\",\n          \"const\": \"Sakura\"\n        },\n        {\n          \"description\": \"Sandcastle (https://tinted-theming.github.io/tinted-gallery/#base16-sandcastle)\",\n          \"type\": \"string\",\n          \"const\": \"Sandcastle\"\n        },\n        {\n          \"description\": \"Selenized Black (https://tinted-theming.github.io/tinted-gallery/#base16-selenized-black)\",\n          \"type\": \"string\",\n          \"const\": \"SelenizedBlack\"\n        },\n        {\n          \"description\": \"Selenized Dark (https://tinted-theming.github.io/tinted-gallery/#base16-selenized-dark)\",\n          \"type\": \"string\",\n          \"const\": \"SelenizedDark\"\n        },\n        {\n          \"description\": \"Selenized Light (https://tinted-theming.github.io/tinted-gallery/#base16-selenized-light)\",\n          \"type\": \"string\",\n          \"const\": \"SelenizedLight\"\n        },\n        {\n          \"description\": \"Selenized White (https://tinted-theming.github.io/tinted-gallery/#base16-selenized-white)\",\n          \"type\": \"string\",\n          \"const\": \"SelenizedWhite\"\n        },\n        {\n          \"description\": \"Seti (https://tinted-theming.github.io/tinted-gallery/#base16-seti)\",\n          \"type\": \"string\",\n          \"const\": \"Seti\"\n        },\n        {\n          \"description\": \"Shades Of Purple (https://tinted-theming.github.io/tinted-gallery/#base16-shades-of-purple)\",\n          \"type\": \"string\",\n          \"const\": \"ShadesOfPurple\"\n        },\n        {\n          \"description\": \"Shadesmear Dark (https://tinted-theming.github.io/tinted-gallery/#base16-shadesmear-dark)\",\n          \"type\": \"string\",\n          \"const\": \"ShadesmearDark\"\n        },\n        {\n          \"description\": \"Shadesmear Light (https://tinted-theming.github.io/tinted-gallery/#base16-shadesmear-light)\",\n          \"type\": \"string\",\n          \"const\": \"ShadesmearLight\"\n        },\n        {\n          \"description\": \"Shapeshifter (https://tinted-theming.github.io/tinted-gallery/#base16-shapeshifter)\",\n          \"type\": \"string\",\n          \"const\": \"Shapeshifter\"\n        },\n        {\n          \"description\": \"Silk Dark (https://tinted-theming.github.io/tinted-gallery/#base16-silk-dark)\",\n          \"type\": \"string\",\n          \"const\": \"SilkDark\"\n        },\n        {\n          \"description\": \"Silk Light (https://tinted-theming.github.io/tinted-gallery/#base16-silk-light)\",\n          \"type\": \"string\",\n          \"const\": \"SilkLight\"\n        },\n        {\n          \"description\": \"Snazzy (https://tinted-theming.github.io/tinted-gallery/#base16-snazzy)\",\n          \"type\": \"string\",\n          \"const\": \"Snazzy\"\n        },\n        {\n          \"description\": \"Solarflare Light (https://tinted-theming.github.io/tinted-gallery/#base16-solarflare-light)\",\n          \"type\": \"string\",\n          \"const\": \"SolarflareLight\"\n        },\n        {\n          \"description\": \"Solarflare (https://tinted-theming.github.io/tinted-gallery/#base16-solarflare)\",\n          \"type\": \"string\",\n          \"const\": \"Solarflare\"\n        },\n        {\n          \"description\": \"Solarized Dark (https://tinted-theming.github.io/tinted-gallery/#base16-solarized-dark)\",\n          \"type\": \"string\",\n          \"const\": \"SolarizedDark\"\n        },\n        {\n          \"description\": \"Solarized Light (https://tinted-theming.github.io/tinted-gallery/#base16-solarized-light)\",\n          \"type\": \"string\",\n          \"const\": \"SolarizedLight\"\n        },\n        {\n          \"description\": \"Spaceduck (https://tinted-theming.github.io/tinted-gallery/#base16-spaceduck)\",\n          \"type\": \"string\",\n          \"const\": \"Spaceduck\"\n        },\n        {\n          \"description\": \"Spacemacs (https://tinted-theming.github.io/tinted-gallery/#base16-spacemacs)\",\n          \"type\": \"string\",\n          \"const\": \"Spacemacs\"\n        },\n        {\n          \"description\": \"Sparky (https://tinted-theming.github.io/tinted-gallery/#base16-sparky)\",\n          \"type\": \"string\",\n          \"const\": \"Sparky\"\n        },\n        {\n          \"description\": \"Standardized Dark (https://tinted-theming.github.io/tinted-gallery/#base16-standardized-dark)\",\n          \"type\": \"string\",\n          \"const\": \"StandardizedDark\"\n        },\n        {\n          \"description\": \"Standardized Light (https://tinted-theming.github.io/tinted-gallery/#base16-standardized-light)\",\n          \"type\": \"string\",\n          \"const\": \"StandardizedLight\"\n        },\n        {\n          \"description\": \"Stella (https://tinted-theming.github.io/tinted-gallery/#base16-stella)\",\n          \"type\": \"string\",\n          \"const\": \"Stella\"\n        },\n        {\n          \"description\": \"Still Alive (https://tinted-theming.github.io/tinted-gallery/#base16-still-alive)\",\n          \"type\": \"string\",\n          \"const\": \"StillAlive\"\n        },\n        {\n          \"description\": \"Summercamp (https://tinted-theming.github.io/tinted-gallery/#base16-summercamp)\",\n          \"type\": \"string\",\n          \"const\": \"Summercamp\"\n        },\n        {\n          \"description\": \"Summerfruit Dark (https://tinted-theming.github.io/tinted-gallery/#base16-summerfruit-dark)\",\n          \"type\": \"string\",\n          \"const\": \"SummerfruitDark\"\n        },\n        {\n          \"description\": \"Summerfruit Light (https://tinted-theming.github.io/tinted-gallery/#base16-summerfruit-light)\",\n          \"type\": \"string\",\n          \"const\": \"SummerfruitLight\"\n        },\n        {\n          \"description\": \"Synth Midnight Dark (https://tinted-theming.github.io/tinted-gallery/#base16-synth-midnight-dark)\",\n          \"type\": \"string\",\n          \"const\": \"SynthMidnightDark\"\n        },\n        {\n          \"description\": \"Synth Midnight Light (https://tinted-theming.github.io/tinted-gallery/#base16-synth-midnight-light)\",\n          \"type\": \"string\",\n          \"const\": \"SynthMidnightLight\"\n        },\n        {\n          \"description\": \"Tango (https://tinted-theming.github.io/tinted-gallery/#base16-tango)\",\n          \"type\": \"string\",\n          \"const\": \"Tango\"\n        },\n        {\n          \"description\": \"Tarot (https://tinted-theming.github.io/tinted-gallery/#base16-tarot)\",\n          \"type\": \"string\",\n          \"const\": \"Tarot\"\n        },\n        {\n          \"description\": \"Tender (https://tinted-theming.github.io/tinted-gallery/#base16-tender)\",\n          \"type\": \"string\",\n          \"const\": \"Tender\"\n        },\n        {\n          \"description\": \"Terracotta Dark (https://tinted-theming.github.io/tinted-gallery/#base16-terracotta-dark)\",\n          \"type\": \"string\",\n          \"const\": \"TerracottaDark\"\n        },\n        {\n          \"description\": \"Terracotta (https://tinted-theming.github.io/tinted-gallery/#base16-terracotta)\",\n          \"type\": \"string\",\n          \"const\": \"Terracotta\"\n        },\n        {\n          \"description\": \"Tokyo City Dark (https://tinted-theming.github.io/tinted-gallery/#base16-tokyo-city-dark)\",\n          \"type\": \"string\",\n          \"const\": \"TokyoCityDark\"\n        },\n        {\n          \"description\": \"Tokyo City Light (https://tinted-theming.github.io/tinted-gallery/#base16-tokyo-city-light)\",\n          \"type\": \"string\",\n          \"const\": \"TokyoCityLight\"\n        },\n        {\n          \"description\": \"Tokyo City Terminal Dark (https://tinted-theming.github.io/tinted-gallery/#base16-tokyo-city-terminal-dark)\",\n          \"type\": \"string\",\n          \"const\": \"TokyoCityTerminalDark\"\n        },\n        {\n          \"description\": \"Tokyo City Terminal Light (https://tinted-theming.github.io/tinted-gallery/#base16-tokyo-city-terminal-light)\",\n          \"type\": \"string\",\n          \"const\": \"TokyoCityTerminalLight\"\n        },\n        {\n          \"description\": \"Tokyo Night Dark (https://tinted-theming.github.io/tinted-gallery/#base16-tokyo-night-dark)\",\n          \"type\": \"string\",\n          \"const\": \"TokyoNightDark\"\n        },\n        {\n          \"description\": \"Tokyo Night Light (https://tinted-theming.github.io/tinted-gallery/#base16-tokyo-night-light)\",\n          \"type\": \"string\",\n          \"const\": \"TokyoNightLight\"\n        },\n        {\n          \"description\": \"Tokyo Night Moon (https://tinted-theming.github.io/tinted-gallery/#base16-tokyo-night-moon)\",\n          \"type\": \"string\",\n          \"const\": \"TokyoNightMoon\"\n        },\n        {\n          \"description\": \"Tokyo Night Storm (https://tinted-theming.github.io/tinted-gallery/#base16-tokyo-night-storm)\",\n          \"type\": \"string\",\n          \"const\": \"TokyoNightStorm\"\n        },\n        {\n          \"description\": \"Tokyo Night Terminal Dark (https://tinted-theming.github.io/tinted-gallery/#base16-tokyo-night-terminal-dark)\",\n          \"type\": \"string\",\n          \"const\": \"TokyoNightTerminalDark\"\n        },\n        {\n          \"description\": \"Tokyo Night Terminal Light (https://tinted-theming.github.io/tinted-gallery/#base16-tokyo-night-terminal-light)\",\n          \"type\": \"string\",\n          \"const\": \"TokyoNightTerminalLight\"\n        },\n        {\n          \"description\": \"Tokyo Night Terminal Storm (https://tinted-theming.github.io/tinted-gallery/#base16-tokyo-night-terminal-storm)\",\n          \"type\": \"string\",\n          \"const\": \"TokyoNightTerminalStorm\"\n        },\n        {\n          \"description\": \"Tokyodark Terminal (https://tinted-theming.github.io/tinted-gallery/#base16-tokyodark-terminal)\",\n          \"type\": \"string\",\n          \"const\": \"TokyodarkTerminal\"\n        },\n        {\n          \"description\": \"Tokyodark (https://tinted-theming.github.io/tinted-gallery/#base16-tokyodark)\",\n          \"type\": \"string\",\n          \"const\": \"Tokyodark\"\n        },\n        {\n          \"description\": \"Tomorrow Night Eighties (https://tinted-theming.github.io/tinted-gallery/#base16-tomorrow-night-eighties)\",\n          \"type\": \"string\",\n          \"const\": \"TomorrowNightEighties\"\n        },\n        {\n          \"description\": \"Tomorrow Night (https://tinted-theming.github.io/tinted-gallery/#base16-tomorrow-night)\",\n          \"type\": \"string\",\n          \"const\": \"TomorrowNight\"\n        },\n        {\n          \"description\": \"Tomorrow (https://tinted-theming.github.io/tinted-gallery/#base16-tomorrow)\",\n          \"type\": \"string\",\n          \"const\": \"Tomorrow\"\n        },\n        {\n          \"description\": \"Tube (https://tinted-theming.github.io/tinted-gallery/#base16-tube)\",\n          \"type\": \"string\",\n          \"const\": \"Tube\"\n        },\n        {\n          \"description\": \"Twilight (https://tinted-theming.github.io/tinted-gallery/#base16-twilight)\",\n          \"type\": \"string\",\n          \"const\": \"Twilight\"\n        },\n        {\n          \"description\": \"Unikitty Dark (https://tinted-theming.github.io/tinted-gallery/#base16-unikitty-dark)\",\n          \"type\": \"string\",\n          \"const\": \"UnikittyDark\"\n        },\n        {\n          \"description\": \"Unikitty Light (https://tinted-theming.github.io/tinted-gallery/#base16-unikitty-light)\",\n          \"type\": \"string\",\n          \"const\": \"UnikittyLight\"\n        },\n        {\n          \"description\": \"Unikitty Reversible (https://tinted-theming.github.io/tinted-gallery/#base16-unikitty-reversible)\",\n          \"type\": \"string\",\n          \"const\": \"UnikittyReversible\"\n        },\n        {\n          \"description\": \"Uwunicorn (https://tinted-theming.github.io/tinted-gallery/#base16-uwunicorn)\",\n          \"type\": \"string\",\n          \"const\": \"Uwunicorn\"\n        },\n        {\n          \"description\": \"Vesper (https://tinted-theming.github.io/tinted-gallery/#base16-vesper)\",\n          \"type\": \"string\",\n          \"const\": \"Vesper\"\n        },\n        {\n          \"description\": \"Vice (https://tinted-theming.github.io/tinted-gallery/#base16-vice)\",\n          \"type\": \"string\",\n          \"const\": \"Vice\"\n        },\n        {\n          \"description\": \"Vulcan (https://tinted-theming.github.io/tinted-gallery/#base16-vulcan)\",\n          \"type\": \"string\",\n          \"const\": \"Vulcan\"\n        },\n        {\n          \"description\": \"Windows 10 Light (https://tinted-theming.github.io/tinted-gallery/#base16-windows-10-light)\",\n          \"type\": \"string\",\n          \"const\": \"Windows10Light\"\n        },\n        {\n          \"description\": \"Windows 10 (https://tinted-theming.github.io/tinted-gallery/#base16-windows-10)\",\n          \"type\": \"string\",\n          \"const\": \"Windows10\"\n        },\n        {\n          \"description\": \"Windows 95 Light (https://tinted-theming.github.io/tinted-gallery/#base16-windows-95-light)\",\n          \"type\": \"string\",\n          \"const\": \"Windows95Light\"\n        },\n        {\n          \"description\": \"Windows 95 (https://tinted-theming.github.io/tinted-gallery/#base16-windows-95)\",\n          \"type\": \"string\",\n          \"const\": \"Windows95\"\n        },\n        {\n          \"description\": \"Windows Highcontrast Light (https://tinted-theming.github.io/tinted-gallery/#base16-windows-highcontrast-light)\",\n          \"type\": \"string\",\n          \"const\": \"WindowsHighcontrastLight\"\n        },\n        {\n          \"description\": \"Windows Highcontrast (https://tinted-theming.github.io/tinted-gallery/#base16-windows-highcontrast)\",\n          \"type\": \"string\",\n          \"const\": \"WindowsHighcontrast\"\n        },\n        {\n          \"description\": \"Windows Nt Light (https://tinted-theming.github.io/tinted-gallery/#base16-windows-nt-light)\",\n          \"type\": \"string\",\n          \"const\": \"WindowsNtLight\"\n        },\n        {\n          \"description\": \"Windows Nt (https://tinted-theming.github.io/tinted-gallery/#base16-windows-nt)\",\n          \"type\": \"string\",\n          \"const\": \"WindowsNt\"\n        },\n        {\n          \"description\": \"Woodland (https://tinted-theming.github.io/tinted-gallery/#base16-woodland)\",\n          \"type\": \"string\",\n          \"const\": \"Woodland\"\n        },\n        {\n          \"description\": \"Xcode Dusk (https://tinted-theming.github.io/tinted-gallery/#base16-xcode-dusk)\",\n          \"type\": \"string\",\n          \"const\": \"XcodeDusk\"\n        },\n        {\n          \"description\": \"Zenbones (https://tinted-theming.github.io/tinted-gallery/#base16-zenbones)\",\n          \"type\": \"string\",\n          \"const\": \"Zenbones\"\n        },\n        {\n          \"description\": \"Zenburn (https://tinted-theming.github.io/tinted-gallery/#base16-zenburn)\",\n          \"type\": \"string\",\n          \"const\": \"Zenburn\"\n        }\n      ]\n    },\n    \"Base16ColourPalette\": {\n      \"description\": \"Base16 colour palette: https://github.com/chriskempson/base16\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"base_00\": {\n          \"description\": \"Base00\",\n          \"$ref\": \"#/$defs/Colour\"\n        },\n        \"base_01\": {\n          \"description\": \"Base01\",\n          \"$ref\": \"#/$defs/Colour\"\n        },\n        \"base_02\": {\n          \"description\": \"Base02\",\n          \"$ref\": \"#/$defs/Colour\"\n        },\n        \"base_03\": {\n          \"description\": \"Base03\",\n          \"$ref\": \"#/$defs/Colour\"\n        },\n        \"base_04\": {\n          \"description\": \"Base04\",\n          \"$ref\": \"#/$defs/Colour\"\n        },\n        \"base_05\": {\n          \"description\": \"Base05\",\n          \"$ref\": \"#/$defs/Colour\"\n        },\n        \"base_06\": {\n          \"description\": \"Base06\",\n          \"$ref\": \"#/$defs/Colour\"\n        },\n        \"base_07\": {\n          \"description\": \"Base07\",\n          \"$ref\": \"#/$defs/Colour\"\n        },\n        \"base_08\": {\n          \"description\": \"Base08\",\n          \"$ref\": \"#/$defs/Colour\"\n        },\n        \"base_09\": {\n          \"description\": \"Base09\",\n          \"$ref\": \"#/$defs/Colour\"\n        },\n        \"base_0a\": {\n          \"description\": \"Base0A\",\n          \"$ref\": \"#/$defs/Colour\"\n        },\n        \"base_0b\": {\n          \"description\": \"Base0B\",\n          \"$ref\": \"#/$defs/Colour\"\n        },\n        \"base_0c\": {\n          \"description\": \"Base0C\",\n          \"$ref\": \"#/$defs/Colour\"\n        },\n        \"base_0d\": {\n          \"description\": \"Base0D\",\n          \"$ref\": \"#/$defs/Colour\"\n        },\n        \"base_0e\": {\n          \"description\": \"Base0E\",\n          \"$ref\": \"#/$defs/Colour\"\n        },\n        \"base_0f\": {\n          \"description\": \"Base0F\",\n          \"$ref\": \"#/$defs/Colour\"\n        }\n      },\n      \"required\": [\n        \"base_00\",\n        \"base_01\",\n        \"base_02\",\n        \"base_03\",\n        \"base_04\",\n        \"base_05\",\n        \"base_06\",\n        \"base_07\",\n        \"base_08\",\n        \"base_09\",\n        \"base_0a\",\n        \"base_0b\",\n        \"base_0c\",\n        \"base_0d\",\n        \"base_0e\",\n        \"base_0f\"\n      ]\n    },\n    \"Base16Value\": {\n      \"description\": \"Base16 value\",\n      \"oneOf\": [\n        {\n          \"description\": \"Base00\",\n          \"type\": \"string\",\n          \"const\": \"Base00\"\n        },\n        {\n          \"description\": \"Base01\",\n          \"type\": \"string\",\n          \"const\": \"Base01\"\n        },\n        {\n          \"description\": \"Base02\",\n          \"type\": \"string\",\n          \"const\": \"Base02\"\n        },\n        {\n          \"description\": \"Base03\",\n          \"type\": \"string\",\n          \"const\": \"Base03\"\n        },\n        {\n          \"description\": \"Base04\",\n          \"type\": \"string\",\n          \"const\": \"Base04\"\n        },\n        {\n          \"description\": \"Base05\",\n          \"type\": \"string\",\n          \"const\": \"Base05\"\n        },\n        {\n          \"description\": \"Base06\",\n          \"type\": \"string\",\n          \"const\": \"Base06\"\n        },\n        {\n          \"description\": \"Base07\",\n          \"type\": \"string\",\n          \"const\": \"Base07\"\n        },\n        {\n          \"description\": \"Base08\",\n          \"type\": \"string\",\n          \"const\": \"Base08\"\n        },\n        {\n          \"description\": \"Base09\",\n          \"type\": \"string\",\n          \"const\": \"Base09\"\n        },\n        {\n          \"description\": \"Base0A\",\n          \"type\": \"string\",\n          \"const\": \"Base0A\"\n        },\n        {\n          \"description\": \"Base0B\",\n          \"type\": \"string\",\n          \"const\": \"Base0B\"\n        },\n        {\n          \"description\": \"Base0C\",\n          \"type\": \"string\",\n          \"const\": \"Base0C\"\n        },\n        {\n          \"description\": \"Base0D\",\n          \"type\": \"string\",\n          \"const\": \"Base0D\"\n        },\n        {\n          \"description\": \"Base0E\",\n          \"type\": \"string\",\n          \"const\": \"Base0E\"\n        },\n        {\n          \"description\": \"Base0F\",\n          \"type\": \"string\",\n          \"const\": \"Base0F\"\n        }\n      ]\n    },\n    \"BatteryConfig\": {\n      \"description\": \"Battery widget configuration\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"auto_select_under\": {\n          \"description\": \"Select when the current percentage is under this value [[1-100]]\",\n          \"type\": [\n            \"integer\",\n            \"null\"\n          ],\n          \"format\": \"uint8\",\n          \"maximum\": 255,\n          \"minimum\": 0\n        },\n        \"data_refresh_interval\": {\n          \"description\": \"Data refresh interval in seconds\",\n          \"type\": [\n            \"integer\",\n            \"null\"\n          ],\n          \"format\": \"uint64\",\n          \"default\": 10,\n          \"minimum\": 0\n        },\n        \"enable\": {\n          \"description\": \"Enable the Battery widget\",\n          \"type\": \"boolean\"\n        },\n        \"hide_on_full_charge\": {\n          \"description\": \"Hide the widget if the battery is at full charge\",\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ]\n        },\n        \"label_prefix\": {\n          \"description\": \"Display label prefix\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/LabelPrefix\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        }\n      },\n      \"required\": [\n        \"enable\"\n      ]\n    },\n    \"BorderImplementation\": {\n      \"description\": \"Border style\",\n      \"oneOf\": [\n        {\n          \"description\": \"Use the adjustable komorebi border implementation\",\n          \"type\": \"string\",\n          \"const\": \"Komorebi\"\n        },\n        {\n          \"description\": \"Use the thin Windows accent border implementation\",\n          \"type\": \"string\",\n          \"const\": \"Windows\"\n        }\n      ]\n    },\n    \"BorderStyle\": {\n      \"description\": \"Border style\",\n      \"oneOf\": [\n        {\n          \"description\": \"Use the system border style\",\n          \"type\": \"string\",\n          \"const\": \"System\"\n        },\n        {\n          \"description\": \"Use the Windows 11-style rounded borders\",\n          \"type\": \"string\",\n          \"const\": \"Rounded\"\n        },\n        {\n          \"description\": \"Use the Windows 10-style square borders\",\n          \"type\": \"string\",\n          \"const\": \"Square\"\n        }\n      ]\n    },\n    \"Catppuccin\": {\n      \"description\": \"Catppuccin palette\",\n      \"oneOf\": [\n        {\n          \"description\": \"Frappe (https://catppuccin.com/palette#flavor-frappe)\",\n          \"type\": \"string\",\n          \"const\": \"Frappe\"\n        },\n        {\n          \"description\": \"Latte (https://catppuccin.com/palette#flavor-latte)\",\n          \"type\": \"string\",\n          \"const\": \"Latte\"\n        },\n        {\n          \"description\": \"Macchiato (https://catppuccin.com/palette#flavor-macchiato)\",\n          \"type\": \"string\",\n          \"const\": \"Macchiato\"\n        },\n        {\n          \"description\": \"Mocha (https://catppuccin.com/palette#flavor-mocha)\",\n          \"type\": \"string\",\n          \"const\": \"Mocha\"\n        }\n      ]\n    },\n    \"CatppuccinValue\": {\n      \"description\": \"Catppuccin Value\",\n      \"oneOf\": [\n        {\n          \"description\": \"Rosewater\",\n          \"type\": \"string\",\n          \"const\": \"Rosewater\"\n        },\n        {\n          \"description\": \"Flamingo\",\n          \"type\": \"string\",\n          \"const\": \"Flamingo\"\n        },\n        {\n          \"description\": \"Pink\",\n          \"type\": \"string\",\n          \"const\": \"Pink\"\n        },\n        {\n          \"description\": \"Mauve\",\n          \"type\": \"string\",\n          \"const\": \"Mauve\"\n        },\n        {\n          \"description\": \"Red\",\n          \"type\": \"string\",\n          \"const\": \"Red\"\n        },\n        {\n          \"description\": \"Maroon\",\n          \"type\": \"string\",\n          \"const\": \"Maroon\"\n        },\n        {\n          \"description\": \"Peach\",\n          \"type\": \"string\",\n          \"const\": \"Peach\"\n        },\n        {\n          \"description\": \"Yellow\",\n          \"type\": \"string\",\n          \"const\": \"Yellow\"\n        },\n        {\n          \"description\": \"Green\",\n          \"type\": \"string\",\n          \"const\": \"Green\"\n        },\n        {\n          \"description\": \"Teal\",\n          \"type\": \"string\",\n          \"const\": \"Teal\"\n        },\n        {\n          \"description\": \"Sky\",\n          \"type\": \"string\",\n          \"const\": \"Sky\"\n        },\n        {\n          \"description\": \"Sapphire\",\n          \"type\": \"string\",\n          \"const\": \"Sapphire\"\n        },\n        {\n          \"description\": \"Blue\",\n          \"type\": \"string\",\n          \"const\": \"Blue\"\n        },\n        {\n          \"description\": \"Lavender\",\n          \"type\": \"string\",\n          \"const\": \"Lavender\"\n        },\n        {\n          \"description\": \"Text\",\n          \"type\": \"string\",\n          \"const\": \"Text\"\n        },\n        {\n          \"description\": \"Subtext1\",\n          \"type\": \"string\",\n          \"const\": \"Subtext1\"\n        },\n        {\n          \"description\": \"Subtext0\",\n          \"type\": \"string\",\n          \"const\": \"Subtext0\"\n        },\n        {\n          \"description\": \"Overlay2\",\n          \"type\": \"string\",\n          \"const\": \"Overlay2\"\n        },\n        {\n          \"description\": \"Overlay1\",\n          \"type\": \"string\",\n          \"const\": \"Overlay1\"\n        },\n        {\n          \"description\": \"Overlay0\",\n          \"type\": \"string\",\n          \"const\": \"Overlay0\"\n        },\n        {\n          \"description\": \"Surface2\",\n          \"type\": \"string\",\n          \"const\": \"Surface2\"\n        },\n        {\n          \"description\": \"Surface1\",\n          \"type\": \"string\",\n          \"const\": \"Surface1\"\n        },\n        {\n          \"description\": \"Surface0\",\n          \"type\": \"string\",\n          \"const\": \"Surface0\"\n        },\n        {\n          \"description\": \"Base\",\n          \"type\": \"string\",\n          \"const\": \"Base\"\n        },\n        {\n          \"description\": \"Mantle\",\n          \"type\": \"string\",\n          \"const\": \"Mantle\"\n        },\n        {\n          \"description\": \"Crust\",\n          \"type\": \"string\",\n          \"const\": \"Crust\"\n        }\n      ]\n    },\n    \"Colour\": {\n      \"description\": \"Colour representation\",\n      \"anyOf\": [\n        {\n          \"description\": \"Colour represented as RGB\",\n          \"$ref\": \"#/$defs/Rgb\"\n        },\n        {\n          \"description\": \"Colour represented as Hex\",\n          \"$ref\": \"#/$defs/Hex\"\n        }\n      ]\n    },\n    \"CpuConfig\": {\n      \"description\": \"CPU widget configuration\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"auto_select_over\": {\n          \"description\": \"Select when the current percentage is over this value [[1-100]]\",\n          \"type\": [\n            \"integer\",\n            \"null\"\n          ],\n          \"format\": \"uint8\",\n          \"maximum\": 255,\n          \"minimum\": 0\n        },\n        \"data_refresh_interval\": {\n          \"description\": \"Data refresh interval in seconds\",\n          \"type\": [\n            \"integer\",\n            \"null\"\n          ],\n          \"format\": \"uint64\",\n          \"default\": 10,\n          \"minimum\": 0\n        },\n        \"enable\": {\n          \"description\": \"Enable the Cpu widget\",\n          \"type\": \"boolean\"\n        },\n        \"label_prefix\": {\n          \"description\": \"Display label prefix\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/LabelPrefix\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        }\n      },\n      \"required\": [\n        \"enable\"\n      ]\n    },\n    \"CustomModifiers\": {\n      \"description\": \"Custom format with additive modifiers for integer format specifiers\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"format\": {\n          \"description\": \"Custom format (https://docs.rs/chrono/latest/chrono/format/strftime/index.html)\",\n          \"type\": \"string\"\n        },\n        \"modifiers\": {\n          \"description\": \"Additive modifiers for integer format specifiers (e.g. { \\\"%U\\\": 1 } to increment the zero-indexed week number by 1)\",\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          }\n        }\n      },\n      \"required\": [\n        \"format\",\n        \"modifiers\"\n      ]\n    },\n    \"CycleDirection\": {\n      \"type\": \"string\",\n      \"enum\": [\n        \"Previous\",\n        \"Next\"\n      ]\n    },\n    \"DateConfig\": {\n      \"description\": \"Date widget configuration\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"enable\": {\n          \"description\": \"Enable the Date widget\",\n          \"type\": \"boolean\"\n        },\n        \"format\": {\n          \"description\": \"Set the Date format\",\n          \"$ref\": \"#/$defs/DateFormat\"\n        },\n        \"label_prefix\": {\n          \"description\": \"Display label prefix\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/LabelPrefix\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"timezone\": {\n          \"description\": \"TimeZone (https://docs.rs/chrono-tz/latest/chrono_tz/enum.Tz.html)\\n\\nUse a custom format to display additional information, i.e.:\\n```json\\n{\\n    \\\"Date\\\": {\\n        \\\"enable\\\": true,\\n        \\\"format\\\": { \\\"Custom\\\": \\\"%D %Z (Tokyo)\\\" },\\n        \\\"timezone\\\": \\\"Asia/Tokyo\\\"\\n     }\\n}\\n```\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        }\n      },\n      \"required\": [\n        \"enable\",\n        \"format\"\n      ]\n    },\n    \"DateFormat\": {\n      \"description\": \"Date widget format\",\n      \"oneOf\": [\n        {\n          \"description\": \"Month/Date/Year format (09/08/24)\",\n          \"type\": \"string\",\n          \"const\": \"MonthDateYear\"\n        },\n        {\n          \"description\": \"Year-Month-Date format (2024-09-08)\",\n          \"type\": \"string\",\n          \"const\": \"YearMonthDate\"\n        },\n        {\n          \"description\": \"Date-Month-Year format (8-Sep-2024)\",\n          \"type\": \"string\",\n          \"const\": \"DateMonthYear\"\n        },\n        {\n          \"description\": \"Day Date Month Year format (8 September 2024)\",\n          \"type\": \"string\",\n          \"const\": \"DayDateMonthYear\"\n        },\n        {\n          \"title\": \"Custom\",\n          \"description\": \"Custom format (https://docs.rs/chrono/latest/chrono/format/strftime/index.html)\",\n          \"type\": \"object\",\n          \"properties\": {\n            \"Custom\": {\n              \"type\": \"string\"\n            }\n          },\n          \"additionalProperties\": false,\n          \"required\": [\n            \"Custom\"\n          ]\n        },\n        {\n          \"title\": \"CustomModifiers\",\n          \"description\": \"Custom format with modifiers\",\n          \"type\": \"object\",\n          \"properties\": {\n            \"CustomModifiers\": {\n              \"$ref\": \"#/$defs/CustomModifiers\"\n            }\n          },\n          \"additionalProperties\": false,\n          \"required\": [\n            \"CustomModifiers\"\n          ]\n        }\n      ]\n    },\n    \"DefaultLayout\": {\n      \"description\": \"A predefined komorebi layout\",\n      \"oneOf\": [\n        {\n          \"description\": \"BSP Layout\\n\\n```text\\n+-------+-----+\\n|       |     |\\n|       +--+--+\\n|       |  |--|\\n+-------+--+--+\\n```\",\n          \"type\": \"string\",\n          \"const\": \"BSP\"\n        },\n        {\n          \"description\": \"Columns Layout\\n\\n```text\\n+--+--+--+--+\\n|  |  |  |  |\\n|  |  |  |  |\\n|  |  |  |  |\\n+--+--+--+--+\\n```\",\n          \"type\": \"string\",\n          \"const\": \"Columns\"\n        },\n        {\n          \"description\": \"Rows Layout\\n\\n```text\\n+-----------+\\n|-----------|\\n|-----------|\\n|-----------|\\n+-----------+\\n```\",\n          \"type\": \"string\",\n          \"const\": \"Rows\"\n        },\n        {\n          \"description\": \"Vertical Stack Layout\\n\\n```text\\n+-------+-----+\\n|       |     |\\n|       +-----+\\n|       |     |\\n+-------+-----+\\n```\",\n          \"type\": \"string\",\n          \"const\": \"VerticalStack\"\n        },\n        {\n          \"description\": \"Horizontal Stack Layout\\n\\n```text\\n+------+------+\\n|             |\\n|------+------+\\n|      |      |\\n+------+------+\\n```\",\n          \"type\": \"string\",\n          \"const\": \"HorizontalStack\"\n        },\n        {\n          \"description\": \"Ultrawide Vertical Stack Layout\\n\\n```text\\n+-----+-----------+-----+\\n|     |           |     |\\n|     |           +-----+\\n|     |           |     |\\n|     |           +-----+\\n|     |           |     |\\n+-----+-----------+-----+\\n```\",\n          \"type\": \"string\",\n          \"const\": \"UltrawideVerticalStack\"\n        },\n        {\n          \"description\": \"Grid Layout\\n\\n```text\\n+-----+-----+   +---+---+---+   +---+---+---+   +---+---+---+\\n|     |     |   |   |   |   |   |   |   |   |   |   |   |   |\\n|     |     |   |   |   |   |   |   |   |   |   |   |   +---+\\n+-----+-----+   |   +---+---+   +---+---+---+   +---+---|   |\\n|     |     |   |   |   |   |   |   |   |   |   |   |   +---+\\n|     |     |   |   |   |   |   |   |   |   |   |   |   |   |\\n+-----+-----+   +---+---+---+   +---+---+---+   +---+---+---+\\n  4 windows       5 windows       6 windows       7 windows\\n```\",\n          \"type\": \"string\",\n          \"const\": \"Grid\"\n        },\n        {\n          \"description\": \"Right Main Vertical Stack Layout\\n\\n```text\\n+-----+-------+\\n|     |       |\\n+-----+       |\\n|     |       |\\n+-----+-------+\\n```\",\n          \"type\": \"string\",\n          \"const\": \"RightMainVerticalStack\"\n        },\n        {\n          \"description\": \"Scrolling Layout\\n\\n```text\\n+--+--+--+--+--+--+\\n|     |     |     |\\n|     |     |     |\\n|     |     |     |\\n+--+--+--+--+--+--+\\n```\",\n          \"type\": \"string\",\n          \"const\": \"Scrolling\"\n        }\n      ]\n    },\n    \"DisplayFormat\": {\n      \"description\": \"Display format\",\n      \"oneOf\": [\n        {\n          \"description\": \"Show only icon\",\n          \"type\": \"string\",\n          \"const\": \"Icon\"\n        },\n        {\n          \"description\": \"Show only text\",\n          \"type\": \"string\",\n          \"const\": \"Text\"\n        },\n        {\n          \"description\": \"Show an icon and text for the selected element, and text on the rest\",\n          \"type\": \"string\",\n          \"const\": \"TextAndIconOnSelected\"\n        },\n        {\n          \"description\": \"Show both icon and text\",\n          \"type\": \"string\",\n          \"const\": \"IconAndText\"\n        },\n        {\n          \"description\": \"Show an icon and text for the selected element, and icons on the rest\",\n          \"type\": \"string\",\n          \"const\": \"IconAndTextOnSelected\"\n        }\n      ]\n    },\n    \"FocusFollowsMouseImplementation\": {\n      \"description\": \"Focus follows mouse implementation\",\n      \"oneOf\": [\n        {\n          \"description\": \"Custom FFM implementation (slightly more CPU-intensive)\",\n          \"type\": \"string\",\n          \"const\": \"Komorebi\"\n        },\n        {\n          \"description\": \"Native (legacy) Windows FFM implementation\",\n          \"type\": \"string\",\n          \"const\": \"Windows\"\n        }\n      ]\n    },\n    \"FrameConfig\": {\n      \"description\": \"Frame configuration\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"inner_margin\": {\n          \"description\": \"Margin inside the painted frame\",\n          \"$ref\": \"#/$defs/Position\"\n        }\n      },\n      \"required\": [\n        \"inner_margin\"\n      ]\n    },\n    \"GroupedSpacingConfig\": {\n      \"description\": \"Grouped vertical and horizontal spacing\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"horizontal\": {\n          \"description\": \"Horizontal grouped spacing\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/GroupedSpacingOptions\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"vertical\": {\n          \"description\": \"Vertical grouped spacing\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/GroupedSpacingOptions\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        }\n      }\n    },\n    \"GroupedSpacingOptions\": {\n      \"description\": \"Grouped spacing options\",\n      \"anyOf\": [\n        {\n          \"description\": \"Symmetrical grouped spacing\",\n          \"type\": \"number\",\n          \"format\": \"float\"\n        },\n        {\n          \"description\": \"Split grouped spacing\",\n          \"type\": \"array\",\n          \"maxItems\": 2,\n          \"minItems\": 2,\n          \"prefixItems\": [\n            {\n              \"type\": \"number\",\n              \"format\": \"float\"\n            },\n            {\n              \"type\": \"number\",\n              \"format\": \"float\"\n            }\n          ]\n        }\n      ]\n    },\n    \"Grouping\": {\n      \"description\": \"Grouping\",\n      \"oneOf\": [\n        {\n          \"title\": \"None\",\n          \"description\": \"No grouping is applied\",\n          \"type\": \"object\",\n          \"properties\": {\n            \"kind\": {\n              \"type\": \"string\",\n              \"const\": \"None\"\n            }\n          },\n          \"required\": [\n            \"kind\"\n          ]\n        },\n        {\n          \"title\": \"Bar\",\n          \"description\": \"Widgets are grouped as a whole\",\n          \"type\": \"object\",\n          \"properties\": {\n            \"kind\": {\n              \"type\": \"string\",\n              \"const\": \"Bar\"\n            }\n          },\n          \"$ref\": \"#/$defs/GroupingConfig\",\n          \"required\": [\n            \"kind\"\n          ]\n        },\n        {\n          \"title\": \"Alignment\",\n          \"description\": \"Widgets are grouped by alignment\",\n          \"type\": \"object\",\n          \"properties\": {\n            \"kind\": {\n              \"type\": \"string\",\n              \"const\": \"Alignment\"\n            }\n          },\n          \"$ref\": \"#/$defs/GroupingConfig\",\n          \"required\": [\n            \"kind\"\n          ]\n        },\n        {\n          \"title\": \"Widget\",\n          \"description\": \"Widgets are grouped individually\",\n          \"type\": \"object\",\n          \"properties\": {\n            \"kind\": {\n              \"type\": \"string\",\n              \"const\": \"Widget\"\n            }\n          },\n          \"$ref\": \"#/$defs/GroupingConfig\",\n          \"required\": [\n            \"kind\"\n          ]\n        }\n      ]\n    },\n    \"GroupingConfig\": {\n      \"description\": \"Grouping configuration\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"rounding\": {\n          \"description\": \"Rounding values for the 4 corners. Can be a single or 4 values.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/RoundingConfig\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"style\": {\n          \"description\": \"Styles for the grouping\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/GroupingStyle\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"transparency_alpha\": {\n          \"description\": \"Alpha value for the color transparency [[0-255]] (default: 200)\",\n          \"type\": [\n            \"integer\",\n            \"null\"\n          ],\n          \"format\": \"uint8\",\n          \"maximum\": 255,\n          \"minimum\": 0\n        }\n      }\n    },\n    \"GroupingStyle\": {\n      \"description\": \"Grouping Style\",\n      \"oneOf\": [\n        {\n          \"description\": \"Default\",\n          \"type\": \"string\",\n          \"const\": \"Default\"\n        },\n        {\n          \"description\": \"A shadow is added under the default group. (blur: 4, offset: x-1 y-1, spread: 3)\",\n          \"type\": \"string\",\n          \"const\": \"DefaultWithShadowB4O1S3\"\n        },\n        {\n          \"description\": \"A shadow is added under the default group. (blur: 4, offset: x-0 y-0, spread: 3)\",\n          \"type\": \"string\",\n          \"const\": \"DefaultWithShadowB4O0S3\"\n        },\n        {\n          \"description\": \"A shadow is added under the default group. (blur: 0, offset: x-1 y-1, spread: 3)\",\n          \"type\": \"string\",\n          \"const\": \"DefaultWithShadowB0O1S3\"\n        },\n        {\n          \"description\": \"A glow is added under the default group. (blur: 3, offset: x-1 y-1, spread: 2)\",\n          \"type\": \"string\",\n          \"const\": \"DefaultWithGlowB3O1S2\"\n        },\n        {\n          \"description\": \"A glow is added under the default group. (blur: 3, offset: x-0 y-0, spread: 2)\",\n          \"type\": \"string\",\n          \"const\": \"DefaultWithGlowB3O0S2\"\n        },\n        {\n          \"description\": \"A glow is added under the default group. (blur: 0, offset: x-1 y-1, spread: 2)\",\n          \"type\": \"string\",\n          \"const\": \"DefaultWithGlowB0O1S2\"\n        }\n      ]\n    },\n    \"Hex\": {\n      \"description\": \"Colour represented as a Hex string\",\n      \"type\": \"string\",\n      \"format\": \"color-hex\"\n    },\n    \"HidingBehaviour\": {\n      \"description\": \"Window hiding behaviour\",\n      \"oneOf\": [\n        {\n          \"description\": \"END OF LIFE FEATURE: Use the `SW_HIDE` flag to hide windows when switching workspaces (has issues with Electron apps)\",\n          \"type\": \"string\",\n          \"const\": \"Hide\",\n          \"deprecated\": true\n        },\n        {\n          \"description\": \"Use the `SW_MINIMIZE` flag to hide windows when switching workspaces (has issues with frequent workspace switching)\",\n          \"type\": \"string\",\n          \"const\": \"Minimize\"\n        },\n        {\n          \"description\": \"Use the undocumented SetCloak Win32 function to hide windows when switching workspaces\",\n          \"type\": \"string\",\n          \"const\": \"Cloak\"\n        }\n      ]\n    },\n    \"IndividualSpacingConfig\": {\n      \"description\": \"Individual spacing configuration\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"bottom\": {\n          \"description\": \"Spacing for the bottom\",\n          \"type\": \"number\",\n          \"format\": \"float\"\n        },\n        \"left\": {\n          \"description\": \"Spacing for the left\",\n          \"type\": \"number\",\n          \"format\": \"float\"\n        },\n        \"right\": {\n          \"description\": \"Spacing for the right\",\n          \"type\": \"number\",\n          \"format\": \"float\"\n        },\n        \"top\": {\n          \"description\": \"Spacing for the top\",\n          \"type\": \"number\",\n          \"format\": \"float\"\n        }\n      },\n      \"required\": [\n        \"top\",\n        \"bottom\",\n        \"left\",\n        \"right\"\n      ]\n    },\n    \"KeyboardConfig\": {\n      \"description\": \"Keyboard widget configuration\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"data_refresh_interval\": {\n          \"description\": \"Data refresh interval\",\n          \"type\": [\n            \"integer\",\n            \"null\"\n          ],\n          \"format\": \"uint64\",\n          \"default\": 10,\n          \"minimum\": 0\n        },\n        \"enable\": {\n          \"description\": \"Enable the Input widget\",\n          \"type\": \"boolean\"\n        },\n        \"label_prefix\": {\n          \"description\": \"Display label prefix\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/LabelPrefix\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        }\n      },\n      \"required\": [\n        \"enable\"\n      ]\n    },\n    \"KomobarTheme\": {\n      \"description\": \"Komorebi bar theme\",\n      \"oneOf\": [\n        {\n          \"title\": \"Catppuccin\",\n          \"description\": \"Theme from catppuccin-egui\",\n          \"type\": \"object\",\n          \"properties\": {\n            \"palette\": {\n              \"type\": \"string\",\n              \"const\": \"Catppuccin\"\n            }\n          },\n          \"$ref\": \"#/$defs/KomobarThemeCatppuccin\",\n          \"required\": [\n            \"palette\"\n          ]\n        },\n        {\n          \"title\": \"Base16\",\n          \"description\": \"Theme from base16-egui-themes\",\n          \"type\": \"object\",\n          \"properties\": {\n            \"palette\": {\n              \"type\": \"string\",\n              \"const\": \"Base16\"\n            }\n          },\n          \"$ref\": \"#/$defs/KomobarThemeBase16\",\n          \"required\": [\n            \"palette\"\n          ]\n        },\n        {\n          \"title\": \"Custom\",\n          \"description\": \"Custom Base16 theme\",\n          \"type\": \"object\",\n          \"properties\": {\n            \"palette\": {\n              \"type\": \"string\",\n              \"const\": \"Custom\"\n            }\n          },\n          \"$ref\": \"#/$defs/KomobarThemeCustom\",\n          \"required\": [\n            \"palette\"\n          ]\n        }\n      ]\n    },\n    \"KomobarThemeBase16\": {\n      \"description\": \"Theme from base16-egui-themes\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"accent\": {\n          \"description\": \"Accent colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base0D\"\n        },\n        \"auto_select_fill\": {\n          \"description\": \"Auto select fill colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"auto_select_text\": {\n          \"description\": \"Auto select text colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"name\": {\n          \"description\": \"Name of the Base16 theme (previews: https://tinted-theming.github.io/tinted-gallery/)\",\n          \"$ref\": \"#/$defs/Base16\"\n        }\n      },\n      \"required\": [\n        \"name\"\n      ]\n    },\n    \"KomobarThemeCatppuccin\": {\n      \"description\": \"Theme from catppuccin-egui\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"accent\": {\n          \"description\": \"Accent colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/CatppuccinValue\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Blue\"\n        },\n        \"auto_select_fill\": {\n          \"description\": \"Auto select fill colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/CatppuccinValue\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"auto_select_text\": {\n          \"description\": \"Auto select text colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/CatppuccinValue\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"name\": {\n          \"description\": \"Name of the Catppuccin theme (previews: https://github.com/catppuccin/catppuccin)\",\n          \"$ref\": \"#/$defs/Catppuccin\"\n        }\n      },\n      \"required\": [\n        \"name\"\n      ]\n    },\n    \"KomobarThemeCustom\": {\n      \"description\": \"Theme from base16-egui-themes\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"accent\": {\n          \"description\": \"Accent colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Blue\"\n        },\n        \"auto_select_fill\": {\n          \"description\": \"Auto select fill colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"auto_select_text\": {\n          \"description\": \"Auto select text colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"colours\": {\n          \"description\": \"Colours of the custom Base16 theme palette\",\n          \"$ref\": \"#/$defs/Base16ColourPalette\"\n        }\n      },\n      \"required\": [\n        \"colours\"\n      ]\n    },\n    \"KomorebiConfig\": {\n      \"description\": \"Komorebi widget configuration\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"configuration_switcher\": {\n          \"description\": \"Configure the Configuration Switcher widget\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/KomorebiConfigurationSwitcherConfig\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"focused_container\": {\n          \"description\": \"Configure the Focused Container widget\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/KomorebiFocusedContainerConfig\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"layout\": {\n          \"description\": \"Configure the Layout widget\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/KomorebiLayoutConfig\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"locked_container\": {\n          \"description\": \"Configure the Locked Container widget\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/KomorebiLockedContainerConfig\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"workspace_layer\": {\n          \"description\": \"Configure the Workspace Layer widget\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/KomorebiWorkspaceLayerConfig\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"workspaces\": {\n          \"description\": \"Configure the Workspaces widget\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/KomorebiWorkspacesConfig\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        }\n      }\n    },\n    \"KomorebiConfigurationSwitcherConfig\": {\n      \"description\": \"Komorebi widget configuration switcher configuration\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"configurations\": {\n          \"description\": \"A map of display friendly name => path to configuration.json\",\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        },\n        \"enable\": {\n          \"description\": \"Enable the Komorebi Configurations widget\",\n          \"type\": \"boolean\"\n        }\n      },\n      \"required\": [\n        \"enable\",\n        \"configurations\"\n      ]\n    },\n    \"KomorebiFocusedContainerConfig\": {\n      \"description\": \"Komorebi widget focused container configuration\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"display\": {\n          \"description\": \"Display format of the currently focused container\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/DisplayFormat\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"enable\": {\n          \"description\": \"Enable the Komorebi Focused Container widget\",\n          \"type\": \"boolean\"\n        },\n        \"show_icon\": {\n          \"description\": \"DEPRECATED: use `display` instead (Show the icon of the currently focused container)\",\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ],\n          \"deprecated\": true\n        }\n      },\n      \"required\": [\n        \"enable\"\n      ]\n    },\n    \"KomorebiLayout\": {\n      \"description\": \"Komorebi layout kind\",\n      \"anyOf\": [\n        {\n          \"title\": \"Default\",\n          \"description\": \"Predefined layout\",\n          \"$ref\": \"#/$defs/DefaultLayout\"\n        },\n        {\n          \"description\": \"Monocle mode\",\n          \"type\": \"null\"\n        },\n        {\n          \"description\": \"Floating layer\",\n          \"type\": \"null\"\n        },\n        {\n          \"description\": \"Paused\",\n          \"type\": \"null\"\n        },\n        {\n          \"description\": \"Custom layout\",\n          \"type\": \"null\"\n        }\n      ]\n    },\n    \"KomorebiLayoutConfig\": {\n      \"description\": \"Komorebi widget layout configuration\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"display\": {\n          \"description\": \"Display format of the current layout\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/DisplayFormat\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"enable\": {\n          \"description\": \"Enable the Komorebi Layout widget\",\n          \"type\": \"boolean\"\n        },\n        \"options\": {\n          \"description\": \"List of layout options\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"$ref\": \"#/$defs/KomorebiLayout\"\n          }\n        }\n      },\n      \"required\": [\n        \"enable\"\n      ]\n    },\n    \"KomorebiLockedContainerConfig\": {\n      \"description\": \"Komorebi widget locked container configuration\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"display\": {\n          \"description\": \"Display format of the current locked state\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/DisplayFormat\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"enable\": {\n          \"description\": \"Enable the Komorebi Locked Container widget\",\n          \"type\": \"boolean\"\n        },\n        \"show_when_unlocked\": {\n          \"description\": \"Show the widget event if the layer is unlocked\",\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ]\n        }\n      },\n      \"required\": [\n        \"enable\"\n      ]\n    },\n    \"KomorebiMouseMessage\": {\n      \"description\": \"Komorebi socket mouse message\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"focus_monitor_at_cursor\": {\n          \"description\": \"Send the FocusMonitorAtCursor message\",\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ],\n          \"default\": true\n        },\n        \"ignore_mouse_follows_focus\": {\n          \"description\": \"Wrap the {message} with a MouseFollowsFocus(false) and MouseFollowsFocus({original.value}) message\",\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ],\n          \"default\": true\n        },\n        \"message\": {\n          \"description\": \"The message to send to the komorebi client\",\n          \"$ref\": \"#/$defs/SocketMessage\"\n        }\n      },\n      \"required\": [\n        \"message\"\n      ]\n    },\n    \"KomorebiTheme\": {\n      \"description\": \"Komorebi theme\",\n      \"oneOf\": [\n        {\n          \"title\": \"Catppuccin\",\n          \"description\": \"Theme from catppuccin-egui\",\n          \"type\": \"object\",\n          \"properties\": {\n            \"palette\": {\n              \"type\": \"string\",\n              \"const\": \"Catppuccin\"\n            }\n          },\n          \"$ref\": \"#/$defs/KomorebiThemeCatppuccin\",\n          \"required\": [\n            \"palette\"\n          ]\n        },\n        {\n          \"title\": \"Base16\",\n          \"description\": \"Theme from base16-egui-themes\",\n          \"type\": \"object\",\n          \"properties\": {\n            \"palette\": {\n              \"type\": \"string\",\n              \"const\": \"Base16\"\n            }\n          },\n          \"$ref\": \"#/$defs/KomorebiThemeBase16\",\n          \"required\": [\n            \"palette\"\n          ]\n        },\n        {\n          \"title\": \"Custom\",\n          \"description\": \"Custom Base16 theme\",\n          \"type\": \"object\",\n          \"properties\": {\n            \"palette\": {\n              \"type\": \"string\",\n              \"const\": \"Custom\"\n            }\n          },\n          \"$ref\": \"#/$defs/KomorebiThemeCustom\",\n          \"required\": [\n            \"palette\"\n          ]\n        }\n      ]\n    },\n    \"KomorebiThemeBase16\": {\n      \"description\": \"Theme from base16-egui-themes\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"bar_accent\": {\n          \"description\": \"Bar accent colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base0D\"\n        },\n        \"floating_border\": {\n          \"description\": \"Floating window border colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base09\"\n        },\n        \"monocle_border\": {\n          \"description\": \"Monocle window border colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base0F\"\n        },\n        \"name\": {\n          \"description\": \"Name of the Base16 theme (theme previews: https://tinted-theming.github.io/tinted-gallery/)\",\n          \"$ref\": \"#/$defs/Base16\"\n        },\n        \"single_border\": {\n          \"description\": \"Single window border colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base0D\"\n        },\n        \"stack_border\": {\n          \"description\": \"Stack window border colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base0B\"\n        },\n        \"stackbar_background\": {\n          \"description\": \"Stackbar background colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base01\"\n        },\n        \"stackbar_focused_text\": {\n          \"description\": \"Stackbar focused text colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base0B\"\n        },\n        \"stackbar_unfocused_text\": {\n          \"description\": \"Stackbar unfocused text colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base05\"\n        },\n        \"unfocused_border\": {\n          \"description\": \"Unfocused window border colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base01\"\n        },\n        \"unfocused_locked_border\": {\n          \"description\": \"Unfocused locked window border colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base08\"\n        }\n      },\n      \"required\": [\n        \"name\"\n      ]\n    },\n    \"KomorebiThemeCatppuccin\": {\n      \"description\": \"Theme from catppuccin-egui\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"bar_accent\": {\n          \"description\": \"Bar accent colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/CatppuccinValue\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Blue\"\n        },\n        \"floating_border\": {\n          \"description\": \"Floating window border colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/CatppuccinValue\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Yellow\"\n        },\n        \"monocle_border\": {\n          \"description\": \"Monocle window border colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/CatppuccinValue\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Pink\"\n        },\n        \"name\": {\n          \"description\": \"Name of the Catppuccin theme (previews: https://github.com/catppuccin/catppuccin)\",\n          \"$ref\": \"#/$defs/Catppuccin\"\n        },\n        \"single_border\": {\n          \"description\": \"Single window border colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/CatppuccinValue\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Blue\"\n        },\n        \"stack_border\": {\n          \"description\": \"Stack window border colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/CatppuccinValue\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Green\"\n        },\n        \"stackbar_background\": {\n          \"description\": \"Stackbar background colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/CatppuccinValue\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base\"\n        },\n        \"stackbar_focused_text\": {\n          \"description\": \"Stackbar focused text colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/CatppuccinValue\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Green\"\n        },\n        \"stackbar_unfocused_text\": {\n          \"description\": \"Stackbar unfocused text colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/CatppuccinValue\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Text\"\n        },\n        \"unfocused_border\": {\n          \"description\": \"Unfocused window border colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/CatppuccinValue\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base\"\n        },\n        \"unfocused_locked_border\": {\n          \"description\": \"Unfocused locked window border colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/CatppuccinValue\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Red\"\n        }\n      },\n      \"required\": [\n        \"name\"\n      ]\n    },\n    \"KomorebiThemeCustom\": {\n      \"description\": \"Custom Base16 theme\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"bar_accent\": {\n          \"description\": \"Bar accent colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base0D\"\n        },\n        \"colours\": {\n          \"description\": \"Colours of the custom Base16 theme palette\",\n          \"$ref\": \"#/$defs/Base16ColourPalette\"\n        },\n        \"floating_border\": {\n          \"description\": \"Floating window border colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base09\"\n        },\n        \"monocle_border\": {\n          \"description\": \"Monocle window border colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base0F\"\n        },\n        \"single_border\": {\n          \"description\": \"Single window border colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base0D\"\n        },\n        \"stack_border\": {\n          \"description\": \"Stack window border colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base0B\"\n        },\n        \"stackbar_background\": {\n          \"description\": \"Stackbar background colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base01\"\n        },\n        \"stackbar_focused_text\": {\n          \"description\": \"Stackbar focused text colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base0B\"\n        },\n        \"stackbar_unfocused_text\": {\n          \"description\": \"Stackbar unfocused text colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base05\"\n        },\n        \"unfocused_border\": {\n          \"description\": \"Unfocused window border colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base01\"\n        },\n        \"unfocused_locked_border\": {\n          \"description\": \"Unfocused locked window border colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base08\"\n        }\n      },\n      \"required\": [\n        \"colours\"\n      ]\n    },\n    \"KomorebiWorkspaceLayerConfig\": {\n      \"description\": \"Komorebi widget workspace layer configuration\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"display\": {\n          \"description\": \"Display format of the current layer\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/DisplayFormat\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"enable\": {\n          \"description\": \"Enable the Komorebi Workspace Layer widget\",\n          \"type\": \"boolean\"\n        },\n        \"show_when_tiling\": {\n          \"description\": \"Show the widget event if the layer is Tiling\",\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ]\n        }\n      },\n      \"required\": [\n        \"enable\"\n      ]\n    },\n    \"KomorebiWorkspacesConfig\": {\n      \"description\": \"Komorebi widget workspaces configuration\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"display\": {\n          \"description\": \"Display format of the workspace\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/WorkspacesDisplayFormat\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"enable\": {\n          \"description\": \"Enable the Komorebi Workspaces widget\",\n          \"type\": \"boolean\"\n        },\n        \"hide_empty_workspaces\": {\n          \"description\": \"Hide workspaces without any windows\",\n          \"type\": \"boolean\"\n        }\n      },\n      \"required\": [\n        \"enable\",\n        \"hide_empty_workspaces\"\n      ]\n    },\n    \"LabelPrefix\": {\n      \"description\": \"Label prefix\",\n      \"oneOf\": [\n        {\n          \"description\": \"Show no prefix\",\n          \"type\": \"string\",\n          \"const\": \"None\"\n        },\n        {\n          \"description\": \"Show an icon\",\n          \"type\": \"string\",\n          \"const\": \"Icon\"\n        },\n        {\n          \"description\": \"Show text\",\n          \"type\": \"string\",\n          \"const\": \"Text\"\n        },\n        {\n          \"description\": \"Show an icon and text\",\n          \"type\": \"string\",\n          \"const\": \"IconAndText\"\n        }\n      ]\n    },\n    \"MediaConfig\": {\n      \"description\": \"Media widget configuration\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"display\": {\n          \"description\": \"Display format of the media widget (defaults to IconAndText)\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/MediaDisplayFormat\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"enable\": {\n          \"description\": \"Enable the Media widget\",\n          \"type\": \"boolean\"\n        }\n      },\n      \"required\": [\n        \"enable\"\n      ]\n    },\n    \"MediaDisplayFormat\": {\n      \"description\": \"Media widget display format\",\n      \"oneOf\": [\n        {\n          \"description\": \"Show only the media info icon\",\n          \"type\": \"string\",\n          \"const\": \"Icon\"\n        },\n        {\n          \"description\": \"Show only the media info text (artist - title)\",\n          \"type\": \"string\",\n          \"const\": \"Text\"\n        },\n        {\n          \"description\": \"Show both icon and text\",\n          \"type\": \"string\",\n          \"const\": \"IconAndText\"\n        },\n        {\n          \"description\": \"Show only the control buttons (previous, play/pause, next)\",\n          \"type\": \"string\",\n          \"const\": \"ControlsOnly\"\n        },\n        {\n          \"description\": \"Show icon with control buttons\",\n          \"type\": \"string\",\n          \"const\": \"IconAndControls\"\n        },\n        {\n          \"description\": \"Show text with control buttons\",\n          \"type\": \"string\",\n          \"const\": \"TextAndControls\"\n        },\n        {\n          \"description\": \"Show icon, text, and control buttons\",\n          \"type\": \"string\",\n          \"const\": \"Full\"\n        }\n      ]\n    },\n    \"MemoryConfig\": {\n      \"description\": \"Memory widget configuration\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"auto_select_over\": {\n          \"description\": \"Select when the current percentage is over this value [[1-100]]\",\n          \"type\": [\n            \"integer\",\n            \"null\"\n          ],\n          \"format\": \"uint8\",\n          \"maximum\": 255,\n          \"minimum\": 0\n        },\n        \"data_refresh_interval\": {\n          \"description\": \"Data refresh interval in seconds\",\n          \"type\": [\n            \"integer\",\n            \"null\"\n          ],\n          \"format\": \"uint64\",\n          \"default\": 10,\n          \"minimum\": 0\n        },\n        \"enable\": {\n          \"description\": \"Enable the Memory widget\",\n          \"type\": \"boolean\"\n        },\n        \"label_prefix\": {\n          \"description\": \"Display label prefix\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/LabelPrefix\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        }\n      },\n      \"required\": [\n        \"enable\"\n      ]\n    },\n    \"MonitorConfig\": {\n      \"description\": \"Monitor configuration\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"index\": {\n          \"description\": \"Komorebi monitor index of the monitor on which to render the bar\",\n          \"type\": \"integer\",\n          \"format\": \"uint\",\n          \"minimum\": 0\n        },\n        \"work_area_offset\": {\n          \"description\": \"Automatically apply a work area offset for this monitor to accommodate the bar\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Rect\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        }\n      },\n      \"required\": [\n        \"index\"\n      ]\n    },\n    \"MonitorConfigOrIndex\": {\n      \"description\": \"Monitor configuration or monitor index\",\n      \"anyOf\": [\n        {\n          \"description\": \"The monitor index where you want the bar to show\",\n          \"type\": \"integer\",\n          \"format\": \"uint\",\n          \"minimum\": 0\n        },\n        {\n          \"description\": \"The full monitor options with the index and an optional work_area_offset\",\n          \"$ref\": \"#/$defs/MonitorConfig\"\n        }\n      ]\n    },\n    \"MouseConfig\": {\n      \"description\": \"Mouse configuration\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"horizontal_scroll_threshold\": {\n          \"description\": \"Defines how many points a user needs to scroll horizontally to make a \\\"tick\\\" on a mouse/touchpad/touchscreen\",\n          \"type\": [\n            \"number\",\n            \"null\"\n          ],\n          \"format\": \"float\",\n          \"default\": 30.0\n        },\n        \"on_extra1_click\": {\n          \"description\": \"Command to send on extra1/back button click\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/MouseMessage\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"on_extra2_click\": {\n          \"description\": \"Command to send on extra2/forward button click\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/MouseMessage\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"on_middle_click\": {\n          \"description\": \"Command to send on middle button click\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/MouseMessage\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"on_primary_double_click\": {\n          \"description\": \"Command to send on primary/left double button click\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/MouseMessage\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"on_scroll_down\": {\n          \"description\": \"Command to send on scrolling down (every tick)\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/MouseMessage\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"on_scroll_left\": {\n          \"description\": \"Command to send on scrolling left (every tick)\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/MouseMessage\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"on_scroll_right\": {\n          \"description\": \"Command to send on scrolling right (every tick)\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/MouseMessage\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"on_scroll_up\": {\n          \"description\": \"Command to send on scrolling up (every tick)\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/MouseMessage\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"on_secondary_click\": {\n          \"description\": \"Command to send on secondary/right button click\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/MouseMessage\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"vertical_scroll_threshold\": {\n          \"description\": \"Defines how many points a user needs to scroll vertically to make a \\\"tick\\\" on a mouse/touchpad/touchscreen\",\n          \"type\": [\n            \"number\",\n            \"null\"\n          ],\n          \"format\": \"float\",\n          \"default\": 30.0\n        }\n      }\n    },\n    \"MouseMessage\": {\n      \"description\": \"Mouse message\",\n      \"anyOf\": [\n        {\n          \"description\": \"Send a message to the komorebi client.\\nBy default, a batch of messages are sent in the following order:\\nFocusMonitorAtCursor =>\\nMouseFollowsFocus(false) =>\\n{message} =>\\nMouseFollowsFocus({original.value})\\n\\nExample:\\n```json\\n\\\"on_extra2_click\\\": {\\n  \\\"message\\\": {\\n    \\\"type\\\": \\\"NewWorkspace\\\"\\n  }\\n},\\n```\\nor:\\n```json\\n\\\"on_middle_click\\\": {\\n  \\\"focus_monitor_at_cursor\\\": false,\\n  \\\"ignore_mouse_follows_focus\\\": false,\\n  \\\"message\\\": {\\n    \\\"type\\\": \\\"TogglePause\\\"\\n  }\\n}\\n```\\nor:\\n```json\\n\\\"on_scroll_up\\\": {\\n  \\\"message\\\": {\\n    \\\"type\\\": \\\"CycleFocusWorkspace\\\",\\n    \\\"content\\\": \\\"Previous\\\"\\n  }\\n}\\n```\",\n          \"$ref\": \"#/$defs/KomorebiMouseMessage\"\n        },\n        {\n          \"description\": \"Execute a custom command.\\nCMD (%variable%), Bash ($variable) and PowerShell ($Env:variable) variables will be resolved.\\nExample: `komorebic toggle-pause`\",\n          \"type\": \"string\"\n        }\n      ]\n    },\n    \"MoveBehaviour\": {\n      \"description\": \"Move behaviour when the operation works across a monitor boundary\",\n      \"oneOf\": [\n        {\n          \"description\": \"Swap the window container with the window container at the edge of the adjacent monitor\",\n          \"type\": \"string\",\n          \"const\": \"Swap\"\n        },\n        {\n          \"description\": \"Insert the window container into the focused workspace on the adjacent monitor\",\n          \"type\": \"string\",\n          \"const\": \"Insert\"\n        },\n        {\n          \"description\": \"Do nothing if trying to move a window container in the direction of an adjacent monitor\",\n          \"type\": \"string\",\n          \"const\": \"NoOp\"\n        }\n      ]\n    },\n    \"NetworkConfig\": {\n      \"description\": \"Network widget configuration\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"activity_left_padding\": {\n          \"description\": \"Characters to reserve for received and transmitted activity\",\n          \"type\": [\n            \"integer\",\n            \"null\"\n          ],\n          \"format\": \"uint\",\n          \"minimum\": 0\n        },\n        \"auto_select\": {\n          \"description\": \"Select when the value is over a limit (1MiB is 1048576 bytes (1024*1024))\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/NetworkSelectConfig\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"data_refresh_interval\": {\n          \"description\": \"Data refresh interval in seconds\",\n          \"type\": [\n            \"integer\",\n            \"null\"\n          ],\n          \"format\": \"uint64\",\n          \"default\": 10,\n          \"minimum\": 0\n        },\n        \"enable\": {\n          \"description\": \"Enable the Network widget\",\n          \"type\": \"boolean\"\n        },\n        \"label_prefix\": {\n          \"description\": \"Display label prefix\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/LabelPrefix\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"show_activity\": {\n          \"description\": \"Show received and transmitted activity\",\n          \"type\": \"boolean\"\n        },\n        \"show_default_interface\": {\n          \"description\": \"Show default interface\",\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ]\n        },\n        \"show_total_activity\": {\n          \"description\": \"Show total received and transmitted activity\",\n          \"type\": \"boolean\"\n        }\n      },\n      \"required\": [\n        \"enable\",\n        \"show_total_activity\",\n        \"show_activity\"\n      ]\n    },\n    \"NetworkSelectConfig\": {\n      \"description\": \"Network select configuration\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"received_over\": {\n          \"description\": \"Select the received data when it's over this value\",\n          \"type\": [\n            \"integer\",\n            \"null\"\n          ],\n          \"format\": \"uint64\",\n          \"minimum\": 0\n        },\n        \"total_received_over\": {\n          \"description\": \"Select the total received data when it's over this value\",\n          \"type\": [\n            \"integer\",\n            \"null\"\n          ],\n          \"format\": \"uint64\",\n          \"minimum\": 0\n        },\n        \"total_transmitted_over\": {\n          \"description\": \"Select the total transmitted data when it's over this value\",\n          \"type\": [\n            \"integer\",\n            \"null\"\n          ],\n          \"format\": \"uint64\",\n          \"minimum\": 0\n        },\n        \"transmitted_over\": {\n          \"description\": \"Select the transmitted data when it's over this value\",\n          \"type\": [\n            \"integer\",\n            \"null\"\n          ],\n          \"format\": \"uint64\",\n          \"minimum\": 0\n        }\n      }\n    },\n    \"OperationBehaviour\": {\n      \"description\": \"Operation behaviour for temporarily unmanaged and floating windows\",\n      \"oneOf\": [\n        {\n          \"description\": \"Process commands on temporarily unmanaged/floated windows\",\n          \"type\": \"string\",\n          \"const\": \"Op\"\n        },\n        {\n          \"description\": \"Ignore commands on temporarily unmanaged/floated windows\",\n          \"type\": \"string\",\n          \"const\": \"NoOp\"\n        }\n      ]\n    },\n    \"OperationDirection\": {\n      \"type\": \"string\",\n      \"enum\": [\n        \"Left\",\n        \"Right\",\n        \"Up\",\n        \"Down\"\n      ]\n    },\n    \"PathBuf\": {\n      \"description\": \"A file system path. Environment variables like %VAR%, $Env:VAR, or $VAR are automatically resolved.\",\n      \"type\": \"string\"\n    },\n    \"Position\": {\n      \"description\": \"Position\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"x\": {\n          \"description\": \"X coordinate\",\n          \"type\": \"number\",\n          \"format\": \"float\"\n        },\n        \"y\": {\n          \"description\": \"Y coordinate\",\n          \"type\": \"number\",\n          \"format\": \"float\"\n        }\n      },\n      \"required\": [\n        \"x\",\n        \"y\"\n      ]\n    },\n    \"PositionConfig\": {\n      \"description\": \"Position configuration\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"end\": {\n          \"description\": \"The desired size of the bar from the starting position (usually monitor width x desired height)\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Position\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"start\": {\n          \"description\": \"The desired starting position of the bar (0,0 = top left of the screen)\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Position\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        }\n      }\n    },\n    \"Rect\": {\n      \"description\": \"Rectangle dimensions\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"bottom\": {\n          \"description\": \"Height of the rectangle (from the top point)\",\n          \"type\": \"integer\",\n          \"format\": \"int32\"\n        },\n        \"left\": {\n          \"description\": \"Left point of the rectangle\",\n          \"type\": \"integer\",\n          \"format\": \"int32\"\n        },\n        \"right\": {\n          \"description\": \"Width of the recentangle (from the left point)\",\n          \"type\": \"integer\",\n          \"format\": \"int32\"\n        },\n        \"top\": {\n          \"description\": \"Top point of the rectangle\",\n          \"type\": \"integer\",\n          \"format\": \"int32\"\n        }\n      },\n      \"required\": [\n        \"left\",\n        \"top\",\n        \"right\",\n        \"bottom\"\n      ]\n    },\n    \"Rgb\": {\n      \"description\": \"Colour represented as RGB\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"b\": {\n          \"description\": \"Blue\",\n          \"type\": \"integer\",\n          \"format\": \"uint32\",\n          \"minimum\": 0\n        },\n        \"g\": {\n          \"description\": \"Green\",\n          \"type\": \"integer\",\n          \"format\": \"uint32\",\n          \"minimum\": 0\n        },\n        \"r\": {\n          \"description\": \"Red\",\n          \"type\": \"integer\",\n          \"format\": \"uint32\",\n          \"minimum\": 0\n        }\n      },\n      \"required\": [\n        \"r\",\n        \"g\",\n        \"b\"\n      ]\n    },\n    \"RoundingConfig\": {\n      \"description\": \"Rounding configuration\",\n      \"anyOf\": [\n        {\n          \"description\": \"All 4 corners are the same\",\n          \"type\": \"number\",\n          \"format\": \"float\"\n        },\n        {\n          \"description\": \"All 4 corners are custom. Order: NW, NE, SW, SE\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"number\",\n            \"format\": \"float\"\n          },\n          \"maxItems\": 4,\n          \"minItems\": 4\n        }\n      ]\n    },\n    \"Sizing\": {\n      \"description\": \"Sizing\",\n      \"oneOf\": [\n        {\n          \"description\": \"Increase\",\n          \"type\": \"string\",\n          \"const\": \"Increase\"\n        },\n        {\n          \"description\": \"Decrease\",\n          \"type\": \"string\",\n          \"const\": \"Decrease\"\n        }\n      ]\n    },\n    \"SocketMessage\": {\n      \"oneOf\": [\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"$ref\": \"#/$defs/OperationDirection\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"FocusWindow\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"$ref\": \"#/$defs/OperationDirection\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"MoveWindow\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"$ref\": \"#/$defs/OperationDirection\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"PreselectDirection\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"CancelPreselect\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"$ref\": \"#/$defs/CycleDirection\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"CycleFocusWindow\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"$ref\": \"#/$defs/CycleDirection\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"CycleMoveWindow\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"$ref\": \"#/$defs/OperationDirection\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"StackWindow\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"UnstackWindow\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"$ref\": \"#/$defs/CycleDirection\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"CycleStack\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"$ref\": \"#/$defs/CycleDirection\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"CycleStackIndex\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"integer\",\n              \"format\": \"uint\",\n              \"minimum\": 0\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"FocusStackWindow\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"StackAll\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"UnstackAll\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 2,\n              \"minItems\": 2,\n              \"prefixItems\": [\n                {\n                  \"$ref\": \"#/$defs/OperationDirection\"\n                },\n                {\n                  \"$ref\": \"#/$defs/Sizing\"\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"ResizeWindowEdge\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 2,\n              \"minItems\": 2,\n              \"prefixItems\": [\n                {\n                  \"$ref\": \"#/$defs/Axis\"\n                },\n                {\n                  \"$ref\": \"#/$defs/Sizing\"\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"ResizeWindowAxis\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"MoveContainerToLastWorkspace\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"SendContainerToLastWorkspace\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"integer\",\n              \"format\": \"uint\",\n              \"minimum\": 0\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"MoveContainerToMonitorNumber\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"$ref\": \"#/$defs/CycleDirection\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"CycleMoveContainerToMonitor\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"integer\",\n              \"format\": \"uint\",\n              \"minimum\": 0\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"MoveContainerToWorkspaceNumber\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"string\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"MoveContainerToNamedWorkspace\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"$ref\": \"#/$defs/CycleDirection\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"CycleMoveContainerToWorkspace\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"integer\",\n              \"format\": \"uint\",\n              \"minimum\": 0\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"SendContainerToMonitorNumber\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"$ref\": \"#/$defs/CycleDirection\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"CycleSendContainerToMonitor\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"integer\",\n              \"format\": \"uint\",\n              \"minimum\": 0\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"SendContainerToWorkspaceNumber\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"$ref\": \"#/$defs/CycleDirection\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"CycleSendContainerToWorkspace\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 2,\n              \"minItems\": 2,\n              \"prefixItems\": [\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                },\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"SendContainerToMonitorWorkspaceNumber\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 2,\n              \"minItems\": 2,\n              \"prefixItems\": [\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                },\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"MoveContainerToMonitorWorkspaceNumber\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"string\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"SendContainerToNamedWorkspace\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"$ref\": \"#/$defs/CycleDirection\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"CycleMoveWorkspaceToMonitor\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"integer\",\n              \"format\": \"uint\",\n              \"minimum\": 0\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"MoveWorkspaceToMonitorNumber\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"integer\",\n              \"format\": \"uint\",\n              \"minimum\": 0\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"SwapWorkspacesToMonitorNumber\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"ForceFocus\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"Close\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"Minimize\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"Promote\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"PromoteSwap\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"PromoteFocus\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"$ref\": \"#/$defs/OperationDirection\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"PromoteWindow\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"string\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"EagerFocus\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 3,\n              \"minItems\": 3,\n              \"prefixItems\": [\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                },\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                },\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"LockMonitorWorkspaceContainer\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 3,\n              \"minItems\": 3,\n              \"prefixItems\": [\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                },\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                },\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"UnlockMonitorWorkspaceContainer\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"ToggleLock\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"ToggleFloat\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"ToggleMonocle\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"ToggleMaximize\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"ToggleWindowContainerBehaviour\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"ToggleFloatOverride\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"$ref\": \"#/$defs/HidingBehaviour\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"WindowHidingBehaviour\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"ToggleCrossMonitorMoveBehaviour\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"$ref\": \"#/$defs/MoveBehaviour\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"CrossMonitorMoveBehaviour\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"$ref\": \"#/$defs/OperationBehaviour\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"UnmanagedWindowOperationBehaviour\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"ManageFocusedWindow\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"UnmanageFocusedWindow\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 2,\n              \"minItems\": 2,\n              \"prefixItems\": [\n                {\n                  \"$ref\": \"#/$defs/Sizing\"\n                },\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"int32\"\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"AdjustContainerPadding\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 2,\n              \"minItems\": 2,\n              \"prefixItems\": [\n                {\n                  \"$ref\": \"#/$defs/Sizing\"\n                },\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"int32\"\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"AdjustWorkspacePadding\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"$ref\": \"#/$defs/DefaultLayout\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"ChangeLayout\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"$ref\": \"#/$defs/CycleDirection\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"CycleLayout\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 2,\n              \"minItems\": 2,\n              \"prefixItems\": [\n                {\n                  \"type\": [\n                    \"array\",\n                    \"null\"\n                  ],\n                  \"items\": {\n                    \"type\": \"number\",\n                    \"format\": \"float\"\n                  }\n                },\n                {\n                  \"type\": [\n                    \"array\",\n                    \"null\"\n                  ],\n                  \"items\": {\n                    \"type\": \"number\",\n                    \"format\": \"float\"\n                  }\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"LayoutRatios\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"integer\",\n              \"format\": \"uint\",\n              \"minimum\": 1\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"ScrollingLayoutColumns\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"$ref\": \"#/$defs/PathBuf\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"ChangeLayoutCustom\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"$ref\": \"#/$defs/Axis\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"FlipLayout\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"ToggleWorkspaceWindowContainerBehaviour\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"ToggleWorkspaceFloatOverride\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 5,\n              \"minItems\": 5,\n              \"prefixItems\": [\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                },\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"int32\"\n                },\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"int32\"\n                },\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"int32\"\n                },\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"int32\"\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"MonitorIndexPreference\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 2,\n              \"minItems\": 2,\n              \"prefixItems\": [\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                },\n                {\n                  \"type\": \"string\"\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"DisplayIndexPreference\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 2,\n              \"minItems\": 2,\n              \"prefixItems\": [\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                },\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"EnsureWorkspaces\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 2,\n              \"minItems\": 2,\n              \"prefixItems\": [\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                },\n                {\n                  \"type\": \"array\",\n                  \"items\": {\n                    \"type\": \"string\"\n                  }\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"EnsureNamedWorkspaces\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"NewWorkspace\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"ToggleTiling\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"Stop\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"StopIgnoreRestore\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"TogglePause\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"Retile\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"RetileWithResizeDimensions\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"QuickSave\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"QuickLoad\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"$ref\": \"#/$defs/PathBuf\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"Save\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"$ref\": \"#/$defs/PathBuf\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"Load\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"$ref\": \"#/$defs/CycleDirection\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"CycleFocusMonitor\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"$ref\": \"#/$defs/CycleDirection\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"CycleFocusWorkspace\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"$ref\": \"#/$defs/CycleDirection\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"CycleFocusEmptyWorkspace\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"integer\",\n              \"format\": \"uint\",\n              \"minimum\": 0\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"FocusMonitorNumber\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"FocusMonitorAtCursor\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"FocusLastWorkspace\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"CloseWorkspace\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"integer\",\n              \"format\": \"uint\",\n              \"minimum\": 0\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"FocusWorkspaceNumber\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"integer\",\n              \"format\": \"uint\",\n              \"minimum\": 0\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"FocusWorkspaceNumbers\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 2,\n              \"minItems\": 2,\n              \"prefixItems\": [\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                },\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"FocusMonitorWorkspaceNumber\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"string\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"FocusNamedWorkspace\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 3,\n              \"minItems\": 3,\n              \"prefixItems\": [\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                },\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                },\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"int32\"\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"ContainerPadding\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 2,\n              \"minItems\": 2,\n              \"prefixItems\": [\n                {\n                  \"type\": \"string\"\n                },\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"int32\"\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"NamedWorkspaceContainerPadding\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"integer\",\n              \"format\": \"int32\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"FocusedWorkspaceContainerPadding\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 3,\n              \"minItems\": 3,\n              \"prefixItems\": [\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                },\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                },\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"int32\"\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"WorkspacePadding\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 2,\n              \"minItems\": 2,\n              \"prefixItems\": [\n                {\n                  \"type\": \"string\"\n                },\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"int32\"\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"NamedWorkspacePadding\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"integer\",\n              \"format\": \"int32\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"FocusedWorkspacePadding\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 3,\n              \"minItems\": 3,\n              \"prefixItems\": [\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                },\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                },\n                {\n                  \"type\": \"boolean\"\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"WorkspaceTiling\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 2,\n              \"minItems\": 2,\n              \"prefixItems\": [\n                {\n                  \"type\": \"string\"\n                },\n                {\n                  \"type\": \"boolean\"\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"NamedWorkspaceTiling\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 3,\n              \"minItems\": 3,\n              \"prefixItems\": [\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                },\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                },\n                {\n                  \"type\": \"string\"\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"WorkspaceName\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 3,\n              \"minItems\": 3,\n              \"prefixItems\": [\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                },\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                },\n                {\n                  \"$ref\": \"#/$defs/DefaultLayout\"\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"WorkspaceLayout\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 2,\n              \"minItems\": 2,\n              \"prefixItems\": [\n                {\n                  \"type\": \"string\"\n                },\n                {\n                  \"$ref\": \"#/$defs/DefaultLayout\"\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"NamedWorkspaceLayout\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 3,\n              \"minItems\": 3,\n              \"prefixItems\": [\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                },\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                },\n                {\n                  \"$ref\": \"#/$defs/PathBuf\"\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"WorkspaceLayoutCustom\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 2,\n              \"minItems\": 2,\n              \"prefixItems\": [\n                {\n                  \"type\": \"string\"\n                },\n                {\n                  \"$ref\": \"#/$defs/PathBuf\"\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"NamedWorkspaceLayoutCustom\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 4,\n              \"minItems\": 4,\n              \"prefixItems\": [\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                },\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                },\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                },\n                {\n                  \"$ref\": \"#/$defs/DefaultLayout\"\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"WorkspaceLayoutRule\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 3,\n              \"minItems\": 3,\n              \"prefixItems\": [\n                {\n                  \"type\": \"string\"\n                },\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                },\n                {\n                  \"$ref\": \"#/$defs/DefaultLayout\"\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"NamedWorkspaceLayoutRule\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 4,\n              \"minItems\": 4,\n              \"prefixItems\": [\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                },\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                },\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                },\n                {\n                  \"$ref\": \"#/$defs/PathBuf\"\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"WorkspaceLayoutCustomRule\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 3,\n              \"minItems\": 3,\n              \"prefixItems\": [\n                {\n                  \"type\": \"string\"\n                },\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                },\n                {\n                  \"$ref\": \"#/$defs/PathBuf\"\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"NamedWorkspaceLayoutCustomRule\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 2,\n              \"minItems\": 2,\n              \"prefixItems\": [\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                },\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"ClearWorkspaceLayoutRules\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"string\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"ClearNamedWorkspaceLayoutRules\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"ToggleWorkspaceLayer\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"ReloadConfiguration\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"$ref\": \"#/$defs/PathBuf\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"ReplaceConfiguration\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"$ref\": \"#/$defs/PathBuf\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"ReloadStaticConfiguration\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"boolean\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"WatchConfiguration\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"CompleteConfiguration\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"boolean\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"AltFocusHack\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"$ref\": \"#/$defs/KomorebiTheme\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"Theme\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 2,\n              \"minItems\": 2,\n              \"prefixItems\": [\n                {\n                  \"type\": \"boolean\"\n                },\n                {\n                  \"anyOf\": [\n                    {\n                      \"$ref\": \"#/$defs/AnimationPrefix\"\n                    },\n                    {\n                      \"type\": \"null\"\n                    }\n                  ]\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"Animation\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 2,\n              \"minItems\": 2,\n              \"prefixItems\": [\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint64\",\n                  \"minimum\": 0\n                },\n                {\n                  \"anyOf\": [\n                    {\n                      \"$ref\": \"#/$defs/AnimationPrefix\"\n                    },\n                    {\n                      \"type\": \"null\"\n                    }\n                  ]\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"AnimationDuration\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"integer\",\n              \"format\": \"uint64\",\n              \"minimum\": 0\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"AnimationFps\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 2,\n              \"minItems\": 2,\n              \"prefixItems\": [\n                {\n                  \"$ref\": \"#/$defs/AnimationStyle\"\n                },\n                {\n                  \"anyOf\": [\n                    {\n                      \"$ref\": \"#/$defs/AnimationPrefix\"\n                    },\n                    {\n                      \"type\": \"null\"\n                    }\n                  ]\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"AnimationStyle\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"boolean\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"Border\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 4,\n              \"minItems\": 4,\n              \"prefixItems\": [\n                {\n                  \"$ref\": \"#/$defs/WindowKind\"\n                },\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint32\",\n                  \"minimum\": 0\n                },\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint32\",\n                  \"minimum\": 0\n                },\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint32\",\n                  \"minimum\": 0\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"BorderColour\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"$ref\": \"#/$defs/BorderStyle\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"BorderStyle\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"integer\",\n              \"format\": \"int32\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"BorderWidth\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"integer\",\n              \"format\": \"int32\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"BorderOffset\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"$ref\": \"#/$defs/BorderImplementation\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"BorderImplementation\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"boolean\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"Transparency\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"ToggleTransparency\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"integer\",\n              \"format\": \"uint8\",\n              \"maximum\": 255,\n              \"minimum\": 0\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"TransparencyAlpha\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"$ref\": \"#/$defs/Rect\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"InvisibleBorders\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"$ref\": \"#/$defs/StackbarMode\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"StackbarMode\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"$ref\": \"#/$defs/StackbarLabel\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"StackbarLabel\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 3,\n              \"minItems\": 3,\n              \"prefixItems\": [\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint32\",\n                  \"minimum\": 0\n                },\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint32\",\n                  \"minimum\": 0\n                },\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint32\",\n                  \"minimum\": 0\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"StackbarFocusedTextColour\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 3,\n              \"minItems\": 3,\n              \"prefixItems\": [\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint32\",\n                  \"minimum\": 0\n                },\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint32\",\n                  \"minimum\": 0\n                },\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint32\",\n                  \"minimum\": 0\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"StackbarUnfocusedTextColour\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 3,\n              \"minItems\": 3,\n              \"prefixItems\": [\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint32\",\n                  \"minimum\": 0\n                },\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint32\",\n                  \"minimum\": 0\n                },\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint32\",\n                  \"minimum\": 0\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"StackbarBackgroundColour\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"integer\",\n              \"format\": \"int32\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"StackbarHeight\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"integer\",\n              \"format\": \"int32\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"StackbarTabWidth\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"integer\",\n              \"format\": \"int32\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"StackbarFontSize\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": [\n                \"string\",\n                \"null\"\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"StackbarFontFamily\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"$ref\": \"#/$defs/Rect\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"WorkAreaOffset\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 2,\n              \"minItems\": 2,\n              \"prefixItems\": [\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                },\n                {\n                  \"$ref\": \"#/$defs/Rect\"\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"MonitorWorkAreaOffset\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 3,\n              \"minItems\": 3,\n              \"prefixItems\": [\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                },\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                },\n                {\n                  \"$ref\": \"#/$defs/Rect\"\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"WorkspaceWorkAreaOffset\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"ToggleWindowBasedWorkAreaOffset\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"integer\",\n              \"format\": \"int32\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"ResizeDelta\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 4,\n              \"minItems\": 4,\n              \"prefixItems\": [\n                {\n                  \"$ref\": \"#/$defs/ApplicationIdentifier\"\n                },\n                {\n                  \"type\": \"string\"\n                },\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                },\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"InitialWorkspaceRule\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 3,\n              \"minItems\": 3,\n              \"prefixItems\": [\n                {\n                  \"$ref\": \"#/$defs/ApplicationIdentifier\"\n                },\n                {\n                  \"type\": \"string\"\n                },\n                {\n                  \"type\": \"string\"\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"InitialNamedWorkspaceRule\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 4,\n              \"minItems\": 4,\n              \"prefixItems\": [\n                {\n                  \"$ref\": \"#/$defs/ApplicationIdentifier\"\n                },\n                {\n                  \"type\": \"string\"\n                },\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                },\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"WorkspaceRule\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 3,\n              \"minItems\": 3,\n              \"prefixItems\": [\n                {\n                  \"$ref\": \"#/$defs/ApplicationIdentifier\"\n                },\n                {\n                  \"type\": \"string\"\n                },\n                {\n                  \"type\": \"string\"\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"NamedWorkspaceRule\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 2,\n              \"minItems\": 2,\n              \"prefixItems\": [\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                },\n                {\n                  \"type\": \"integer\",\n                  \"format\": \"uint\",\n                  \"minimum\": 0\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"ClearWorkspaceRules\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"string\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"ClearNamedWorkspaceRules\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"ClearAllWorkspaceRules\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"EnforceWorkspaceRules\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"SessionFloatRule\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"SessionFloatRules\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"ClearSessionFloatRules\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 2,\n              \"minItems\": 2,\n              \"prefixItems\": [\n                {\n                  \"$ref\": \"#/$defs/ApplicationIdentifier\"\n                },\n                {\n                  \"type\": \"string\"\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"IgnoreRule\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 2,\n              \"minItems\": 2,\n              \"prefixItems\": [\n                {\n                  \"$ref\": \"#/$defs/ApplicationIdentifier\"\n                },\n                {\n                  \"type\": \"string\"\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"ManageRule\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 2,\n              \"minItems\": 2,\n              \"prefixItems\": [\n                {\n                  \"$ref\": \"#/$defs/ApplicationIdentifier\"\n                },\n                {\n                  \"type\": \"string\"\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"IdentifyObjectNameChangeApplication\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 2,\n              \"minItems\": 2,\n              \"prefixItems\": [\n                {\n                  \"$ref\": \"#/$defs/ApplicationIdentifier\"\n                },\n                {\n                  \"type\": \"string\"\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"IdentifyTrayApplication\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 2,\n              \"minItems\": 2,\n              \"prefixItems\": [\n                {\n                  \"$ref\": \"#/$defs/ApplicationIdentifier\"\n                },\n                {\n                  \"type\": \"string\"\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"IdentifyLayeredApplication\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 2,\n              \"minItems\": 2,\n              \"prefixItems\": [\n                {\n                  \"$ref\": \"#/$defs/ApplicationIdentifier\"\n                },\n                {\n                  \"type\": \"string\"\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"IdentifyBorderOverflowApplication\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"State\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"GlobalState\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"VisibleWindows\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"MonitorInformation\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"$ref\": \"#/$defs/StateQuery\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"Query\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 2,\n              \"minItems\": 2,\n              \"prefixItems\": [\n                {\n                  \"$ref\": \"#/$defs/FocusFollowsMouseImplementation\"\n                },\n                {\n                  \"type\": \"boolean\"\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"FocusFollowsMouse\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"$ref\": \"#/$defs/FocusFollowsMouseImplementation\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"ToggleFocusFollowsMouse\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"boolean\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"MouseFollowsFocus\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"ToggleMouseFollowsFocus\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 2,\n              \"minItems\": 2,\n              \"prefixItems\": [\n                {\n                  \"$ref\": \"#/$defs/ApplicationIdentifier\"\n                },\n                {\n                  \"type\": \"string\"\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"RemoveTitleBar\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"ToggleTitleBars\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"string\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"AddSubscriberSocket\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"array\",\n              \"maxItems\": 2,\n              \"minItems\": 2,\n              \"prefixItems\": [\n                {\n                  \"type\": \"string\"\n                },\n                {\n                  \"$ref\": \"#/$defs/SubscribeOptions\"\n                }\n              ]\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"AddSubscriberSocketWithOptions\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"string\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"RemoveSubscriberSocket\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"string\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"AddSubscriberPipe\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"string\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"RemoveSubscriberPipe\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"ApplicationSpecificConfigurationSchema\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"NotificationSchema\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"SocketSchema\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"StaticConfigSchema\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"GenerateStaticConfig\"\n            }\n          },\n          \"required\": [\n            \"type\"\n          ]\n        },\n        {\n          \"type\": \"object\",\n          \"properties\": {\n            \"content\": {\n              \"type\": \"integer\",\n              \"format\": \"int\"\n            },\n            \"type\": {\n              \"type\": \"string\",\n              \"const\": \"DebugWindow\"\n            }\n          },\n          \"required\": [\n            \"type\",\n            \"content\"\n          ]\n        }\n      ]\n    },\n    \"SpacingKind\": {\n      \"description\": \"Spacing kind\",\n      \"anyOf\": [\n        {\n          \"description\": \"Spacing applied to all sides\",\n          \"type\": \"number\",\n          \"format\": \"float\"\n        },\n        {\n          \"description\": \"Individual spacing applied to each side\",\n          \"$ref\": \"#/$defs/IndividualSpacingConfig\"\n        },\n        {\n          \"description\": \"Grouped vertical and horizontal spacing\",\n          \"$ref\": \"#/$defs/GroupedSpacingConfig\"\n        }\n      ]\n    },\n    \"StackbarLabel\": {\n      \"description\": \"Starbar label\",\n      \"oneOf\": [\n        {\n          \"description\": \"Process name\",\n          \"type\": \"string\",\n          \"const\": \"Process\"\n        },\n        {\n          \"description\": \"Window title\",\n          \"type\": \"string\",\n          \"const\": \"Title\"\n        }\n      ]\n    },\n    \"StackbarMode\": {\n      \"description\": \"Stackbar mode\",\n      \"oneOf\": [\n        {\n          \"description\": \"Always show\",\n          \"type\": \"string\",\n          \"const\": \"Always\"\n        },\n        {\n          \"description\": \"Never show\",\n          \"type\": \"string\",\n          \"const\": \"Never\"\n        },\n        {\n          \"description\": \"Show on stack\",\n          \"type\": \"string\",\n          \"const\": \"OnStack\"\n        }\n      ]\n    },\n    \"StateQuery\": {\n      \"type\": \"string\",\n      \"enum\": [\n        \"FocusedMonitorIndex\",\n        \"FocusedWorkspaceIndex\",\n        \"FocusedContainerIndex\",\n        \"FocusedWindowIndex\",\n        \"FocusedWorkspaceName\",\n        \"FocusedWorkspaceLayout\",\n        \"FocusedContainerKind\",\n        \"Version\"\n      ]\n    },\n    \"StorageConfig\": {\n      \"description\": \"Storage widget configuration\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"auto_hide_under\": {\n          \"description\": \"Hide when the current percentage is under this value [[1-100]]\",\n          \"type\": [\n            \"integer\",\n            \"null\"\n          ],\n          \"format\": \"uint8\",\n          \"maximum\": 255,\n          \"minimum\": 0\n        },\n        \"auto_select_over\": {\n          \"description\": \"Select when the current percentage is over this value [[1-100]]\",\n          \"type\": [\n            \"integer\",\n            \"null\"\n          ],\n          \"format\": \"uint8\",\n          \"maximum\": 255,\n          \"minimum\": 0\n        },\n        \"data_refresh_interval\": {\n          \"description\": \"Data refresh interval in seconds\",\n          \"type\": [\n            \"integer\",\n            \"null\"\n          ],\n          \"format\": \"uint64\",\n          \"default\": 10,\n          \"minimum\": 0\n        },\n        \"enable\": {\n          \"description\": \"Enable the Storage widget\",\n          \"type\": \"boolean\"\n        },\n        \"label_prefix\": {\n          \"description\": \"Display label prefix\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/LabelPrefix\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"show_read_only_disks\": {\n          \"description\": \"Show disks that are read only\",\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ],\n          \"default\": false\n        },\n        \"show_removable_disks\": {\n          \"description\": \"Show removable disks\",\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ],\n          \"default\": true\n        }\n      },\n      \"required\": [\n        \"enable\"\n      ]\n    },\n    \"SubscribeOptions\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"filter_state_changes\": {\n          \"description\": \"Only emit notifications when the window manager state has changed\",\n          \"type\": \"boolean\"\n        }\n      },\n      \"required\": [\n        \"filter_state_changes\"\n      ]\n    },\n    \"TimeConfig\": {\n      \"description\": \"Time widget configuration\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"changing_icon\": {\n          \"description\": \"Change the icon depending on the time. The default icon is used between 8:30 and 12:00\",\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ]\n        },\n        \"enable\": {\n          \"description\": \"Enable the Time widget\",\n          \"type\": \"boolean\"\n        },\n        \"format\": {\n          \"description\": \"Set the Time format\",\n          \"$ref\": \"#/$defs/TimeFormat\"\n        },\n        \"label_prefix\": {\n          \"description\": \"Display label prefix\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/LabelPrefix\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"timezone\": {\n          \"description\": \"TimeZone (https://docs.rs/chrono-tz/latest/chrono_tz/enum.Tz.html)\\n\\nUse a custom format to display additional information, i.e.:\\n```json\\n{\\n    \\\"Time\\\": {\\n        \\\"enable\\\": true,\\n        \\\"format\\\": { \\\"Custom\\\": \\\"%T %Z (Tokyo)\\\" },\\n        \\\"timezone\\\": \\\"Asia/Tokyo\\\"\\n     }\\n}\\n```\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        }\n      },\n      \"required\": [\n        \"enable\",\n        \"format\"\n      ]\n    },\n    \"TimeFormat\": {\n      \"description\": \"Time format\",\n      \"oneOf\": [\n        {\n          \"description\": \"Twelve-hour format (with seconds)\",\n          \"type\": \"string\",\n          \"const\": \"TwelveHour\"\n        },\n        {\n          \"description\": \"Twelve-hour format (without seconds)\",\n          \"type\": \"string\",\n          \"const\": \"TwelveHourWithoutSeconds\"\n        },\n        {\n          \"description\": \"Twenty-four-hour format (with seconds)\",\n          \"type\": \"string\",\n          \"const\": \"TwentyFourHour\"\n        },\n        {\n          \"description\": \"Twenty-four-hour format (without seconds)\",\n          \"type\": \"string\",\n          \"const\": \"TwentyFourHourWithoutSeconds\"\n        },\n        {\n          \"description\": \"Twenty-four-hour format displayed as a binary clock with circles (with seconds) (https://en.wikipedia.org/wiki/Binary_clock)\",\n          \"type\": \"string\",\n          \"const\": \"BinaryCircle\"\n        },\n        {\n          \"description\": \"Twenty-four-hour format displayed as a binary clock with rectangles (with seconds) (https://en.wikipedia.org/wiki/Binary_clock)\",\n          \"type\": \"string\",\n          \"const\": \"BinaryRectangle\"\n        },\n        {\n          \"title\": \"Custom\",\n          \"description\": \"Custom format (https://docs.rs/chrono/latest/chrono/format/strftime/index.html)\",\n          \"type\": \"object\",\n          \"properties\": {\n            \"Custom\": {\n              \"type\": \"string\"\n            }\n          },\n          \"additionalProperties\": false,\n          \"required\": [\n            \"Custom\"\n          ]\n        }\n      ]\n    },\n    \"UpdateConfig\": {\n      \"description\": \"Update widget configuration\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"data_refresh_interval\": {\n          \"description\": \"Data refresh interval in hours\",\n          \"type\": [\n            \"integer\",\n            \"null\"\n          ],\n          \"format\": \"uint64\",\n          \"default\": 12,\n          \"minimum\": 0\n        },\n        \"enable\": {\n          \"description\": \"Enable the Update widget\",\n          \"type\": \"boolean\"\n        },\n        \"label_prefix\": {\n          \"description\": \"Display label prefix\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/LabelPrefix\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        }\n      },\n      \"required\": [\n        \"enable\"\n      ]\n    },\n    \"WidgetConfig\": {\n      \"description\": \"Widget configuration\",\n      \"oneOf\": [\n        {\n          \"title\": \"Applications\",\n          \"description\": \"Applications widget configuration\",\n          \"type\": \"object\",\n          \"properties\": {\n            \"Applications\": {\n              \"$ref\": \"#/$defs/ApplicationsConfig\"\n            }\n          },\n          \"additionalProperties\": false,\n          \"required\": [\n            \"Applications\"\n          ]\n        },\n        {\n          \"title\": \"Battery\",\n          \"description\": \"Battery widget configuration\",\n          \"type\": \"object\",\n          \"properties\": {\n            \"Battery\": {\n              \"$ref\": \"#/$defs/BatteryConfig\"\n            }\n          },\n          \"additionalProperties\": false,\n          \"required\": [\n            \"Battery\"\n          ]\n        },\n        {\n          \"title\": \"Cpu\",\n          \"description\": \"CPU widget configuration\",\n          \"type\": \"object\",\n          \"properties\": {\n            \"Cpu\": {\n              \"$ref\": \"#/$defs/CpuConfig\"\n            }\n          },\n          \"additionalProperties\": false,\n          \"required\": [\n            \"Cpu\"\n          ]\n        },\n        {\n          \"title\": \"Date\",\n          \"description\": \"Date widget configuration\",\n          \"type\": \"object\",\n          \"properties\": {\n            \"Date\": {\n              \"$ref\": \"#/$defs/DateConfig\"\n            }\n          },\n          \"additionalProperties\": false,\n          \"required\": [\n            \"Date\"\n          ]\n        },\n        {\n          \"title\": \"Keyboard\",\n          \"description\": \"Keyboard widget configuration\",\n          \"type\": \"object\",\n          \"properties\": {\n            \"Keyboard\": {\n              \"$ref\": \"#/$defs/KeyboardConfig\"\n            }\n          },\n          \"additionalProperties\": false,\n          \"required\": [\n            \"Keyboard\"\n          ]\n        },\n        {\n          \"title\": \"Komorebi\",\n          \"description\": \"Komorebi widget configuration\",\n          \"type\": \"object\",\n          \"properties\": {\n            \"Komorebi\": {\n              \"$ref\": \"#/$defs/KomorebiConfig\"\n            }\n          },\n          \"additionalProperties\": false,\n          \"required\": [\n            \"Komorebi\"\n          ]\n        },\n        {\n          \"title\": \"Media\",\n          \"description\": \"Media widget configuration\",\n          \"type\": \"object\",\n          \"properties\": {\n            \"Media\": {\n              \"$ref\": \"#/$defs/MediaConfig\"\n            }\n          },\n          \"additionalProperties\": false,\n          \"required\": [\n            \"Media\"\n          ]\n        },\n        {\n          \"title\": \"Memory\",\n          \"description\": \"Memory widget configuration\",\n          \"type\": \"object\",\n          \"properties\": {\n            \"Memory\": {\n              \"$ref\": \"#/$defs/MemoryConfig\"\n            }\n          },\n          \"additionalProperties\": false,\n          \"required\": [\n            \"Memory\"\n          ]\n        },\n        {\n          \"title\": \"Network\",\n          \"description\": \"Network widget configuration\",\n          \"type\": \"object\",\n          \"properties\": {\n            \"Network\": {\n              \"$ref\": \"#/$defs/NetworkConfig\"\n            }\n          },\n          \"additionalProperties\": false,\n          \"required\": [\n            \"Network\"\n          ]\n        },\n        {\n          \"title\": \"Storage\",\n          \"description\": \"Storage widget configuration\",\n          \"type\": \"object\",\n          \"properties\": {\n            \"Storage\": {\n              \"$ref\": \"#/$defs/StorageConfig\"\n            }\n          },\n          \"additionalProperties\": false,\n          \"required\": [\n            \"Storage\"\n          ]\n        },\n        {\n          \"title\": \"Time\",\n          \"description\": \"Time widget configuration\",\n          \"type\": \"object\",\n          \"properties\": {\n            \"Time\": {\n              \"$ref\": \"#/$defs/TimeConfig\"\n            }\n          },\n          \"additionalProperties\": false,\n          \"required\": [\n            \"Time\"\n          ]\n        },\n        {\n          \"title\": \"Update\",\n          \"description\": \"Update widget configuration\",\n          \"type\": \"object\",\n          \"properties\": {\n            \"Update\": {\n              \"$ref\": \"#/$defs/UpdateConfig\"\n            }\n          },\n          \"additionalProperties\": false,\n          \"required\": [\n            \"Update\"\n          ]\n        }\n      ]\n    },\n    \"WindowKind\": {\n      \"description\": \"Window kind\",\n      \"oneOf\": [\n        {\n          \"description\": \"Single window\",\n          \"type\": \"string\",\n          \"const\": \"Single\"\n        },\n        {\n          \"description\": \"Stack container\",\n          \"type\": \"string\",\n          \"const\": \"Stack\"\n        },\n        {\n          \"description\": \"Monocle container\",\n          \"type\": \"string\",\n          \"const\": \"Monocle\"\n        },\n        {\n          \"description\": \"Unfocused window\",\n          \"type\": \"string\",\n          \"const\": \"Unfocused\"\n        },\n        {\n          \"description\": \"Unfocused locked container\",\n          \"type\": \"string\",\n          \"const\": \"UnfocusedLocked\"\n        },\n        {\n          \"description\": \"Floating window\",\n          \"type\": \"string\",\n          \"const\": \"Floating\"\n        }\n      ]\n    },\n    \"WorkspacesDisplayFormat\": {\n      \"description\": \"Workspaces display format\",\n      \"anyOf\": [\n        {\n          \"description\": \"Show all icons only\",\n          \"type\": \"string\",\n          \"const\": \"AllIcons\"\n        },\n        {\n          \"description\": \"Show both all icons and text\",\n          \"type\": \"string\",\n          \"const\": \"AllIconsAndText\"\n        },\n        {\n          \"description\": \"Show all icons and text for the selected element, and all icons on the rest\",\n          \"type\": \"string\",\n          \"const\": \"AllIconsAndTextOnSelected\"\n        },\n        {\n          \"$ref\": \"#/$defs/DisplayFormat\"\n        }\n      ]\n    }\n  }\n}\n"
  },
  {
    "path": "schema.json",
    "content": "{\n  \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n  \"title\": \"StaticConfig\",\n  \"description\": \"The `komorebi.json` static configuration file reference for `v0.1.40`\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"animation\": {\n      \"description\": \"Animations configuration options\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/$defs/AnimationsConfig\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ]\n    },\n    \"app_specific_configuration_path\": {\n      \"description\": \"Path to applications.json from komorebi-application-specific-configurations\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/$defs/AppSpecificConfigurationPath\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ]\n    },\n    \"bar_configurations\": {\n      \"description\": \"Komorebi status bar configuration files for multiple instances on different monitors\",\n      \"type\": [\n        \"array\",\n        \"null\"\n      ],\n      \"items\": {\n        \"$ref\": \"#/$defs/PathBuf\"\n      }\n    },\n    \"border\": {\n      \"description\": \"Display window borders\",\n      \"type\": [\n        \"boolean\",\n        \"null\"\n      ],\n      \"default\": true\n    },\n    \"border_colours\": {\n      \"description\": \"Window border colours for different container types (has no effect if [`theme`] is defined)\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/$defs/BorderColours\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ]\n    },\n    \"border_implementation\": {\n      \"description\": \"Window border implementation\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/$defs/BorderImplementation\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ],\n      \"default\": \"Komorebi\"\n    },\n    \"border_offset\": {\n      \"description\": \"Offset of window borders\",\n      \"type\": [\n        \"integer\",\n        \"null\"\n      ],\n      \"format\": \"int32\",\n      \"default\": -1\n    },\n    \"border_overflow_applications\": {\n      \"description\": \"Identify border overflow applications\",\n      \"type\": [\n        \"array\",\n        \"null\"\n      ],\n      \"items\": {\n        \"$ref\": \"#/$defs/MatchingRule\"\n      }\n    },\n    \"border_style\": {\n      \"description\": \"Window border style\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/$defs/BorderStyle\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ],\n      \"default\": \"System\"\n    },\n    \"border_width\": {\n      \"description\": \"Width of window borders\",\n      \"type\": [\n        \"integer\",\n        \"null\"\n      ],\n      \"format\": \"int32\",\n      \"default\": 8\n    },\n    \"border_z_order\": {\n      \"description\": \"DEPRECATED from v0.1.31: no longer required\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/$defs/ZOrder\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ],\n      \"deprecated\": true\n    },\n    \"cross_boundary_behaviour\": {\n      \"description\": \"Determine what happens when an action is called on a window at a monitor boundary\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/$defs/CrossBoundaryBehaviour\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ],\n      \"default\": \"Monitor\"\n    },\n    \"cross_monitor_move_behaviour\": {\n      \"description\": \"Determine what happens when a window is moved across a monitor boundary\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/$defs/MoveBehaviour\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ],\n      \"default\": \"Swap\"\n    },\n    \"default_container_padding\": {\n      \"description\": \"Global default container padding\",\n      \"type\": [\n        \"integer\",\n        \"null\"\n      ],\n      \"format\": \"int32\",\n      \"default\": 10\n    },\n    \"default_workspace_padding\": {\n      \"description\": \"Global default workspace padding\",\n      \"type\": [\n        \"integer\",\n        \"null\"\n      ],\n      \"format\": \"int32\",\n      \"default\": 10\n    },\n    \"display_index_preferences\": {\n      \"description\": \"Set display index preferences\",\n      \"type\": [\n        \"object\",\n        \"null\"\n      ],\n      \"additionalProperties\": false,\n      \"patternProperties\": {\n        \"^\\\\d+$\": {\n          \"type\": \"string\"\n        }\n      }\n    },\n    \"float_override\": {\n      \"description\": \"Enable or disable float override, which makes it so every new window opens in floating mode\",\n      \"type\": [\n        \"boolean\",\n        \"null\"\n      ],\n      \"default\": false\n    },\n    \"float_override_placement\": {\n      \"description\": \"Determines the `Placement` to be used when spawning a window with float override active\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/$defs/Placement\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ]\n    },\n    \"float_rule_placement\": {\n      \"description\": \"Determines the `Placement` to be used when spawning a window that matches a\\n`floating_applications` rule\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/$defs/Placement\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ]\n    },\n    \"floating_applications\": {\n      \"description\": \"Identify applications which should be managed as floating windows\",\n      \"type\": [\n        \"array\",\n        \"null\"\n      ],\n      \"items\": {\n        \"$ref\": \"#/$defs/MatchingRule\"\n      }\n    },\n    \"floating_layer_behaviour\": {\n      \"description\": \"Determines what happens on a new window when on the `FloatingLayer`\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/$defs/FloatingLayerBehaviour\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ],\n      \"default\": \"Tile\"\n    },\n    \"floating_layer_placement\": {\n      \"description\": \"Determines the `Placement` to be used when spawning a window on the floating layer with the\\n`FloatingLayerBehaviour` set to `FloatingLayerBehaviour::Float`\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/$defs/Placement\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ],\n      \"default\": \"Center\"\n    },\n    \"floating_window_aspect_ratio\": {\n      \"description\": \"Aspect ratio to resize with when toggling floating mode for a window\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/$defs/AspectRatio\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ]\n    },\n    \"focus_follows_mouse\": {\n      \"description\": \"END OF LIFE FEATURE: Use https://github.com/LGUG2Z/masir instead\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/$defs/FocusFollowsMouseImplementation\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ],\n      \"deprecated\": true\n    },\n    \"global_work_area_offset\": {\n      \"description\": \"Global work area (space used for tiling) offset\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/$defs/Rect\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ]\n    },\n    \"ignore_rules\": {\n      \"description\": \"Individual window floating rules\",\n      \"type\": [\n        \"array\",\n        \"null\"\n      ],\n      \"items\": {\n        \"$ref\": \"#/$defs/MatchingRule\"\n      }\n    },\n    \"invisible_borders\": {\n      \"description\": \"DEPRECATED from v0.1.22: no longer required\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/$defs/Rect\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ],\n      \"deprecated\": true\n    },\n    \"layered_applications\": {\n      \"description\": \"Identify applications that have the `WS_EX_LAYERED` extended window style\",\n      \"type\": [\n        \"array\",\n        \"null\"\n      ],\n      \"items\": {\n        \"$ref\": \"#/$defs/MatchingRule\"\n      }\n    },\n    \"manage_rules\": {\n      \"description\": \"Individual window force-manage rules\",\n      \"type\": [\n        \"array\",\n        \"null\"\n      ],\n      \"items\": {\n        \"$ref\": \"#/$defs/MatchingRule\"\n      }\n    },\n    \"minimum_window_height\": {\n      \"description\": \"DISCOURAGED: Minimum height for a window to be eligible for tiling\",\n      \"type\": [\n        \"integer\",\n        \"null\"\n      ],\n      \"format\": \"int32\"\n    },\n    \"minimum_window_width\": {\n      \"description\": \"DISCOURAGED: Minimum width for a window to be eligible for tiling\",\n      \"type\": [\n        \"integer\",\n        \"null\"\n      ],\n      \"format\": \"int32\"\n    },\n    \"monitor_index_preferences\": {\n      \"description\": \"Set monitor index preferences\",\n      \"type\": [\n        \"object\",\n        \"null\"\n      ],\n      \"additionalProperties\": false,\n      \"patternProperties\": {\n        \"^\\\\d+$\": {\n          \"$ref\": \"#/$defs/Rect\"\n        }\n      }\n    },\n    \"monitors\": {\n      \"description\": \"Monitor and workspace configurations\",\n      \"type\": [\n        \"array\",\n        \"null\"\n      ],\n      \"items\": {\n        \"$ref\": \"#/$defs/MonitorConfig\"\n      }\n    },\n    \"mouse_follows_focus\": {\n      \"description\": \"Enable or disable mouse follows focus\",\n      \"type\": [\n        \"boolean\",\n        \"null\"\n      ],\n      \"default\": true\n    },\n    \"object_name_change_applications\": {\n      \"description\": \"Identify applications that send `EVENT_OBJECT_NAMECHANGE` on launch (very rare)\",\n      \"type\": [\n        \"array\",\n        \"null\"\n      ],\n      \"items\": {\n        \"$ref\": \"#/$defs/MatchingRule\"\n      }\n    },\n    \"object_name_change_title_ignore_list\": {\n      \"description\": \"Do not process `EVENT_OBJECT_NAMECHANGE` events as Show events for identified applications matching these title regexes\",\n      \"type\": [\n        \"array\",\n        \"null\"\n      ],\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"remove_titlebar_applications\": {\n      \"description\": \"HEAVILY DISCOURAGED: Identify applications for which komorebi should forcibly remove title bars\",\n      \"type\": [\n        \"array\",\n        \"null\"\n      ],\n      \"items\": {\n        \"$ref\": \"#/$defs/MatchingRule\"\n      }\n    },\n    \"resize_delta\": {\n      \"description\": \"Delta to resize windows by\",\n      \"type\": [\n        \"integer\",\n        \"null\"\n      ],\n      \"format\": \"int32\",\n      \"default\": 50\n    },\n    \"slow_application_compensation_time\": {\n      \"description\": \"How long to wait when compensating for slow applications, in milliseconds\",\n      \"type\": [\n        \"integer\",\n        \"null\"\n      ],\n      \"format\": \"uint64\",\n      \"default\": 20,\n      \"minimum\": 0\n    },\n    \"slow_application_identifiers\": {\n      \"description\": \"Identify applications which are slow to send initial event notifications\",\n      \"type\": [\n        \"array\",\n        \"null\"\n      ],\n      \"items\": {\n        \"$ref\": \"#/$defs/MatchingRule\"\n      }\n    },\n    \"stackbar\": {\n      \"description\": \"Stackbar configuration options\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/$defs/StackbarConfig\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ]\n    },\n    \"theme\": {\n      \"description\": \"Theme configuration options\\n\\nIf a theme is specified, `border_colours` will have no effect\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/$defs/KomorebiTheme\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ]\n    },\n    \"toggle_float_placement\": {\n      \"description\": \"Determines the placement of a new window when toggling to float\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/$defs/Placement\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ],\n      \"default\": \"CenterAndResize\"\n    },\n    \"transparency\": {\n      \"description\": \"Add transparency to unfocused windows\",\n      \"type\": [\n        \"boolean\",\n        \"null\"\n      ],\n      \"default\": false\n    },\n    \"transparency_alpha\": {\n      \"description\": \"Alpha value for unfocused window transparency [[0-255]]\",\n      \"type\": [\n        \"integer\",\n        \"null\"\n      ],\n      \"format\": \"uint8\",\n      \"default\": 200,\n      \"maximum\": 255,\n      \"minimum\": 0\n    },\n    \"transparency_ignore_rules\": {\n      \"description\": \"Individual window transparency ignore rules\",\n      \"type\": [\n        \"array\",\n        \"null\"\n      ],\n      \"items\": {\n        \"$ref\": \"#/$defs/MatchingRule\"\n      }\n    },\n    \"tray_and_multi_window_applications\": {\n      \"description\": \"Identify tray and multi-window applications\",\n      \"type\": [\n        \"array\",\n        \"null\"\n      ],\n      \"items\": {\n        \"$ref\": \"#/$defs/MatchingRule\"\n      }\n    },\n    \"unmanaged_window_operation_behaviour\": {\n      \"description\": \"Determine what happens when commands are sent while an unmanaged window is in the foreground\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/$defs/OperationBehaviour\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ],\n      \"default\": \"Op\"\n    },\n    \"window_container_behaviour\": {\n      \"description\": \"Determine what happens when a new window is opened\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/$defs/WindowContainerBehaviour\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ],\n      \"default\": \"Create\"\n    },\n    \"window_handling_behaviour\": {\n      \"description\": \"Which Windows API behaviour to use when manipulating windows\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/$defs/WindowHandlingBehaviour\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ],\n      \"default\": \"Sync\"\n    },\n    \"window_hiding_behaviour\": {\n      \"description\": \"Which Windows signal to use when hiding windows\",\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/$defs/HidingBehaviour\"\n        },\n        {\n          \"type\": \"null\"\n        }\n      ],\n      \"default\": \"Cloak\"\n    }\n  },\n  \"$defs\": {\n    \"AnimationStyle\": {\n      \"description\": \"Mathematical function which describes the rate at which a value changes\",\n      \"oneOf\": [\n        {\n          \"description\": \"Linear\",\n          \"type\": \"string\",\n          \"const\": \"Linear\"\n        },\n        {\n          \"description\": \"Ease in sine\",\n          \"type\": \"string\",\n          \"const\": \"EaseInSine\"\n        },\n        {\n          \"description\": \"Ease out sine\",\n          \"type\": \"string\",\n          \"const\": \"EaseOutSine\"\n        },\n        {\n          \"description\": \"Ease in out sine\",\n          \"type\": \"string\",\n          \"const\": \"EaseInOutSine\"\n        },\n        {\n          \"description\": \"Ease in quad\",\n          \"type\": \"string\",\n          \"const\": \"EaseInQuad\"\n        },\n        {\n          \"description\": \"Ease out quad\",\n          \"type\": \"string\",\n          \"const\": \"EaseOutQuad\"\n        },\n        {\n          \"description\": \"Ease in out quad\",\n          \"type\": \"string\",\n          \"const\": \"EaseInOutQuad\"\n        },\n        {\n          \"description\": \"Ease in cubic\",\n          \"type\": \"string\",\n          \"const\": \"EaseInCubic\"\n        },\n        {\n          \"description\": \"Ease out cubic\",\n          \"type\": \"string\",\n          \"const\": \"EaseOutCubic\"\n        },\n        {\n          \"description\": \"Ease in out cubic\",\n          \"type\": \"string\",\n          \"const\": \"EaseInOutCubic\"\n        },\n        {\n          \"description\": \"Ease in quart\",\n          \"type\": \"string\",\n          \"const\": \"EaseInQuart\"\n        },\n        {\n          \"description\": \"Ease out quart\",\n          \"type\": \"string\",\n          \"const\": \"EaseOutQuart\"\n        },\n        {\n          \"description\": \"Ease in out quart\",\n          \"type\": \"string\",\n          \"const\": \"EaseInOutQuart\"\n        },\n        {\n          \"description\": \"Ease in quint\",\n          \"type\": \"string\",\n          \"const\": \"EaseInQuint\"\n        },\n        {\n          \"description\": \"Ease out quint\",\n          \"type\": \"string\",\n          \"const\": \"EaseOutQuint\"\n        },\n        {\n          \"description\": \"Ease in out quint\",\n          \"type\": \"string\",\n          \"const\": \"EaseInOutQuint\"\n        },\n        {\n          \"description\": \"Ease in expo\",\n          \"type\": \"string\",\n          \"const\": \"EaseInExpo\"\n        },\n        {\n          \"description\": \"Ease out expo\",\n          \"type\": \"string\",\n          \"const\": \"EaseOutExpo\"\n        },\n        {\n          \"description\": \"Ease in out expo\",\n          \"type\": \"string\",\n          \"const\": \"EaseInOutExpo\"\n        },\n        {\n          \"description\": \"Ease in circ\",\n          \"type\": \"string\",\n          \"const\": \"EaseInCirc\"\n        },\n        {\n          \"description\": \"Ease out circ\",\n          \"type\": \"string\",\n          \"const\": \"EaseOutCirc\"\n        },\n        {\n          \"description\": \"Ease in out circ\",\n          \"type\": \"string\",\n          \"const\": \"EaseInOutCirc\"\n        },\n        {\n          \"description\": \"Ease in back\",\n          \"type\": \"string\",\n          \"const\": \"EaseInBack\"\n        },\n        {\n          \"description\": \"Ease out back\",\n          \"type\": \"string\",\n          \"const\": \"EaseOutBack\"\n        },\n        {\n          \"description\": \"Ease in out back\",\n          \"type\": \"string\",\n          \"const\": \"EaseInOutBack\"\n        },\n        {\n          \"description\": \"Ease in elastic\",\n          \"type\": \"string\",\n          \"const\": \"EaseInElastic\"\n        },\n        {\n          \"description\": \"Ease out elastic\",\n          \"type\": \"string\",\n          \"const\": \"EaseOutElastic\"\n        },\n        {\n          \"description\": \"Ease in out elastic\",\n          \"type\": \"string\",\n          \"const\": \"EaseInOutElastic\"\n        },\n        {\n          \"description\": \"Ease in bounce\",\n          \"type\": \"string\",\n          \"const\": \"EaseInBounce\"\n        },\n        {\n          \"description\": \"Ease out bounce\",\n          \"type\": \"string\",\n          \"const\": \"EaseOutBounce\"\n        },\n        {\n          \"description\": \"Ease in out bounce\",\n          \"type\": \"string\",\n          \"const\": \"EaseInOutBounce\"\n        },\n        {\n          \"title\": \"CubicBezier\",\n          \"description\": \"Custom Cubic Bézier function\",\n          \"type\": \"object\",\n          \"properties\": {\n            \"CubicBezier\": {\n              \"type\": \"array\",\n              \"maxItems\": 4,\n              \"minItems\": 4,\n              \"prefixItems\": [\n                {\n                  \"type\": \"number\",\n                  \"format\": \"double\"\n                },\n                {\n                  \"type\": \"number\",\n                  \"format\": \"double\"\n                },\n                {\n                  \"type\": \"number\",\n                  \"format\": \"double\"\n                },\n                {\n                  \"type\": \"number\",\n                  \"format\": \"double\"\n                }\n              ]\n            }\n          },\n          \"additionalProperties\": false,\n          \"required\": [\n            \"CubicBezier\"\n          ]\n        }\n      ]\n    },\n    \"AnimationsConfig\": {\n      \"description\": \"Animations configuration options\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"duration\": {\n          \"description\": \"Set the animation duration in ms\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/PerAnimationPrefixConfig2\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": 250\n        },\n        \"enabled\": {\n          \"description\": \"Enable or disable animations\",\n          \"$ref\": \"#/$defs/PerAnimationPrefixConfig\",\n          \"default\": false\n        },\n        \"fps\": {\n          \"description\": \"Set the animation FPS\",\n          \"type\": [\n            \"integer\",\n            \"null\"\n          ],\n          \"format\": \"uint64\",\n          \"default\": 60,\n          \"minimum\": 0\n        },\n        \"style\": {\n          \"description\": \"Set the animation style\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/PerAnimationPrefixConfig3\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Linear\"\n        }\n      },\n      \"required\": [\n        \"enabled\"\n      ]\n    },\n    \"AppSpecificConfigurationPath\": {\n      \"description\": \"Path(s) to application-specific configuration file(s)\",\n      \"anyOf\": [\n        {\n          \"description\": \"A single `applications.json` file\",\n          \"$ref\": \"#/$defs/PathBuf\"\n        },\n        {\n          \"description\": \"Multiple `applications.json` files\",\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/$defs/PathBuf\"\n          }\n        }\n      ]\n    },\n    \"ApplicationIdentifier\": {\n      \"description\": \"Application identifier\",\n      \"oneOf\": [\n        {\n          \"description\": \"Executable name\",\n          \"type\": \"string\",\n          \"const\": \"Exe\"\n        },\n        {\n          \"description\": \"Class\",\n          \"type\": \"string\",\n          \"const\": \"Class\"\n        },\n        {\n          \"description\": \"Window title\",\n          \"type\": \"string\",\n          \"const\": \"Title\"\n        },\n        {\n          \"description\": \"Executable path\",\n          \"type\": \"string\",\n          \"const\": \"Path\"\n        }\n      ]\n    },\n    \"AspectRatio\": {\n      \"description\": \"Aspect ratio for temporarily floating windows\",\n      \"anyOf\": [\n        {\n          \"title\": \"Predefined\",\n          \"description\": \"Predefined aspect ratio\",\n          \"$ref\": \"#/$defs/PredefinedAspectRatio\"\n        },\n        {\n          \"title\": \"Custom\",\n          \"description\": \"Custom W:H aspect ratio\",\n          \"type\": \"array\",\n          \"maxItems\": 2,\n          \"minItems\": 2,\n          \"prefixItems\": [\n            {\n              \"type\": \"integer\",\n              \"format\": \"int32\"\n            },\n            {\n              \"type\": \"integer\",\n              \"format\": \"int32\"\n            }\n          ]\n        }\n      ]\n    },\n    \"Axis\": {\n      \"description\": \"Axis on which to perform an operation\",\n      \"oneOf\": [\n        {\n          \"description\": \"Horizontal axis\",\n          \"type\": \"string\",\n          \"const\": \"Horizontal\"\n        },\n        {\n          \"description\": \"Vertical axis\",\n          \"type\": \"string\",\n          \"const\": \"Vertical\"\n        },\n        {\n          \"description\": \"Both horizontal and vertical axes\",\n          \"type\": \"string\",\n          \"const\": \"HorizontalAndVertical\"\n        }\n      ]\n    },\n    \"Base16\": {\n      \"description\": \"Base 16 colour palette\",\n      \"oneOf\": [\n        {\n          \"description\": \"3024 (https://tinted-theming.github.io/tinted-gallery/#base16-3024)\",\n          \"type\": \"string\",\n          \"const\": \"3024\"\n        },\n        {\n          \"description\": \"Apathy (https://tinted-theming.github.io/tinted-gallery/#base16-apathy)\",\n          \"type\": \"string\",\n          \"const\": \"Apathy\"\n        },\n        {\n          \"description\": \"Apprentice (https://tinted-theming.github.io/tinted-gallery/#base16-apprentice)\",\n          \"type\": \"string\",\n          \"const\": \"Apprentice\"\n        },\n        {\n          \"description\": \"Ashes (https://tinted-theming.github.io/tinted-gallery/#base16-ashes)\",\n          \"type\": \"string\",\n          \"const\": \"Ashes\"\n        },\n        {\n          \"description\": \"Atelier Cave Light (https://tinted-theming.github.io/tinted-gallery/#base16-atelier-cave-light)\",\n          \"type\": \"string\",\n          \"const\": \"AtelierCaveLight\"\n        },\n        {\n          \"description\": \"Atelier Cave (https://tinted-theming.github.io/tinted-gallery/#base16-atelier-cave)\",\n          \"type\": \"string\",\n          \"const\": \"AtelierCave\"\n        },\n        {\n          \"description\": \"Atelier Dune Light (https://tinted-theming.github.io/tinted-gallery/#base16-atelier-dune-light)\",\n          \"type\": \"string\",\n          \"const\": \"AtelierDuneLight\"\n        },\n        {\n          \"description\": \"Atelier Dune (https://tinted-theming.github.io/tinted-gallery/#base16-atelier-dune)\",\n          \"type\": \"string\",\n          \"const\": \"AtelierDune\"\n        },\n        {\n          \"description\": \"Atelier Estuary Light (https://tinted-theming.github.io/tinted-gallery/#base16-atelier-estuary-light)\",\n          \"type\": \"string\",\n          \"const\": \"AtelierEstuaryLight\"\n        },\n        {\n          \"description\": \"Atelier Estuary (https://tinted-theming.github.io/tinted-gallery/#base16-atelier-estuary)\",\n          \"type\": \"string\",\n          \"const\": \"AtelierEstuary\"\n        },\n        {\n          \"description\": \"Atelier Forest Light (https://tinted-theming.github.io/tinted-gallery/#base16-atelier-forest-light)\",\n          \"type\": \"string\",\n          \"const\": \"AtelierForestLight\"\n        },\n        {\n          \"description\": \"Atelier Forest (https://tinted-theming.github.io/tinted-gallery/#base16-atelier-forest)\",\n          \"type\": \"string\",\n          \"const\": \"AtelierForest\"\n        },\n        {\n          \"description\": \"Atelier Heath Light (https://tinted-theming.github.io/tinted-gallery/#base16-atelier-heath-light)\",\n          \"type\": \"string\",\n          \"const\": \"AtelierHeathLight\"\n        },\n        {\n          \"description\": \"Atelier Heath (https://tinted-theming.github.io/tinted-gallery/#base16-atelier-heath)\",\n          \"type\": \"string\",\n          \"const\": \"AtelierHeath\"\n        },\n        {\n          \"description\": \"Atelier Lakeside Light (https://tinted-theming.github.io/tinted-gallery/#base16-atelier-lakeside-light)\",\n          \"type\": \"string\",\n          \"const\": \"AtelierLakesideLight\"\n        },\n        {\n          \"description\": \"Atelier Lakeside (https://tinted-theming.github.io/tinted-gallery/#base16-atelier-lakeside)\",\n          \"type\": \"string\",\n          \"const\": \"AtelierLakeside\"\n        },\n        {\n          \"description\": \"Atelier Plateau Light (https://tinted-theming.github.io/tinted-gallery/#base16-atelier-plateau-light)\",\n          \"type\": \"string\",\n          \"const\": \"AtelierPlateauLight\"\n        },\n        {\n          \"description\": \"Atelier Plateau (https://tinted-theming.github.io/tinted-gallery/#base16-atelier-plateau)\",\n          \"type\": \"string\",\n          \"const\": \"AtelierPlateau\"\n        },\n        {\n          \"description\": \"Atelier Savanna Light (https://tinted-theming.github.io/tinted-gallery/#base16-atelier-savanna-light)\",\n          \"type\": \"string\",\n          \"const\": \"AtelierSavannaLight\"\n        },\n        {\n          \"description\": \"Atelier Savanna (https://tinted-theming.github.io/tinted-gallery/#base16-atelier-savanna)\",\n          \"type\": \"string\",\n          \"const\": \"AtelierSavanna\"\n        },\n        {\n          \"description\": \"Atelier Seaside Light (https://tinted-theming.github.io/tinted-gallery/#base16-atelier-seaside-light)\",\n          \"type\": \"string\",\n          \"const\": \"AtelierSeasideLight\"\n        },\n        {\n          \"description\": \"Atelier Seaside (https://tinted-theming.github.io/tinted-gallery/#base16-atelier-seaside)\",\n          \"type\": \"string\",\n          \"const\": \"AtelierSeaside\"\n        },\n        {\n          \"description\": \"Atelier Sulphurpool Light (https://tinted-theming.github.io/tinted-gallery/#base16-atelier-sulphurpool-light)\",\n          \"type\": \"string\",\n          \"const\": \"AtelierSulphurpoolLight\"\n        },\n        {\n          \"description\": \"Atelier Sulphurpool (https://tinted-theming.github.io/tinted-gallery/#base16-atelier-sulphurpool)\",\n          \"type\": \"string\",\n          \"const\": \"AtelierSulphurpool\"\n        },\n        {\n          \"description\": \"Atlas (https://tinted-theming.github.io/tinted-gallery/#base16-atlas)\",\n          \"type\": \"string\",\n          \"const\": \"Atlas\"\n        },\n        {\n          \"description\": \"Ayu Dark (https://tinted-theming.github.io/tinted-gallery/#base16-ayu-dark)\",\n          \"type\": \"string\",\n          \"const\": \"AyuDark\"\n        },\n        {\n          \"description\": \"Ayu Light (https://tinted-theming.github.io/tinted-gallery/#base16-ayu-light)\",\n          \"type\": \"string\",\n          \"const\": \"AyuLight\"\n        },\n        {\n          \"description\": \"Ayu Mirage (https://tinted-theming.github.io/tinted-gallery/#base16-ayu-mirage)\",\n          \"type\": \"string\",\n          \"const\": \"AyuMirage\"\n        },\n        {\n          \"description\": \"Aztec (https://tinted-theming.github.io/tinted-gallery/#base16-aztec)\",\n          \"type\": \"string\",\n          \"const\": \"Aztec\"\n        },\n        {\n          \"description\": \"Bespin (https://tinted-theming.github.io/tinted-gallery/#base16-bespin)\",\n          \"type\": \"string\",\n          \"const\": \"Bespin\"\n        },\n        {\n          \"description\": \"Black Metal Bathory (https://tinted-theming.github.io/tinted-gallery/#base16-black-metal-bathory)\",\n          \"type\": \"string\",\n          \"const\": \"BlackMetalBathory\"\n        },\n        {\n          \"description\": \"Black Metal Burzum (https://tinted-theming.github.io/tinted-gallery/#base16-black-metal-burzum)\",\n          \"type\": \"string\",\n          \"const\": \"BlackMetalBurzum\"\n        },\n        {\n          \"description\": \"Black Metal Dark Funeral (https://tinted-theming.github.io/tinted-gallery/#base16-black-metal-dark-funeral)\",\n          \"type\": \"string\",\n          \"const\": \"BlackMetalDarkFuneral\"\n        },\n        {\n          \"description\": \"Black Metal Gorgoroth (https://tinted-theming.github.io/tinted-gallery/#base16-black-metal-gorgoroth)\",\n          \"type\": \"string\",\n          \"const\": \"BlackMetalGorgoroth\"\n        },\n        {\n          \"description\": \"Black Metal Immortal (https://tinted-theming.github.io/tinted-gallery/#base16-black-metal-immortal)\",\n          \"type\": \"string\",\n          \"const\": \"BlackMetalImmortal\"\n        },\n        {\n          \"description\": \"Black Metal Khold (https://tinted-theming.github.io/tinted-gallery/#base16-black-metal-khold)\",\n          \"type\": \"string\",\n          \"const\": \"BlackMetalKhold\"\n        },\n        {\n          \"description\": \"Black Metal Marduk (https://tinted-theming.github.io/tinted-gallery/#base16-black-metal-marduk)\",\n          \"type\": \"string\",\n          \"const\": \"BlackMetalMarduk\"\n        },\n        {\n          \"description\": \"Black Metal Mayhem (https://tinted-theming.github.io/tinted-gallery/#base16-black-metal-mayhem)\",\n          \"type\": \"string\",\n          \"const\": \"BlackMetalMayhem\"\n        },\n        {\n          \"description\": \"Black Metal Nile (https://tinted-theming.github.io/tinted-gallery/#base16-black-metal-nile)\",\n          \"type\": \"string\",\n          \"const\": \"BlackMetalNile\"\n        },\n        {\n          \"description\": \"Black Metal Venom (https://tinted-theming.github.io/tinted-gallery/#base16-black-metal-venom)\",\n          \"type\": \"string\",\n          \"const\": \"BlackMetalVenom\"\n        },\n        {\n          \"description\": \"Black Metal (https://tinted-theming.github.io/tinted-gallery/#base16-black-metal)\",\n          \"type\": \"string\",\n          \"const\": \"BlackMetal\"\n        },\n        {\n          \"description\": \"Blueforest (https://tinted-theming.github.io/tinted-gallery/#base16-blueforest)\",\n          \"type\": \"string\",\n          \"const\": \"Blueforest\"\n        },\n        {\n          \"description\": \"Blueish (https://tinted-theming.github.io/tinted-gallery/#base16-blueish)\",\n          \"type\": \"string\",\n          \"const\": \"Blueish\"\n        },\n        {\n          \"description\": \"Brewer (https://tinted-theming.github.io/tinted-gallery/#base16-brewer)\",\n          \"type\": \"string\",\n          \"const\": \"Brewer\"\n        },\n        {\n          \"description\": \"Bright (https://tinted-theming.github.io/tinted-gallery/#base16-bright)\",\n          \"type\": \"string\",\n          \"const\": \"Bright\"\n        },\n        {\n          \"description\": \"Brogrammer (https://tinted-theming.github.io/tinted-gallery/#base16-brogrammer)\",\n          \"type\": \"string\",\n          \"const\": \"Brogrammer\"\n        },\n        {\n          \"description\": \"Brushtrees Dark (https://tinted-theming.github.io/tinted-gallery/#base16-brushtrees-dark)\",\n          \"type\": \"string\",\n          \"const\": \"BrushtreesDark\"\n        },\n        {\n          \"description\": \"Brushtrees (https://tinted-theming.github.io/tinted-gallery/#base16-brushtrees)\",\n          \"type\": \"string\",\n          \"const\": \"Brushtrees\"\n        },\n        {\n          \"description\": \"Caroline (https://tinted-theming.github.io/tinted-gallery/#base16-caroline)\",\n          \"type\": \"string\",\n          \"const\": \"Caroline\"\n        },\n        {\n          \"description\": \"Catppuccin Frappe (https://tinted-theming.github.io/tinted-gallery/#base16-catppuccin-frappe)\",\n          \"type\": \"string\",\n          \"const\": \"CatppuccinFrappe\"\n        },\n        {\n          \"description\": \"Catppuccin Latte (https://tinted-theming.github.io/tinted-gallery/#base16-catppuccin-latte)\",\n          \"type\": \"string\",\n          \"const\": \"CatppuccinLatte\"\n        },\n        {\n          \"description\": \"Catppuccin Macchiato (https://tinted-theming.github.io/tinted-gallery/#base16-catppuccin-macchiato)\",\n          \"type\": \"string\",\n          \"const\": \"CatppuccinMacchiato\"\n        },\n        {\n          \"description\": \"Catppuccin Mocha (https://tinted-theming.github.io/tinted-gallery/#base16-catppuccin-mocha)\",\n          \"type\": \"string\",\n          \"const\": \"CatppuccinMocha\"\n        },\n        {\n          \"description\": \"Chalk (https://tinted-theming.github.io/tinted-gallery/#base16-chalk)\",\n          \"type\": \"string\",\n          \"const\": \"Chalk\"\n        },\n        {\n          \"description\": \"Circus (https://tinted-theming.github.io/tinted-gallery/#base16-circus)\",\n          \"type\": \"string\",\n          \"const\": \"Circus\"\n        },\n        {\n          \"description\": \"Classic Dark (https://tinted-theming.github.io/tinted-gallery/#base16-classic-dark)\",\n          \"type\": \"string\",\n          \"const\": \"ClassicDark\"\n        },\n        {\n          \"description\": \"Classic Light (https://tinted-theming.github.io/tinted-gallery/#base16-classic-light)\",\n          \"type\": \"string\",\n          \"const\": \"ClassicLight\"\n        },\n        {\n          \"description\": \"Codeschool (https://tinted-theming.github.io/tinted-gallery/#base16-codeschool)\",\n          \"type\": \"string\",\n          \"const\": \"Codeschool\"\n        },\n        {\n          \"description\": \"Colors (https://tinted-theming.github.io/tinted-gallery/#base16-colors)\",\n          \"type\": \"string\",\n          \"const\": \"Colors\"\n        },\n        {\n          \"description\": \"Cupcake (https://tinted-theming.github.io/tinted-gallery/#base16-cupcake)\",\n          \"type\": \"string\",\n          \"const\": \"Cupcake\"\n        },\n        {\n          \"description\": \"Cupertino (https://tinted-theming.github.io/tinted-gallery/#base16-cupertino)\",\n          \"type\": \"string\",\n          \"const\": \"Cupertino\"\n        },\n        {\n          \"description\": \"Da One Black (https://tinted-theming.github.io/tinted-gallery/#base16-da-one-black)\",\n          \"type\": \"string\",\n          \"const\": \"DaOneBlack\"\n        },\n        {\n          \"description\": \"Da One Gray (https://tinted-theming.github.io/tinted-gallery/#base16-da-one-gray)\",\n          \"type\": \"string\",\n          \"const\": \"DaOneGray\"\n        },\n        {\n          \"description\": \"Da One Ocean (https://tinted-theming.github.io/tinted-gallery/#base16-da-one-ocean)\",\n          \"type\": \"string\",\n          \"const\": \"DaOneOcean\"\n        },\n        {\n          \"description\": \"Da One Paper (https://tinted-theming.github.io/tinted-gallery/#base16-da-one-paper)\",\n          \"type\": \"string\",\n          \"const\": \"DaOnePaper\"\n        },\n        {\n          \"description\": \"Da One Sea (https://tinted-theming.github.io/tinted-gallery/#base16-da-one-sea)\",\n          \"type\": \"string\",\n          \"const\": \"DaOneSea\"\n        },\n        {\n          \"description\": \"Da One White (https://tinted-theming.github.io/tinted-gallery/#base16-da-one-white)\",\n          \"type\": \"string\",\n          \"const\": \"DaOneWhite\"\n        },\n        {\n          \"description\": \"Danqing Light (https://tinted-theming.github.io/tinted-gallery/#base16-danqing-light)\",\n          \"type\": \"string\",\n          \"const\": \"DanqingLight\"\n        },\n        {\n          \"description\": \"Danqing (https://tinted-theming.github.io/tinted-gallery/#base16-danqing)\",\n          \"type\": \"string\",\n          \"const\": \"Danqing\"\n        },\n        {\n          \"description\": \"Darcula (https://tinted-theming.github.io/tinted-gallery/#base16-darcula)\",\n          \"type\": \"string\",\n          \"const\": \"Darcula\"\n        },\n        {\n          \"description\": \"Darkmoss (https://tinted-theming.github.io/tinted-gallery/#base16-darkmoss)\",\n          \"type\": \"string\",\n          \"const\": \"Darkmoss\"\n        },\n        {\n          \"description\": \"Darktooth (https://tinted-theming.github.io/tinted-gallery/#base16-darktooth)\",\n          \"type\": \"string\",\n          \"const\": \"Darktooth\"\n        },\n        {\n          \"description\": \"Darkviolet (https://tinted-theming.github.io/tinted-gallery/#base16-darkviolet)\",\n          \"type\": \"string\",\n          \"const\": \"Darkviolet\"\n        },\n        {\n          \"description\": \"Decaf (https://tinted-theming.github.io/tinted-gallery/#base16-decaf)\",\n          \"type\": \"string\",\n          \"const\": \"Decaf\"\n        },\n        {\n          \"description\": \"Default Dark (https://tinted-theming.github.io/tinted-gallery/#base16-default-dark)\",\n          \"type\": \"string\",\n          \"const\": \"DefaultDark\"\n        },\n        {\n          \"description\": \"Default Light (https://tinted-theming.github.io/tinted-gallery/#base16-default-light)\",\n          \"type\": \"string\",\n          \"const\": \"DefaultLight\"\n        },\n        {\n          \"description\": \"Dirtysea (https://tinted-theming.github.io/tinted-gallery/#base16-dirtysea)\",\n          \"type\": \"string\",\n          \"const\": \"Dirtysea\"\n        },\n        {\n          \"description\": \"Dracula (https://tinted-theming.github.io/tinted-gallery/#base16-dracula)\",\n          \"type\": \"string\",\n          \"const\": \"Dracula\"\n        },\n        {\n          \"description\": \"Edge Dark (https://tinted-theming.github.io/tinted-gallery/#base16-edge-dark)\",\n          \"type\": \"string\",\n          \"const\": \"EdgeDark\"\n        },\n        {\n          \"description\": \"Edge Light (https://tinted-theming.github.io/tinted-gallery/#base16-edge-light)\",\n          \"type\": \"string\",\n          \"const\": \"EdgeLight\"\n        },\n        {\n          \"description\": \"Eighties (https://tinted-theming.github.io/tinted-gallery/#base16-eighties)\",\n          \"type\": \"string\",\n          \"const\": \"Eighties\"\n        },\n        {\n          \"description\": \"Embers Light (https://tinted-theming.github.io/tinted-gallery/#base16-embers-light)\",\n          \"type\": \"string\",\n          \"const\": \"EmbersLight\"\n        },\n        {\n          \"description\": \"Embers (https://tinted-theming.github.io/tinted-gallery/#base16-embers)\",\n          \"type\": \"string\",\n          \"const\": \"Embers\"\n        },\n        {\n          \"description\": \"Emil (https://tinted-theming.github.io/tinted-gallery/#base16-emil)\",\n          \"type\": \"string\",\n          \"const\": \"Emil\"\n        },\n        {\n          \"description\": \"Equilibrium Dark (https://tinted-theming.github.io/tinted-gallery/#base16-equilibrium-dark)\",\n          \"type\": \"string\",\n          \"const\": \"EquilibriumDark\"\n        },\n        {\n          \"description\": \"Equilibrium Gray Dark (https://tinted-theming.github.io/tinted-gallery/#base16-equilibrium-gray-dark)\",\n          \"type\": \"string\",\n          \"const\": \"EquilibriumGrayDark\"\n        },\n        {\n          \"description\": \"Equilibrium Gray Light (https://tinted-theming.github.io/tinted-gallery/#base16-equilibrium-gray-light)\",\n          \"type\": \"string\",\n          \"const\": \"EquilibriumGrayLight\"\n        },\n        {\n          \"description\": \"Equilibrium Light (https://tinted-theming.github.io/tinted-gallery/#base16-equilibrium-light)\",\n          \"type\": \"string\",\n          \"const\": \"EquilibriumLight\"\n        },\n        {\n          \"description\": \"Eris (https://tinted-theming.github.io/tinted-gallery/#base16-eris)\",\n          \"type\": \"string\",\n          \"const\": \"Eris\"\n        },\n        {\n          \"description\": \"Espresso (https://tinted-theming.github.io/tinted-gallery/#base16-espresso)\",\n          \"type\": \"string\",\n          \"const\": \"Espresso\"\n        },\n        {\n          \"description\": \"Eva Dim (https://tinted-theming.github.io/tinted-gallery/#base16-eva-dim)\",\n          \"type\": \"string\",\n          \"const\": \"EvaDim\"\n        },\n        {\n          \"description\": \"Eva (https://tinted-theming.github.io/tinted-gallery/#base16-eva)\",\n          \"type\": \"string\",\n          \"const\": \"Eva\"\n        },\n        {\n          \"description\": \"Evenok Dark (https://tinted-theming.github.io/tinted-gallery/#base16-evenok-dark)\",\n          \"type\": \"string\",\n          \"const\": \"EvenokDark\"\n        },\n        {\n          \"description\": \"Everforest Dark Hard (https://tinted-theming.github.io/tinted-gallery/#base16-everforest-dark-hard)\",\n          \"type\": \"string\",\n          \"const\": \"EverforestDarkHard\"\n        },\n        {\n          \"description\": \"Everforest (https://tinted-theming.github.io/tinted-gallery/#base16-everforest)\",\n          \"type\": \"string\",\n          \"const\": \"Everforest\"\n        },\n        {\n          \"description\": \"Flat (https://tinted-theming.github.io/tinted-gallery/#base16-flat)\",\n          \"type\": \"string\",\n          \"const\": \"Flat\"\n        },\n        {\n          \"description\": \"Framer (https://tinted-theming.github.io/tinted-gallery/#base16-framer)\",\n          \"type\": \"string\",\n          \"const\": \"Framer\"\n        },\n        {\n          \"description\": \"Fruit Soda (https://tinted-theming.github.io/tinted-gallery/#base16-fruit-soda)\",\n          \"type\": \"string\",\n          \"const\": \"FruitSoda\"\n        },\n        {\n          \"description\": \"Gigavolt (https://tinted-theming.github.io/tinted-gallery/#base16-gigavolt)\",\n          \"type\": \"string\",\n          \"const\": \"Gigavolt\"\n        },\n        {\n          \"description\": \"Github (https://tinted-theming.github.io/tinted-gallery/#base16-github)\",\n          \"type\": \"string\",\n          \"const\": \"Github\"\n        },\n        {\n          \"description\": \"Google Dark (https://tinted-theming.github.io/tinted-gallery/#base16-google-dark)\",\n          \"type\": \"string\",\n          \"const\": \"GoogleDark\"\n        },\n        {\n          \"description\": \"Google Light (https://tinted-theming.github.io/tinted-gallery/#base16-google-light)\",\n          \"type\": \"string\",\n          \"const\": \"GoogleLight\"\n        },\n        {\n          \"description\": \"Gotham (https://tinted-theming.github.io/tinted-gallery/#base16-gotham)\",\n          \"type\": \"string\",\n          \"const\": \"Gotham\"\n        },\n        {\n          \"description\": \"Grayscale Dark (https://tinted-theming.github.io/tinted-gallery/#base16-grayscale-dark)\",\n          \"type\": \"string\",\n          \"const\": \"GrayscaleDark\"\n        },\n        {\n          \"description\": \"Grayscale Light (https://tinted-theming.github.io/tinted-gallery/#base16-grayscale-light)\",\n          \"type\": \"string\",\n          \"const\": \"GrayscaleLight\"\n        },\n        {\n          \"description\": \"Greenscreen (https://tinted-theming.github.io/tinted-gallery/#base16-greenscreen)\",\n          \"type\": \"string\",\n          \"const\": \"Greenscreen\"\n        },\n        {\n          \"description\": \"Gruber (https://tinted-theming.github.io/tinted-gallery/#base16-gruber)\",\n          \"type\": \"string\",\n          \"const\": \"Gruber\"\n        },\n        {\n          \"description\": \"Gruvbox Dark Hard (https://tinted-theming.github.io/tinted-gallery/#base16-gruvbox-dark-hard)\",\n          \"type\": \"string\",\n          \"const\": \"GruvboxDarkHard\"\n        },\n        {\n          \"description\": \"Gruvbox Dark Medium (https://tinted-theming.github.io/tinted-gallery/#base16-gruvbox-dark-medium)\",\n          \"type\": \"string\",\n          \"const\": \"GruvboxDarkMedium\"\n        },\n        {\n          \"description\": \"Gruvbox Dark Pale (https://tinted-theming.github.io/tinted-gallery/#base16-gruvbox-dark-pale)\",\n          \"type\": \"string\",\n          \"const\": \"GruvboxDarkPale\"\n        },\n        {\n          \"description\": \"Gruvbox Dark Soft (https://tinted-theming.github.io/tinted-gallery/#base16-gruvbox-dark-soft)\",\n          \"type\": \"string\",\n          \"const\": \"GruvboxDarkSoft\"\n        },\n        {\n          \"description\": \"Gruvbox Light Hard (https://tinted-theming.github.io/tinted-gallery/#base16-gruvbox-light-hard)\",\n          \"type\": \"string\",\n          \"const\": \"GruvboxLightHard\"\n        },\n        {\n          \"description\": \"Gruvbox Light Medium (https://tinted-theming.github.io/tinted-gallery/#base16-gruvbox-light-medium)\",\n          \"type\": \"string\",\n          \"const\": \"GruvboxLightMedium\"\n        },\n        {\n          \"description\": \"Gruvbox Light Soft (https://tinted-theming.github.io/tinted-gallery/#base16-gruvbox-light-soft)\",\n          \"type\": \"string\",\n          \"const\": \"GruvboxLightSoft\"\n        },\n        {\n          \"description\": \"Gruvbox Material Dark Hard (https://tinted-theming.github.io/tinted-gallery/#base16-gruvbox-material-dark-hard)\",\n          \"type\": \"string\",\n          \"const\": \"GruvboxMaterialDarkHard\"\n        },\n        {\n          \"description\": \"Gruvbox Material Dark Medium (https://tinted-theming.github.io/tinted-gallery/#base16-gruvbox-material-dark-medium)\",\n          \"type\": \"string\",\n          \"const\": \"GruvboxMaterialDarkMedium\"\n        },\n        {\n          \"description\": \"Gruvbox Material Dark Soft (https://tinted-theming.github.io/tinted-gallery/#base16-gruvbox-material-dark-soft)\",\n          \"type\": \"string\",\n          \"const\": \"GruvboxMaterialDarkSoft\"\n        },\n        {\n          \"description\": \"Gruvbox Material Light Hard (https://tinted-theming.github.io/tinted-gallery/#base16-gruvbox-material-light-hard)\",\n          \"type\": \"string\",\n          \"const\": \"GruvboxMaterialLightHard\"\n        },\n        {\n          \"description\": \"Gruvbox Material Light Medium (https://tinted-theming.github.io/tinted-gallery/#base16-gruvbox-material-light-medium)\",\n          \"type\": \"string\",\n          \"const\": \"GruvboxMaterialLightMedium\"\n        },\n        {\n          \"description\": \"Gruvbox Material Light Soft (https://tinted-theming.github.io/tinted-gallery/#base16-gruvbox-material-light-soft)\",\n          \"type\": \"string\",\n          \"const\": \"GruvboxMaterialLightSoft\"\n        },\n        {\n          \"description\": \"Hardcore (https://tinted-theming.github.io/tinted-gallery/#base16-hardcore)\",\n          \"type\": \"string\",\n          \"const\": \"Hardcore\"\n        },\n        {\n          \"description\": \"Harmonic16 Dark (https://tinted-theming.github.io/tinted-gallery/#base16-harmonic16-dark)\",\n          \"type\": \"string\",\n          \"const\": \"Harmonic16Dark\"\n        },\n        {\n          \"description\": \"Harmonic16 Light (https://tinted-theming.github.io/tinted-gallery/#base16-harmonic16-light)\",\n          \"type\": \"string\",\n          \"const\": \"Harmonic16Light\"\n        },\n        {\n          \"description\": \"Heetch Light (https://tinted-theming.github.io/tinted-gallery/#base16-heetch-light)\",\n          \"type\": \"string\",\n          \"const\": \"HeetchLight\"\n        },\n        {\n          \"description\": \"Heetch (https://tinted-theming.github.io/tinted-gallery/#base16-heetch)\",\n          \"type\": \"string\",\n          \"const\": \"Heetch\"\n        },\n        {\n          \"description\": \"Helios (https://tinted-theming.github.io/tinted-gallery/#base16-helios)\",\n          \"type\": \"string\",\n          \"const\": \"Helios\"\n        },\n        {\n          \"description\": \"Hopscotch (https://tinted-theming.github.io/tinted-gallery/#base16-hopscotch)\",\n          \"type\": \"string\",\n          \"const\": \"Hopscotch\"\n        },\n        {\n          \"description\": \"Horizon Dark (https://tinted-theming.github.io/tinted-gallery/#base16-horizon-dark)\",\n          \"type\": \"string\",\n          \"const\": \"HorizonDark\"\n        },\n        {\n          \"description\": \"Horizon Light (https://tinted-theming.github.io/tinted-gallery/#base16-horizon-light)\",\n          \"type\": \"string\",\n          \"const\": \"HorizonLight\"\n        },\n        {\n          \"description\": \"Horizon Terminal Dark (https://tinted-theming.github.io/tinted-gallery/#base16-horizon-terminal-dark)\",\n          \"type\": \"string\",\n          \"const\": \"HorizonTerminalDark\"\n        },\n        {\n          \"description\": \"Horizon Terminal Light (https://tinted-theming.github.io/tinted-gallery/#base16-horizon-terminal-light)\",\n          \"type\": \"string\",\n          \"const\": \"HorizonTerminalLight\"\n        },\n        {\n          \"description\": \"Humanoid Dark (https://tinted-theming.github.io/tinted-gallery/#base16-humanoid-dark)\",\n          \"type\": \"string\",\n          \"const\": \"HumanoidDark\"\n        },\n        {\n          \"description\": \"Humanoid Light (https://tinted-theming.github.io/tinted-gallery/#base16-humanoid-light)\",\n          \"type\": \"string\",\n          \"const\": \"HumanoidLight\"\n        },\n        {\n          \"description\": \"Ia Dark (https://tinted-theming.github.io/tinted-gallery/#base16-ia-dark)\",\n          \"type\": \"string\",\n          \"const\": \"IaDark\"\n        },\n        {\n          \"description\": \"Ia Light (https://tinted-theming.github.io/tinted-gallery/#base16-ia-light)\",\n          \"type\": \"string\",\n          \"const\": \"IaLight\"\n        },\n        {\n          \"description\": \"Icy (https://tinted-theming.github.io/tinted-gallery/#base16-icy)\",\n          \"type\": \"string\",\n          \"const\": \"Icy\"\n        },\n        {\n          \"description\": \"Irblack (https://tinted-theming.github.io/tinted-gallery/#base16-irblack)\",\n          \"type\": \"string\",\n          \"const\": \"Irblack\"\n        },\n        {\n          \"description\": \"Isotope (https://tinted-theming.github.io/tinted-gallery/#base16-isotope)\",\n          \"type\": \"string\",\n          \"const\": \"Isotope\"\n        },\n        {\n          \"description\": \"Jabuti (https://tinted-theming.github.io/tinted-gallery/#base16-jabuti)\",\n          \"type\": \"string\",\n          \"const\": \"Jabuti\"\n        },\n        {\n          \"description\": \"Kanagawa (https://tinted-theming.github.io/tinted-gallery/#base16-kanagawa)\",\n          \"type\": \"string\",\n          \"const\": \"Kanagawa\"\n        },\n        {\n          \"description\": \"Katy (https://tinted-theming.github.io/tinted-gallery/#base16-katy)\",\n          \"type\": \"string\",\n          \"const\": \"Katy\"\n        },\n        {\n          \"description\": \"Kimber (https://tinted-theming.github.io/tinted-gallery/#base16-kimber)\",\n          \"type\": \"string\",\n          \"const\": \"Kimber\"\n        },\n        {\n          \"description\": \"Lime (https://tinted-theming.github.io/tinted-gallery/#base16-lime)\",\n          \"type\": \"string\",\n          \"const\": \"Lime\"\n        },\n        {\n          \"description\": \"Macintosh (https://tinted-theming.github.io/tinted-gallery/#base16-macintosh)\",\n          \"type\": \"string\",\n          \"const\": \"Macintosh\"\n        },\n        {\n          \"description\": \"Marrakesh (https://tinted-theming.github.io/tinted-gallery/#base16-marrakesh)\",\n          \"type\": \"string\",\n          \"const\": \"Marrakesh\"\n        },\n        {\n          \"description\": \"Materia (https://tinted-theming.github.io/tinted-gallery/#base16-materia)\",\n          \"type\": \"string\",\n          \"const\": \"Materia\"\n        },\n        {\n          \"description\": \"Material Darker (https://tinted-theming.github.io/tinted-gallery/#base16-material-darker)\",\n          \"type\": \"string\",\n          \"const\": \"MaterialDarker\"\n        },\n        {\n          \"description\": \"Material Lighter (https://tinted-theming.github.io/tinted-gallery/#base16-material-lighter)\",\n          \"type\": \"string\",\n          \"const\": \"MaterialLighter\"\n        },\n        {\n          \"description\": \"Material Palenight (https://tinted-theming.github.io/tinted-gallery/#base16-material-palenight)\",\n          \"type\": \"string\",\n          \"const\": \"MaterialPalenight\"\n        },\n        {\n          \"description\": \"Material Vivid (https://tinted-theming.github.io/tinted-gallery/#base16-material-vivid)\",\n          \"type\": \"string\",\n          \"const\": \"MaterialVivid\"\n        },\n        {\n          \"description\": \"Material (https://tinted-theming.github.io/tinted-gallery/#base16-material)\",\n          \"type\": \"string\",\n          \"const\": \"Material\"\n        },\n        {\n          \"description\": \"Measured Dark (https://tinted-theming.github.io/tinted-gallery/#base16-measured-dark)\",\n          \"type\": \"string\",\n          \"const\": \"MeasuredDark\"\n        },\n        {\n          \"description\": \"Measured Light (https://tinted-theming.github.io/tinted-gallery/#base16-measured-light)\",\n          \"type\": \"string\",\n          \"const\": \"MeasuredLight\"\n        },\n        {\n          \"description\": \"Mellow Purple (https://tinted-theming.github.io/tinted-gallery/#base16-mellow-purple)\",\n          \"type\": \"string\",\n          \"const\": \"MellowPurple\"\n        },\n        {\n          \"description\": \"Mexico Light (https://tinted-theming.github.io/tinted-gallery/#base16-mexico-light)\",\n          \"type\": \"string\",\n          \"const\": \"MexicoLight\"\n        },\n        {\n          \"description\": \"Mocha (https://tinted-theming.github.io/tinted-gallery/#base16-mocha)\",\n          \"type\": \"string\",\n          \"const\": \"Mocha\"\n        },\n        {\n          \"description\": \"Monokai (https://tinted-theming.github.io/tinted-gallery/#base16-monokai)\",\n          \"type\": \"string\",\n          \"const\": \"Monokai\"\n        },\n        {\n          \"description\": \"Moonlight (https://tinted-theming.github.io/tinted-gallery/#base16-moonlight)\",\n          \"type\": \"string\",\n          \"const\": \"Moonlight\"\n        },\n        {\n          \"description\": \"Mountain (https://tinted-theming.github.io/tinted-gallery/#base16-mountain)\",\n          \"type\": \"string\",\n          \"const\": \"Mountain\"\n        },\n        {\n          \"description\": \"Nebula (https://tinted-theming.github.io/tinted-gallery/#base16-nebula)\",\n          \"type\": \"string\",\n          \"const\": \"Nebula\"\n        },\n        {\n          \"description\": \"Nord Light (https://tinted-theming.github.io/tinted-gallery/#base16-nord-light)\",\n          \"type\": \"string\",\n          \"const\": \"NordLight\"\n        },\n        {\n          \"description\": \"Nord (https://tinted-theming.github.io/tinted-gallery/#base16-nord)\",\n          \"type\": \"string\",\n          \"const\": \"Nord\"\n        },\n        {\n          \"description\": \"Nova (https://tinted-theming.github.io/tinted-gallery/#base16-nova)\",\n          \"type\": \"string\",\n          \"const\": \"Nova\"\n        },\n        {\n          \"description\": \"Ocean (https://tinted-theming.github.io/tinted-gallery/#base16-ocean)\",\n          \"type\": \"string\",\n          \"const\": \"Ocean\"\n        },\n        {\n          \"description\": \"Oceanicnext (https://tinted-theming.github.io/tinted-gallery/#base16-oceanicnext)\",\n          \"type\": \"string\",\n          \"const\": \"Oceanicnext\"\n        },\n        {\n          \"description\": \"One Light (https://tinted-theming.github.io/tinted-gallery/#base16-one-light)\",\n          \"type\": \"string\",\n          \"const\": \"OneLight\"\n        },\n        {\n          \"description\": \"Onedark Dark (https://tinted-theming.github.io/tinted-gallery/#base16-onedark-dark)\",\n          \"type\": \"string\",\n          \"const\": \"OnedarkDark\"\n        },\n        {\n          \"description\": \"Onedark (https://tinted-theming.github.io/tinted-gallery/#base16-onedark)\",\n          \"type\": \"string\",\n          \"const\": \"Onedark\"\n        },\n        {\n          \"description\": \"Outrun Dark (https://tinted-theming.github.io/tinted-gallery/#base16-outrun-dark)\",\n          \"type\": \"string\",\n          \"const\": \"OutrunDark\"\n        },\n        {\n          \"description\": \"Oxocarbon Dark (https://tinted-theming.github.io/tinted-gallery/#base16-oxocarbon-dark)\",\n          \"type\": \"string\",\n          \"const\": \"OxocarbonDark\"\n        },\n        {\n          \"description\": \"Oxocarbon Light (https://tinted-theming.github.io/tinted-gallery/#base16-oxocarbon-light)\",\n          \"type\": \"string\",\n          \"const\": \"OxocarbonLight\"\n        },\n        {\n          \"description\": \"Pandora (https://tinted-theming.github.io/tinted-gallery/#base16-pandora)\",\n          \"type\": \"string\",\n          \"const\": \"Pandora\"\n        },\n        {\n          \"description\": \"Papercolor Dark (https://tinted-theming.github.io/tinted-gallery/#base16-papercolor-dark)\",\n          \"type\": \"string\",\n          \"const\": \"PapercolorDark\"\n        },\n        {\n          \"description\": \"Papercolor Light (https://tinted-theming.github.io/tinted-gallery/#base16-papercolor-light)\",\n          \"type\": \"string\",\n          \"const\": \"PapercolorLight\"\n        },\n        {\n          \"description\": \"Paraiso (https://tinted-theming.github.io/tinted-gallery/#base16-paraiso)\",\n          \"type\": \"string\",\n          \"const\": \"Paraiso\"\n        },\n        {\n          \"description\": \"Pasque (https://tinted-theming.github.io/tinted-gallery/#base16-pasque)\",\n          \"type\": \"string\",\n          \"const\": \"Pasque\"\n        },\n        {\n          \"description\": \"Phd (https://tinted-theming.github.io/tinted-gallery/#base16-phd)\",\n          \"type\": \"string\",\n          \"const\": \"Phd\"\n        },\n        {\n          \"description\": \"Pico (https://tinted-theming.github.io/tinted-gallery/#base16-pico)\",\n          \"type\": \"string\",\n          \"const\": \"Pico\"\n        },\n        {\n          \"description\": \"Pinky (https://tinted-theming.github.io/tinted-gallery/#base16-pinky)\",\n          \"type\": \"string\",\n          \"const\": \"Pinky\"\n        },\n        {\n          \"description\": \"Pop (https://tinted-theming.github.io/tinted-gallery/#base16-pop)\",\n          \"type\": \"string\",\n          \"const\": \"Pop\"\n        },\n        {\n          \"description\": \"Porple (https://tinted-theming.github.io/tinted-gallery/#base16-porple)\",\n          \"type\": \"string\",\n          \"const\": \"Porple\"\n        },\n        {\n          \"description\": \"Precious Dark Eleven (https://tinted-theming.github.io/tinted-gallery/#base16-precious-dark-eleven)\",\n          \"type\": \"string\",\n          \"const\": \"PreciousDarkEleven\"\n        },\n        {\n          \"description\": \"Precious Dark Fifteen (https://tinted-theming.github.io/tinted-gallery/#base16-precious-dark-fifteen)\",\n          \"type\": \"string\",\n          \"const\": \"PreciousDarkFifteen\"\n        },\n        {\n          \"description\": \"Precious Light Warm (https://tinted-theming.github.io/tinted-gallery/#base16-precious-light-warm)\",\n          \"type\": \"string\",\n          \"const\": \"PreciousLightWarm\"\n        },\n        {\n          \"description\": \"Precious Light White (https://tinted-theming.github.io/tinted-gallery/#base16-precious-light-white)\",\n          \"type\": \"string\",\n          \"const\": \"PreciousLightWhite\"\n        },\n        {\n          \"description\": \"Primer Dark Dimmed (https://tinted-theming.github.io/tinted-gallery/#base16-primer-dark-dimmed)\",\n          \"type\": \"string\",\n          \"const\": \"PrimerDarkDimmed\"\n        },\n        {\n          \"description\": \"Primer Dark (https://tinted-theming.github.io/tinted-gallery/#base16-primer-dark)\",\n          \"type\": \"string\",\n          \"const\": \"PrimerDark\"\n        },\n        {\n          \"description\": \"Primer Light (https://tinted-theming.github.io/tinted-gallery/#base16-primer-light)\",\n          \"type\": \"string\",\n          \"const\": \"PrimerLight\"\n        },\n        {\n          \"description\": \"Purpledream (https://tinted-theming.github.io/tinted-gallery/#base16-purpledream)\",\n          \"type\": \"string\",\n          \"const\": \"Purpledream\"\n        },\n        {\n          \"description\": \"Qualia (https://tinted-theming.github.io/tinted-gallery/#base16-qualia)\",\n          \"type\": \"string\",\n          \"const\": \"Qualia\"\n        },\n        {\n          \"description\": \"Railscasts (https://tinted-theming.github.io/tinted-gallery/#base16-railscasts)\",\n          \"type\": \"string\",\n          \"const\": \"Railscasts\"\n        },\n        {\n          \"description\": \"Rebecca (https://tinted-theming.github.io/tinted-gallery/#base16-rebecca)\",\n          \"type\": \"string\",\n          \"const\": \"Rebecca\"\n        },\n        {\n          \"description\": \"Rose Pine Dawn (https://tinted-theming.github.io/tinted-gallery/#base16-rose-pine-dawn)\",\n          \"type\": \"string\",\n          \"const\": \"RosePineDawn\"\n        },\n        {\n          \"description\": \"Rose Pine Moon (https://tinted-theming.github.io/tinted-gallery/#base16-rose-pine-moon)\",\n          \"type\": \"string\",\n          \"const\": \"RosePineMoon\"\n        },\n        {\n          \"description\": \"Rose Pine (https://tinted-theming.github.io/tinted-gallery/#base16-rose-pine)\",\n          \"type\": \"string\",\n          \"const\": \"RosePine\"\n        },\n        {\n          \"description\": \"Saga (https://tinted-theming.github.io/tinted-gallery/#base16-saga)\",\n          \"type\": \"string\",\n          \"const\": \"Saga\"\n        },\n        {\n          \"description\": \"Sagelight (https://tinted-theming.github.io/tinted-gallery/#base16-sagelight)\",\n          \"type\": \"string\",\n          \"const\": \"Sagelight\"\n        },\n        {\n          \"description\": \"Sakura (https://tinted-theming.github.io/tinted-gallery/#base16-sakura)\",\n          \"type\": \"string\",\n          \"const\": \"Sakura\"\n        },\n        {\n          \"description\": \"Sandcastle (https://tinted-theming.github.io/tinted-gallery/#base16-sandcastle)\",\n          \"type\": \"string\",\n          \"const\": \"Sandcastle\"\n        },\n        {\n          \"description\": \"Selenized Black (https://tinted-theming.github.io/tinted-gallery/#base16-selenized-black)\",\n          \"type\": \"string\",\n          \"const\": \"SelenizedBlack\"\n        },\n        {\n          \"description\": \"Selenized Dark (https://tinted-theming.github.io/tinted-gallery/#base16-selenized-dark)\",\n          \"type\": \"string\",\n          \"const\": \"SelenizedDark\"\n        },\n        {\n          \"description\": \"Selenized Light (https://tinted-theming.github.io/tinted-gallery/#base16-selenized-light)\",\n          \"type\": \"string\",\n          \"const\": \"SelenizedLight\"\n        },\n        {\n          \"description\": \"Selenized White (https://tinted-theming.github.io/tinted-gallery/#base16-selenized-white)\",\n          \"type\": \"string\",\n          \"const\": \"SelenizedWhite\"\n        },\n        {\n          \"description\": \"Seti (https://tinted-theming.github.io/tinted-gallery/#base16-seti)\",\n          \"type\": \"string\",\n          \"const\": \"Seti\"\n        },\n        {\n          \"description\": \"Shades Of Purple (https://tinted-theming.github.io/tinted-gallery/#base16-shades-of-purple)\",\n          \"type\": \"string\",\n          \"const\": \"ShadesOfPurple\"\n        },\n        {\n          \"description\": \"Shadesmear Dark (https://tinted-theming.github.io/tinted-gallery/#base16-shadesmear-dark)\",\n          \"type\": \"string\",\n          \"const\": \"ShadesmearDark\"\n        },\n        {\n          \"description\": \"Shadesmear Light (https://tinted-theming.github.io/tinted-gallery/#base16-shadesmear-light)\",\n          \"type\": \"string\",\n          \"const\": \"ShadesmearLight\"\n        },\n        {\n          \"description\": \"Shapeshifter (https://tinted-theming.github.io/tinted-gallery/#base16-shapeshifter)\",\n          \"type\": \"string\",\n          \"const\": \"Shapeshifter\"\n        },\n        {\n          \"description\": \"Silk Dark (https://tinted-theming.github.io/tinted-gallery/#base16-silk-dark)\",\n          \"type\": \"string\",\n          \"const\": \"SilkDark\"\n        },\n        {\n          \"description\": \"Silk Light (https://tinted-theming.github.io/tinted-gallery/#base16-silk-light)\",\n          \"type\": \"string\",\n          \"const\": \"SilkLight\"\n        },\n        {\n          \"description\": \"Snazzy (https://tinted-theming.github.io/tinted-gallery/#base16-snazzy)\",\n          \"type\": \"string\",\n          \"const\": \"Snazzy\"\n        },\n        {\n          \"description\": \"Solarflare Light (https://tinted-theming.github.io/tinted-gallery/#base16-solarflare-light)\",\n          \"type\": \"string\",\n          \"const\": \"SolarflareLight\"\n        },\n        {\n          \"description\": \"Solarflare (https://tinted-theming.github.io/tinted-gallery/#base16-solarflare)\",\n          \"type\": \"string\",\n          \"const\": \"Solarflare\"\n        },\n        {\n          \"description\": \"Solarized Dark (https://tinted-theming.github.io/tinted-gallery/#base16-solarized-dark)\",\n          \"type\": \"string\",\n          \"const\": \"SolarizedDark\"\n        },\n        {\n          \"description\": \"Solarized Light (https://tinted-theming.github.io/tinted-gallery/#base16-solarized-light)\",\n          \"type\": \"string\",\n          \"const\": \"SolarizedLight\"\n        },\n        {\n          \"description\": \"Spaceduck (https://tinted-theming.github.io/tinted-gallery/#base16-spaceduck)\",\n          \"type\": \"string\",\n          \"const\": \"Spaceduck\"\n        },\n        {\n          \"description\": \"Spacemacs (https://tinted-theming.github.io/tinted-gallery/#base16-spacemacs)\",\n          \"type\": \"string\",\n          \"const\": \"Spacemacs\"\n        },\n        {\n          \"description\": \"Sparky (https://tinted-theming.github.io/tinted-gallery/#base16-sparky)\",\n          \"type\": \"string\",\n          \"const\": \"Sparky\"\n        },\n        {\n          \"description\": \"Standardized Dark (https://tinted-theming.github.io/tinted-gallery/#base16-standardized-dark)\",\n          \"type\": \"string\",\n          \"const\": \"StandardizedDark\"\n        },\n        {\n          \"description\": \"Standardized Light (https://tinted-theming.github.io/tinted-gallery/#base16-standardized-light)\",\n          \"type\": \"string\",\n          \"const\": \"StandardizedLight\"\n        },\n        {\n          \"description\": \"Stella (https://tinted-theming.github.io/tinted-gallery/#base16-stella)\",\n          \"type\": \"string\",\n          \"const\": \"Stella\"\n        },\n        {\n          \"description\": \"Still Alive (https://tinted-theming.github.io/tinted-gallery/#base16-still-alive)\",\n          \"type\": \"string\",\n          \"const\": \"StillAlive\"\n        },\n        {\n          \"description\": \"Summercamp (https://tinted-theming.github.io/tinted-gallery/#base16-summercamp)\",\n          \"type\": \"string\",\n          \"const\": \"Summercamp\"\n        },\n        {\n          \"description\": \"Summerfruit Dark (https://tinted-theming.github.io/tinted-gallery/#base16-summerfruit-dark)\",\n          \"type\": \"string\",\n          \"const\": \"SummerfruitDark\"\n        },\n        {\n          \"description\": \"Summerfruit Light (https://tinted-theming.github.io/tinted-gallery/#base16-summerfruit-light)\",\n          \"type\": \"string\",\n          \"const\": \"SummerfruitLight\"\n        },\n        {\n          \"description\": \"Synth Midnight Dark (https://tinted-theming.github.io/tinted-gallery/#base16-synth-midnight-dark)\",\n          \"type\": \"string\",\n          \"const\": \"SynthMidnightDark\"\n        },\n        {\n          \"description\": \"Synth Midnight Light (https://tinted-theming.github.io/tinted-gallery/#base16-synth-midnight-light)\",\n          \"type\": \"string\",\n          \"const\": \"SynthMidnightLight\"\n        },\n        {\n          \"description\": \"Tango (https://tinted-theming.github.io/tinted-gallery/#base16-tango)\",\n          \"type\": \"string\",\n          \"const\": \"Tango\"\n        },\n        {\n          \"description\": \"Tarot (https://tinted-theming.github.io/tinted-gallery/#base16-tarot)\",\n          \"type\": \"string\",\n          \"const\": \"Tarot\"\n        },\n        {\n          \"description\": \"Tender (https://tinted-theming.github.io/tinted-gallery/#base16-tender)\",\n          \"type\": \"string\",\n          \"const\": \"Tender\"\n        },\n        {\n          \"description\": \"Terracotta Dark (https://tinted-theming.github.io/tinted-gallery/#base16-terracotta-dark)\",\n          \"type\": \"string\",\n          \"const\": \"TerracottaDark\"\n        },\n        {\n          \"description\": \"Terracotta (https://tinted-theming.github.io/tinted-gallery/#base16-terracotta)\",\n          \"type\": \"string\",\n          \"const\": \"Terracotta\"\n        },\n        {\n          \"description\": \"Tokyo City Dark (https://tinted-theming.github.io/tinted-gallery/#base16-tokyo-city-dark)\",\n          \"type\": \"string\",\n          \"const\": \"TokyoCityDark\"\n        },\n        {\n          \"description\": \"Tokyo City Light (https://tinted-theming.github.io/tinted-gallery/#base16-tokyo-city-light)\",\n          \"type\": \"string\",\n          \"const\": \"TokyoCityLight\"\n        },\n        {\n          \"description\": \"Tokyo City Terminal Dark (https://tinted-theming.github.io/tinted-gallery/#base16-tokyo-city-terminal-dark)\",\n          \"type\": \"string\",\n          \"const\": \"TokyoCityTerminalDark\"\n        },\n        {\n          \"description\": \"Tokyo City Terminal Light (https://tinted-theming.github.io/tinted-gallery/#base16-tokyo-city-terminal-light)\",\n          \"type\": \"string\",\n          \"const\": \"TokyoCityTerminalLight\"\n        },\n        {\n          \"description\": \"Tokyo Night Dark (https://tinted-theming.github.io/tinted-gallery/#base16-tokyo-night-dark)\",\n          \"type\": \"string\",\n          \"const\": \"TokyoNightDark\"\n        },\n        {\n          \"description\": \"Tokyo Night Light (https://tinted-theming.github.io/tinted-gallery/#base16-tokyo-night-light)\",\n          \"type\": \"string\",\n          \"const\": \"TokyoNightLight\"\n        },\n        {\n          \"description\": \"Tokyo Night Moon (https://tinted-theming.github.io/tinted-gallery/#base16-tokyo-night-moon)\",\n          \"type\": \"string\",\n          \"const\": \"TokyoNightMoon\"\n        },\n        {\n          \"description\": \"Tokyo Night Storm (https://tinted-theming.github.io/tinted-gallery/#base16-tokyo-night-storm)\",\n          \"type\": \"string\",\n          \"const\": \"TokyoNightStorm\"\n        },\n        {\n          \"description\": \"Tokyo Night Terminal Dark (https://tinted-theming.github.io/tinted-gallery/#base16-tokyo-night-terminal-dark)\",\n          \"type\": \"string\",\n          \"const\": \"TokyoNightTerminalDark\"\n        },\n        {\n          \"description\": \"Tokyo Night Terminal Light (https://tinted-theming.github.io/tinted-gallery/#base16-tokyo-night-terminal-light)\",\n          \"type\": \"string\",\n          \"const\": \"TokyoNightTerminalLight\"\n        },\n        {\n          \"description\": \"Tokyo Night Terminal Storm (https://tinted-theming.github.io/tinted-gallery/#base16-tokyo-night-terminal-storm)\",\n          \"type\": \"string\",\n          \"const\": \"TokyoNightTerminalStorm\"\n        },\n        {\n          \"description\": \"Tokyodark Terminal (https://tinted-theming.github.io/tinted-gallery/#base16-tokyodark-terminal)\",\n          \"type\": \"string\",\n          \"const\": \"TokyodarkTerminal\"\n        },\n        {\n          \"description\": \"Tokyodark (https://tinted-theming.github.io/tinted-gallery/#base16-tokyodark)\",\n          \"type\": \"string\",\n          \"const\": \"Tokyodark\"\n        },\n        {\n          \"description\": \"Tomorrow Night Eighties (https://tinted-theming.github.io/tinted-gallery/#base16-tomorrow-night-eighties)\",\n          \"type\": \"string\",\n          \"const\": \"TomorrowNightEighties\"\n        },\n        {\n          \"description\": \"Tomorrow Night (https://tinted-theming.github.io/tinted-gallery/#base16-tomorrow-night)\",\n          \"type\": \"string\",\n          \"const\": \"TomorrowNight\"\n        },\n        {\n          \"description\": \"Tomorrow (https://tinted-theming.github.io/tinted-gallery/#base16-tomorrow)\",\n          \"type\": \"string\",\n          \"const\": \"Tomorrow\"\n        },\n        {\n          \"description\": \"Tube (https://tinted-theming.github.io/tinted-gallery/#base16-tube)\",\n          \"type\": \"string\",\n          \"const\": \"Tube\"\n        },\n        {\n          \"description\": \"Twilight (https://tinted-theming.github.io/tinted-gallery/#base16-twilight)\",\n          \"type\": \"string\",\n          \"const\": \"Twilight\"\n        },\n        {\n          \"description\": \"Unikitty Dark (https://tinted-theming.github.io/tinted-gallery/#base16-unikitty-dark)\",\n          \"type\": \"string\",\n          \"const\": \"UnikittyDark\"\n        },\n        {\n          \"description\": \"Unikitty Light (https://tinted-theming.github.io/tinted-gallery/#base16-unikitty-light)\",\n          \"type\": \"string\",\n          \"const\": \"UnikittyLight\"\n        },\n        {\n          \"description\": \"Unikitty Reversible (https://tinted-theming.github.io/tinted-gallery/#base16-unikitty-reversible)\",\n          \"type\": \"string\",\n          \"const\": \"UnikittyReversible\"\n        },\n        {\n          \"description\": \"Uwunicorn (https://tinted-theming.github.io/tinted-gallery/#base16-uwunicorn)\",\n          \"type\": \"string\",\n          \"const\": \"Uwunicorn\"\n        },\n        {\n          \"description\": \"Vesper (https://tinted-theming.github.io/tinted-gallery/#base16-vesper)\",\n          \"type\": \"string\",\n          \"const\": \"Vesper\"\n        },\n        {\n          \"description\": \"Vice (https://tinted-theming.github.io/tinted-gallery/#base16-vice)\",\n          \"type\": \"string\",\n          \"const\": \"Vice\"\n        },\n        {\n          \"description\": \"Vulcan (https://tinted-theming.github.io/tinted-gallery/#base16-vulcan)\",\n          \"type\": \"string\",\n          \"const\": \"Vulcan\"\n        },\n        {\n          \"description\": \"Windows 10 Light (https://tinted-theming.github.io/tinted-gallery/#base16-windows-10-light)\",\n          \"type\": \"string\",\n          \"const\": \"Windows10Light\"\n        },\n        {\n          \"description\": \"Windows 10 (https://tinted-theming.github.io/tinted-gallery/#base16-windows-10)\",\n          \"type\": \"string\",\n          \"const\": \"Windows10\"\n        },\n        {\n          \"description\": \"Windows 95 Light (https://tinted-theming.github.io/tinted-gallery/#base16-windows-95-light)\",\n          \"type\": \"string\",\n          \"const\": \"Windows95Light\"\n        },\n        {\n          \"description\": \"Windows 95 (https://tinted-theming.github.io/tinted-gallery/#base16-windows-95)\",\n          \"type\": \"string\",\n          \"const\": \"Windows95\"\n        },\n        {\n          \"description\": \"Windows Highcontrast Light (https://tinted-theming.github.io/tinted-gallery/#base16-windows-highcontrast-light)\",\n          \"type\": \"string\",\n          \"const\": \"WindowsHighcontrastLight\"\n        },\n        {\n          \"description\": \"Windows Highcontrast (https://tinted-theming.github.io/tinted-gallery/#base16-windows-highcontrast)\",\n          \"type\": \"string\",\n          \"const\": \"WindowsHighcontrast\"\n        },\n        {\n          \"description\": \"Windows Nt Light (https://tinted-theming.github.io/tinted-gallery/#base16-windows-nt-light)\",\n          \"type\": \"string\",\n          \"const\": \"WindowsNtLight\"\n        },\n        {\n          \"description\": \"Windows Nt (https://tinted-theming.github.io/tinted-gallery/#base16-windows-nt)\",\n          \"type\": \"string\",\n          \"const\": \"WindowsNt\"\n        },\n        {\n          \"description\": \"Woodland (https://tinted-theming.github.io/tinted-gallery/#base16-woodland)\",\n          \"type\": \"string\",\n          \"const\": \"Woodland\"\n        },\n        {\n          \"description\": \"Xcode Dusk (https://tinted-theming.github.io/tinted-gallery/#base16-xcode-dusk)\",\n          \"type\": \"string\",\n          \"const\": \"XcodeDusk\"\n        },\n        {\n          \"description\": \"Zenbones (https://tinted-theming.github.io/tinted-gallery/#base16-zenbones)\",\n          \"type\": \"string\",\n          \"const\": \"Zenbones\"\n        },\n        {\n          \"description\": \"Zenburn (https://tinted-theming.github.io/tinted-gallery/#base16-zenburn)\",\n          \"type\": \"string\",\n          \"const\": \"Zenburn\"\n        }\n      ]\n    },\n    \"Base16ColourPalette\": {\n      \"description\": \"Base16 colour palette: https://github.com/chriskempson/base16\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"base_00\": {\n          \"description\": \"Base00\",\n          \"$ref\": \"#/$defs/Colour\"\n        },\n        \"base_01\": {\n          \"description\": \"Base01\",\n          \"$ref\": \"#/$defs/Colour\"\n        },\n        \"base_02\": {\n          \"description\": \"Base02\",\n          \"$ref\": \"#/$defs/Colour\"\n        },\n        \"base_03\": {\n          \"description\": \"Base03\",\n          \"$ref\": \"#/$defs/Colour\"\n        },\n        \"base_04\": {\n          \"description\": \"Base04\",\n          \"$ref\": \"#/$defs/Colour\"\n        },\n        \"base_05\": {\n          \"description\": \"Base05\",\n          \"$ref\": \"#/$defs/Colour\"\n        },\n        \"base_06\": {\n          \"description\": \"Base06\",\n          \"$ref\": \"#/$defs/Colour\"\n        },\n        \"base_07\": {\n          \"description\": \"Base07\",\n          \"$ref\": \"#/$defs/Colour\"\n        },\n        \"base_08\": {\n          \"description\": \"Base08\",\n          \"$ref\": \"#/$defs/Colour\"\n        },\n        \"base_09\": {\n          \"description\": \"Base09\",\n          \"$ref\": \"#/$defs/Colour\"\n        },\n        \"base_0a\": {\n          \"description\": \"Base0A\",\n          \"$ref\": \"#/$defs/Colour\"\n        },\n        \"base_0b\": {\n          \"description\": \"Base0B\",\n          \"$ref\": \"#/$defs/Colour\"\n        },\n        \"base_0c\": {\n          \"description\": \"Base0C\",\n          \"$ref\": \"#/$defs/Colour\"\n        },\n        \"base_0d\": {\n          \"description\": \"Base0D\",\n          \"$ref\": \"#/$defs/Colour\"\n        },\n        \"base_0e\": {\n          \"description\": \"Base0E\",\n          \"$ref\": \"#/$defs/Colour\"\n        },\n        \"base_0f\": {\n          \"description\": \"Base0F\",\n          \"$ref\": \"#/$defs/Colour\"\n        }\n      },\n      \"required\": [\n        \"base_00\",\n        \"base_01\",\n        \"base_02\",\n        \"base_03\",\n        \"base_04\",\n        \"base_05\",\n        \"base_06\",\n        \"base_07\",\n        \"base_08\",\n        \"base_09\",\n        \"base_0a\",\n        \"base_0b\",\n        \"base_0c\",\n        \"base_0d\",\n        \"base_0e\",\n        \"base_0f\"\n      ]\n    },\n    \"Base16Value\": {\n      \"description\": \"Base16 value\",\n      \"oneOf\": [\n        {\n          \"description\": \"Base00\",\n          \"type\": \"string\",\n          \"const\": \"Base00\"\n        },\n        {\n          \"description\": \"Base01\",\n          \"type\": \"string\",\n          \"const\": \"Base01\"\n        },\n        {\n          \"description\": \"Base02\",\n          \"type\": \"string\",\n          \"const\": \"Base02\"\n        },\n        {\n          \"description\": \"Base03\",\n          \"type\": \"string\",\n          \"const\": \"Base03\"\n        },\n        {\n          \"description\": \"Base04\",\n          \"type\": \"string\",\n          \"const\": \"Base04\"\n        },\n        {\n          \"description\": \"Base05\",\n          \"type\": \"string\",\n          \"const\": \"Base05\"\n        },\n        {\n          \"description\": \"Base06\",\n          \"type\": \"string\",\n          \"const\": \"Base06\"\n        },\n        {\n          \"description\": \"Base07\",\n          \"type\": \"string\",\n          \"const\": \"Base07\"\n        },\n        {\n          \"description\": \"Base08\",\n          \"type\": \"string\",\n          \"const\": \"Base08\"\n        },\n        {\n          \"description\": \"Base09\",\n          \"type\": \"string\",\n          \"const\": \"Base09\"\n        },\n        {\n          \"description\": \"Base0A\",\n          \"type\": \"string\",\n          \"const\": \"Base0A\"\n        },\n        {\n          \"description\": \"Base0B\",\n          \"type\": \"string\",\n          \"const\": \"Base0B\"\n        },\n        {\n          \"description\": \"Base0C\",\n          \"type\": \"string\",\n          \"const\": \"Base0C\"\n        },\n        {\n          \"description\": \"Base0D\",\n          \"type\": \"string\",\n          \"const\": \"Base0D\"\n        },\n        {\n          \"description\": \"Base0E\",\n          \"type\": \"string\",\n          \"const\": \"Base0E\"\n        },\n        {\n          \"description\": \"Base0F\",\n          \"type\": \"string\",\n          \"const\": \"Base0F\"\n        }\n      ]\n    },\n    \"BorderColours\": {\n      \"description\": \"Border colours for different container states\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"floating\": {\n          \"description\": \"Border colour when the container is in floating mode\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Colour\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"monocle\": {\n          \"description\": \"Border colour when the container is in monocle mode\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Colour\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"single\": {\n          \"description\": \"Border colour when the container contains a single window\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Colour\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"stack\": {\n          \"description\": \"Border colour when the container contains multiple windows\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Colour\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"unfocused\": {\n          \"description\": \"Border colour when the container is unfocused\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Colour\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"unfocused_locked\": {\n          \"description\": \"Border colour when the container is unfocused and locked\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Colour\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        }\n      }\n    },\n    \"BorderImplementation\": {\n      \"description\": \"Border style\",\n      \"oneOf\": [\n        {\n          \"description\": \"Use the adjustable komorebi border implementation\",\n          \"type\": \"string\",\n          \"const\": \"Komorebi\"\n        },\n        {\n          \"description\": \"Use the thin Windows accent border implementation\",\n          \"type\": \"string\",\n          \"const\": \"Windows\"\n        }\n      ]\n    },\n    \"BorderStyle\": {\n      \"description\": \"Border style\",\n      \"oneOf\": [\n        {\n          \"description\": \"Use the system border style\",\n          \"type\": \"string\",\n          \"const\": \"System\"\n        },\n        {\n          \"description\": \"Use the Windows 11-style rounded borders\",\n          \"type\": \"string\",\n          \"const\": \"Rounded\"\n        },\n        {\n          \"description\": \"Use the Windows 10-style square borders\",\n          \"type\": \"string\",\n          \"const\": \"Square\"\n        }\n      ]\n    },\n    \"Catppuccin\": {\n      \"description\": \"Catppuccin palette\",\n      \"oneOf\": [\n        {\n          \"description\": \"Frappe (https://catppuccin.com/palette#flavor-frappe)\",\n          \"type\": \"string\",\n          \"const\": \"Frappe\"\n        },\n        {\n          \"description\": \"Latte (https://catppuccin.com/palette#flavor-latte)\",\n          \"type\": \"string\",\n          \"const\": \"Latte\"\n        },\n        {\n          \"description\": \"Macchiato (https://catppuccin.com/palette#flavor-macchiato)\",\n          \"type\": \"string\",\n          \"const\": \"Macchiato\"\n        },\n        {\n          \"description\": \"Mocha (https://catppuccin.com/palette#flavor-mocha)\",\n          \"type\": \"string\",\n          \"const\": \"Mocha\"\n        }\n      ]\n    },\n    \"CatppuccinValue\": {\n      \"description\": \"Catppuccin Value\",\n      \"oneOf\": [\n        {\n          \"description\": \"Rosewater\",\n          \"type\": \"string\",\n          \"const\": \"Rosewater\"\n        },\n        {\n          \"description\": \"Flamingo\",\n          \"type\": \"string\",\n          \"const\": \"Flamingo\"\n        },\n        {\n          \"description\": \"Pink\",\n          \"type\": \"string\",\n          \"const\": \"Pink\"\n        },\n        {\n          \"description\": \"Mauve\",\n          \"type\": \"string\",\n          \"const\": \"Mauve\"\n        },\n        {\n          \"description\": \"Red\",\n          \"type\": \"string\",\n          \"const\": \"Red\"\n        },\n        {\n          \"description\": \"Maroon\",\n          \"type\": \"string\",\n          \"const\": \"Maroon\"\n        },\n        {\n          \"description\": \"Peach\",\n          \"type\": \"string\",\n          \"const\": \"Peach\"\n        },\n        {\n          \"description\": \"Yellow\",\n          \"type\": \"string\",\n          \"const\": \"Yellow\"\n        },\n        {\n          \"description\": \"Green\",\n          \"type\": \"string\",\n          \"const\": \"Green\"\n        },\n        {\n          \"description\": \"Teal\",\n          \"type\": \"string\",\n          \"const\": \"Teal\"\n        },\n        {\n          \"description\": \"Sky\",\n          \"type\": \"string\",\n          \"const\": \"Sky\"\n        },\n        {\n          \"description\": \"Sapphire\",\n          \"type\": \"string\",\n          \"const\": \"Sapphire\"\n        },\n        {\n          \"description\": \"Blue\",\n          \"type\": \"string\",\n          \"const\": \"Blue\"\n        },\n        {\n          \"description\": \"Lavender\",\n          \"type\": \"string\",\n          \"const\": \"Lavender\"\n        },\n        {\n          \"description\": \"Text\",\n          \"type\": \"string\",\n          \"const\": \"Text\"\n        },\n        {\n          \"description\": \"Subtext1\",\n          \"type\": \"string\",\n          \"const\": \"Subtext1\"\n        },\n        {\n          \"description\": \"Subtext0\",\n          \"type\": \"string\",\n          \"const\": \"Subtext0\"\n        },\n        {\n          \"description\": \"Overlay2\",\n          \"type\": \"string\",\n          \"const\": \"Overlay2\"\n        },\n        {\n          \"description\": \"Overlay1\",\n          \"type\": \"string\",\n          \"const\": \"Overlay1\"\n        },\n        {\n          \"description\": \"Overlay0\",\n          \"type\": \"string\",\n          \"const\": \"Overlay0\"\n        },\n        {\n          \"description\": \"Surface2\",\n          \"type\": \"string\",\n          \"const\": \"Surface2\"\n        },\n        {\n          \"description\": \"Surface1\",\n          \"type\": \"string\",\n          \"const\": \"Surface1\"\n        },\n        {\n          \"description\": \"Surface0\",\n          \"type\": \"string\",\n          \"const\": \"Surface0\"\n        },\n        {\n          \"description\": \"Base\",\n          \"type\": \"string\",\n          \"const\": \"Base\"\n        },\n        {\n          \"description\": \"Mantle\",\n          \"type\": \"string\",\n          \"const\": \"Mantle\"\n        },\n        {\n          \"description\": \"Crust\",\n          \"type\": \"string\",\n          \"const\": \"Crust\"\n        }\n      ]\n    },\n    \"Colour\": {\n      \"description\": \"Colour representation\",\n      \"anyOf\": [\n        {\n          \"description\": \"Colour represented as RGB\",\n          \"$ref\": \"#/$defs/Rgb\"\n        },\n        {\n          \"description\": \"Colour represented as Hex\",\n          \"$ref\": \"#/$defs/Hex\"\n        }\n      ]\n    },\n    \"CrossBoundaryBehaviour\": {\n      \"description\": \"Behaviour when an action would cross a monitor boundary\",\n      \"oneOf\": [\n        {\n          \"description\": \"Attempt to perform actions across a workspace boundary\",\n          \"type\": \"string\",\n          \"const\": \"Workspace\"\n        },\n        {\n          \"description\": \"Attempt to perform actions across a monitor boundary\",\n          \"type\": \"string\",\n          \"const\": \"Monitor\"\n        }\n      ]\n    },\n    \"DefaultLayout\": {\n      \"description\": \"A predefined komorebi layout\",\n      \"oneOf\": [\n        {\n          \"description\": \"BSP Layout\\n\\n```text\\n+-------+-----+\\n|       |     |\\n|       +--+--+\\n|       |  |--|\\n+-------+--+--+\\n```\",\n          \"type\": \"string\",\n          \"const\": \"BSP\"\n        },\n        {\n          \"description\": \"Columns Layout\\n\\n```text\\n+--+--+--+--+\\n|  |  |  |  |\\n|  |  |  |  |\\n|  |  |  |  |\\n+--+--+--+--+\\n```\",\n          \"type\": \"string\",\n          \"const\": \"Columns\"\n        },\n        {\n          \"description\": \"Rows Layout\\n\\n```text\\n+-----------+\\n|-----------|\\n|-----------|\\n|-----------|\\n+-----------+\\n```\",\n          \"type\": \"string\",\n          \"const\": \"Rows\"\n        },\n        {\n          \"description\": \"Vertical Stack Layout\\n\\n```text\\n+-------+-----+\\n|       |     |\\n|       +-----+\\n|       |     |\\n+-------+-----+\\n```\",\n          \"type\": \"string\",\n          \"const\": \"VerticalStack\"\n        },\n        {\n          \"description\": \"Horizontal Stack Layout\\n\\n```text\\n+------+------+\\n|             |\\n|------+------+\\n|      |      |\\n+------+------+\\n```\",\n          \"type\": \"string\",\n          \"const\": \"HorizontalStack\"\n        },\n        {\n          \"description\": \"Ultrawide Vertical Stack Layout\\n\\n```text\\n+-----+-----------+-----+\\n|     |           |     |\\n|     |           +-----+\\n|     |           |     |\\n|     |           +-----+\\n|     |           |     |\\n+-----+-----------+-----+\\n```\",\n          \"type\": \"string\",\n          \"const\": \"UltrawideVerticalStack\"\n        },\n        {\n          \"description\": \"Grid Layout\\n\\n```text\\n+-----+-----+   +---+---+---+   +---+---+---+   +---+---+---+\\n|     |     |   |   |   |   |   |   |   |   |   |   |   |   |\\n|     |     |   |   |   |   |   |   |   |   |   |   |   +---+\\n+-----+-----+   |   +---+---+   +---+---+---+   +---+---|   |\\n|     |     |   |   |   |   |   |   |   |   |   |   |   +---+\\n|     |     |   |   |   |   |   |   |   |   |   |   |   |   |\\n+-----+-----+   +---+---+---+   +---+---+---+   +---+---+---+\\n  4 windows       5 windows       6 windows       7 windows\\n```\",\n          \"type\": \"string\",\n          \"const\": \"Grid\"\n        },\n        {\n          \"description\": \"Right Main Vertical Stack Layout\\n\\n```text\\n+-----+-------+\\n|     |       |\\n+-----+       |\\n|     |       |\\n+-----+-------+\\n```\",\n          \"type\": \"string\",\n          \"const\": \"RightMainVerticalStack\"\n        },\n        {\n          \"description\": \"Scrolling Layout\\n\\n```text\\n+--+--+--+--+--+--+\\n|     |     |     |\\n|     |     |     |\\n|     |     |     |\\n+--+--+--+--+--+--+\\n```\",\n          \"type\": \"string\",\n          \"const\": \"Scrolling\"\n        }\n      ]\n    },\n    \"FloatingLayerBehaviour\": {\n      \"description\": \"Floating layer behaviour when a new window is opened\",\n      \"oneOf\": [\n        {\n          \"description\": \"Tile new windows (unless they match a float rule or float override is active)\",\n          \"type\": \"string\",\n          \"const\": \"Tile\"\n        },\n        {\n          \"description\": \"Float new windows\",\n          \"type\": \"string\",\n          \"const\": \"Float\"\n        }\n      ]\n    },\n    \"FocusFollowsMouseImplementation\": {\n      \"description\": \"Focus follows mouse implementation\",\n      \"oneOf\": [\n        {\n          \"description\": \"Custom FFM implementation (slightly more CPU-intensive)\",\n          \"type\": \"string\",\n          \"const\": \"Komorebi\"\n        },\n        {\n          \"description\": \"Native (legacy) Windows FFM implementation\",\n          \"type\": \"string\",\n          \"const\": \"Windows\"\n        }\n      ]\n    },\n    \"GridLayoutOptions\": {\n      \"description\": \"Options for the Grid layout\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"rows\": {\n          \"description\": \"Maximum number of rows per grid column\",\n          \"type\": \"integer\",\n          \"format\": \"uint\",\n          \"minimum\": 0\n        }\n      },\n      \"required\": [\n        \"rows\"\n      ]\n    },\n    \"Hex\": {\n      \"description\": \"Colour represented as a Hex string\",\n      \"type\": \"string\",\n      \"format\": \"color-hex\"\n    },\n    \"HidingBehaviour\": {\n      \"description\": \"Window hiding behaviour\",\n      \"oneOf\": [\n        {\n          \"description\": \"END OF LIFE FEATURE: Use the `SW_HIDE` flag to hide windows when switching workspaces (has issues with Electron apps)\",\n          \"type\": \"string\",\n          \"const\": \"Hide\",\n          \"deprecated\": true\n        },\n        {\n          \"description\": \"Use the `SW_MINIMIZE` flag to hide windows when switching workspaces (has issues with frequent workspace switching)\",\n          \"type\": \"string\",\n          \"const\": \"Minimize\"\n        },\n        {\n          \"description\": \"Use the undocumented SetCloak Win32 function to hide windows when switching workspaces\",\n          \"type\": \"string\",\n          \"const\": \"Cloak\"\n        }\n      ]\n    },\n    \"IdWithIdentifier\": {\n      \"description\": \"Rule for matching applications\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"id\": {\n          \"description\": \"Target identifier\",\n          \"type\": \"string\"\n        },\n        \"kind\": {\n          \"description\": \"Kind of identifier to target\",\n          \"$ref\": \"#/$defs/ApplicationIdentifier\"\n        },\n        \"matching_strategy\": {\n          \"description\": \"Matching strategy to use\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/MatchingStrategy\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        }\n      },\n      \"required\": [\n        \"kind\",\n        \"id\"\n      ]\n    },\n    \"KomorebiTheme\": {\n      \"description\": \"Komorebi theme\",\n      \"oneOf\": [\n        {\n          \"title\": \"Catppuccin\",\n          \"description\": \"Theme from catppuccin-egui\",\n          \"type\": \"object\",\n          \"properties\": {\n            \"palette\": {\n              \"type\": \"string\",\n              \"const\": \"Catppuccin\"\n            }\n          },\n          \"$ref\": \"#/$defs/KomorebiThemeCatppuccin\",\n          \"required\": [\n            \"palette\"\n          ]\n        },\n        {\n          \"title\": \"Base16\",\n          \"description\": \"Theme from base16-egui-themes\",\n          \"type\": \"object\",\n          \"properties\": {\n            \"palette\": {\n              \"type\": \"string\",\n              \"const\": \"Base16\"\n            }\n          },\n          \"$ref\": \"#/$defs/KomorebiThemeBase16\",\n          \"required\": [\n            \"palette\"\n          ]\n        },\n        {\n          \"title\": \"Custom\",\n          \"description\": \"Custom Base16 theme\",\n          \"type\": \"object\",\n          \"properties\": {\n            \"palette\": {\n              \"type\": \"string\",\n              \"const\": \"Custom\"\n            }\n          },\n          \"$ref\": \"#/$defs/KomorebiThemeCustom\",\n          \"required\": [\n            \"palette\"\n          ]\n        }\n      ]\n    },\n    \"KomorebiThemeBase16\": {\n      \"description\": \"Theme from base16-egui-themes\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"bar_accent\": {\n          \"description\": \"Bar accent colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base0D\"\n        },\n        \"floating_border\": {\n          \"description\": \"Floating window border colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base09\"\n        },\n        \"monocle_border\": {\n          \"description\": \"Monocle window border colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base0F\"\n        },\n        \"name\": {\n          \"description\": \"Name of the Base16 theme (theme previews: https://tinted-theming.github.io/tinted-gallery/)\",\n          \"$ref\": \"#/$defs/Base16\"\n        },\n        \"single_border\": {\n          \"description\": \"Single window border colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base0D\"\n        },\n        \"stack_border\": {\n          \"description\": \"Stack window border colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base0B\"\n        },\n        \"stackbar_background\": {\n          \"description\": \"Stackbar background colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base01\"\n        },\n        \"stackbar_focused_text\": {\n          \"description\": \"Stackbar focused text colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base0B\"\n        },\n        \"stackbar_unfocused_text\": {\n          \"description\": \"Stackbar unfocused text colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base05\"\n        },\n        \"unfocused_border\": {\n          \"description\": \"Unfocused window border colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base01\"\n        },\n        \"unfocused_locked_border\": {\n          \"description\": \"Unfocused locked window border colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base08\"\n        }\n      },\n      \"required\": [\n        \"name\"\n      ]\n    },\n    \"KomorebiThemeCatppuccin\": {\n      \"description\": \"Theme from catppuccin-egui\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"bar_accent\": {\n          \"description\": \"Bar accent colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/CatppuccinValue\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Blue\"\n        },\n        \"floating_border\": {\n          \"description\": \"Floating window border colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/CatppuccinValue\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Yellow\"\n        },\n        \"monocle_border\": {\n          \"description\": \"Monocle window border colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/CatppuccinValue\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Pink\"\n        },\n        \"name\": {\n          \"description\": \"Name of the Catppuccin theme (previews: https://github.com/catppuccin/catppuccin)\",\n          \"$ref\": \"#/$defs/Catppuccin\"\n        },\n        \"single_border\": {\n          \"description\": \"Single window border colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/CatppuccinValue\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Blue\"\n        },\n        \"stack_border\": {\n          \"description\": \"Stack window border colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/CatppuccinValue\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Green\"\n        },\n        \"stackbar_background\": {\n          \"description\": \"Stackbar background colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/CatppuccinValue\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base\"\n        },\n        \"stackbar_focused_text\": {\n          \"description\": \"Stackbar focused text colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/CatppuccinValue\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Green\"\n        },\n        \"stackbar_unfocused_text\": {\n          \"description\": \"Stackbar unfocused text colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/CatppuccinValue\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Text\"\n        },\n        \"unfocused_border\": {\n          \"description\": \"Unfocused window border colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/CatppuccinValue\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base\"\n        },\n        \"unfocused_locked_border\": {\n          \"description\": \"Unfocused locked window border colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/CatppuccinValue\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Red\"\n        }\n      },\n      \"required\": [\n        \"name\"\n      ]\n    },\n    \"KomorebiThemeCustom\": {\n      \"description\": \"Custom Base16 theme\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"bar_accent\": {\n          \"description\": \"Bar accent colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base0D\"\n        },\n        \"colours\": {\n          \"description\": \"Colours of the custom Base16 theme palette\",\n          \"$ref\": \"#/$defs/Base16ColourPalette\"\n        },\n        \"floating_border\": {\n          \"description\": \"Floating window border colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base09\"\n        },\n        \"monocle_border\": {\n          \"description\": \"Monocle window border colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base0F\"\n        },\n        \"single_border\": {\n          \"description\": \"Single window border colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base0D\"\n        },\n        \"stack_border\": {\n          \"description\": \"Stack window border colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base0B\"\n        },\n        \"stackbar_background\": {\n          \"description\": \"Stackbar background colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base01\"\n        },\n        \"stackbar_focused_text\": {\n          \"description\": \"Stackbar focused text colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base0B\"\n        },\n        \"stackbar_unfocused_text\": {\n          \"description\": \"Stackbar unfocused text colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base05\"\n        },\n        \"unfocused_border\": {\n          \"description\": \"Unfocused window border colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base01\"\n        },\n        \"unfocused_locked_border\": {\n          \"description\": \"Unfocused locked window border colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base08\"\n        }\n      },\n      \"required\": [\n        \"colours\"\n      ]\n    },\n    \"LayoutOptions\": {\n      \"description\": \"Options for specific layouts\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"column_ratios\": {\n          \"description\": \"Column width ratios (up to MAX_RATIOS values between 0.1 and 0.9)\\n\\n- Used by Columns layout: ratios for each column width\\n- Used by Grid layout: ratios for column widths\\n- Used by BSP, VerticalStack, RightMainVerticalStack: column_ratios[0] as primary split ratio\\n- Used by HorizontalStack: column_ratios[0] as primary split ratio (top area height)\\n- Used by UltrawideVerticalStack: column_ratios[0] as center ratio, column_ratios[1] as left ratio\\n\\nColumns without a ratio share remaining space equally.\\nExample: `[0.3, 0.4, 0.3]` for 30%-40%-30% columns\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"default\": null,\n          \"items\": {\n            \"type\": [\n              \"number\",\n              \"null\"\n            ],\n            \"format\": \"float\"\n          },\n          \"maxItems\": 5,\n          \"minItems\": 5\n        },\n        \"grid\": {\n          \"description\": \"Options related to the Grid layout\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/GridLayoutOptions\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"row_ratios\": {\n          \"description\": \"Row height ratios (up to MAX_RATIOS values between 0.1 and 0.9)\\n\\n- Used by Rows layout: ratios for each row height\\n- Used by Grid layout: ratios for row heights\\n\\nRows without a ratio share remaining space equally.\\nExample: `[0.5, 0.5]` for 50%-50% rows\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"default\": null,\n          \"items\": {\n            \"type\": [\n              \"number\",\n              \"null\"\n            ],\n            \"format\": \"float\"\n          },\n          \"maxItems\": 5,\n          \"minItems\": 5\n        },\n        \"scrolling\": {\n          \"description\": \"Options related to the Scrolling layout\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/ScrollingLayoutOptions\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        }\n      }\n    },\n    \"MatchingRule\": {\n      \"description\": \"Rule for matching applications\",\n      \"anyOf\": [\n        {\n          \"description\": \"Simple matching rule which must evaluate to true\",\n          \"$ref\": \"#/$defs/IdWithIdentifier\"\n        },\n        {\n          \"description\": \"Composite matching rule where all conditions must evaluate to true\",\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/$defs/IdWithIdentifier\"\n          }\n        }\n      ]\n    },\n    \"MatchingStrategy\": {\n      \"description\": \"Strategy for matching identifiers\",\n      \"oneOf\": [\n        {\n          \"description\": \"Should not be used, only kept for backward compatibility\",\n          \"type\": \"string\",\n          \"const\": \"Legacy\"\n        },\n        {\n          \"description\": \"Equals\",\n          \"type\": \"string\",\n          \"const\": \"Equals\"\n        },\n        {\n          \"description\": \"Starts With\",\n          \"type\": \"string\",\n          \"const\": \"StartsWith\"\n        },\n        {\n          \"description\": \"Ends With\",\n          \"type\": \"string\",\n          \"const\": \"EndsWith\"\n        },\n        {\n          \"description\": \"Contains\",\n          \"type\": \"string\",\n          \"const\": \"Contains\"\n        },\n        {\n          \"description\": \"Regex\",\n          \"type\": \"string\",\n          \"const\": \"Regex\"\n        },\n        {\n          \"description\": \"Does not end with\",\n          \"type\": \"string\",\n          \"const\": \"DoesNotEndWith\"\n        },\n        {\n          \"description\": \"Does not start with\",\n          \"type\": \"string\",\n          \"const\": \"DoesNotStartWith\"\n        },\n        {\n          \"description\": \"Does not equal\",\n          \"type\": \"string\",\n          \"const\": \"DoesNotEqual\"\n        },\n        {\n          \"description\": \"Does not contain\",\n          \"type\": \"string\",\n          \"const\": \"DoesNotContain\"\n        }\n      ]\n    },\n    \"MonitorConfig\": {\n      \"description\": \"Monitor configuration\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"container_padding\": {\n          \"description\": \"Container padding (default: global)\",\n          \"type\": [\n            \"integer\",\n            \"null\"\n          ],\n          \"format\": \"int32\"\n        },\n        \"floating_layer_behaviour\": {\n          \"description\": \"Determine what happens to a new window when the Floating workspace layer is active\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/FloatingLayerBehaviour\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Tile\"\n        },\n        \"wallpaper\": {\n          \"description\": \"Specify a wallpaper for this monitor\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Wallpaper\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"window_based_work_area_offset\": {\n          \"description\": \"Window based work area offset\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Rect\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"window_based_work_area_offset_limit\": {\n          \"description\": \"Open window limit after which the window based work area offset will no longer be applied\",\n          \"type\": [\n            \"integer\",\n            \"null\"\n          ],\n          \"format\": \"int\",\n          \"default\": 1\n        },\n        \"work_area_offset\": {\n          \"description\": \"Monitor-specific work area offset\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Rect\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"workspace_padding\": {\n          \"description\": \"Workspace padding (default: global)\",\n          \"type\": [\n            \"integer\",\n            \"null\"\n          ],\n          \"format\": \"int32\"\n        },\n        \"workspaces\": {\n          \"description\": \"Workspace configurations\",\n          \"type\": \"array\",\n          \"items\": {\n            \"$ref\": \"#/$defs/WorkspaceConfig\"\n          }\n        }\n      },\n      \"required\": [\n        \"workspaces\"\n      ]\n    },\n    \"MoveBehaviour\": {\n      \"description\": \"Move behaviour when the operation works across a monitor boundary\",\n      \"oneOf\": [\n        {\n          \"description\": \"Swap the window container with the window container at the edge of the adjacent monitor\",\n          \"type\": \"string\",\n          \"const\": \"Swap\"\n        },\n        {\n          \"description\": \"Insert the window container into the focused workspace on the adjacent monitor\",\n          \"type\": \"string\",\n          \"const\": \"Insert\"\n        },\n        {\n          \"description\": \"Do nothing if trying to move a window container in the direction of an adjacent monitor\",\n          \"type\": \"string\",\n          \"const\": \"NoOp\"\n        }\n      ]\n    },\n    \"OperationBehaviour\": {\n      \"description\": \"Operation behaviour for temporarily unmanaged and floating windows\",\n      \"oneOf\": [\n        {\n          \"description\": \"Process commands on temporarily unmanaged/floated windows\",\n          \"type\": \"string\",\n          \"const\": \"Op\"\n        },\n        {\n          \"description\": \"Ignore commands on temporarily unmanaged/floated windows\",\n          \"type\": \"string\",\n          \"const\": \"NoOp\"\n        }\n      ]\n    },\n    \"PathBuf\": {\n      \"description\": \"A file system path. Environment variables like %VAR%, $Env:VAR, or $VAR are automatically resolved.\",\n      \"type\": \"string\"\n    },\n    \"PerAnimationPrefixConfig\": {\n      \"description\": \"Animation configuration\\n\\nThis can be either global:\\n```json\\n{\\n    \\\"enabled\\\": true,\\n    \\\"style\\\": \\\"EaseInSine\\\",\\n    \\\"fps\\\": 60,\\n    \\\"duration\\\": 250\\n}\\n```\\n\\nOr scoped by an animation kind prefix:\\n```json\\n{\\n    \\\"movement\\\": {\\n        \\\"enabled\\\": true,\\n        \\\"style\\\": \\\"EaseInSine\\\",\\n        \\\"fps\\\": 60,\\n        \\\"duration\\\": 250\\n    }\\n}\\n```\",\n      \"anyOf\": [\n        {\n          \"description\": \"Animation configuration prefixed for a specific animation kind\",\n          \"type\": \"object\",\n          \"properties\": {\n            \"movement\": {\n              \"type\": \"boolean\"\n            },\n            \"transparency\": {\n              \"type\": \"boolean\"\n            }\n          },\n          \"additionalProperties\": false\n        },\n        {\n          \"description\": \"Animation configuration for all animation kinds\",\n          \"type\": \"boolean\"\n        }\n      ]\n    },\n    \"PerAnimationPrefixConfig2\": {\n      \"description\": \"Animation configuration\\n\\nThis can be either global:\\n```json\\n{\\n    \\\"enabled\\\": true,\\n    \\\"style\\\": \\\"EaseInSine\\\",\\n    \\\"fps\\\": 60,\\n    \\\"duration\\\": 250\\n}\\n```\\n\\nOr scoped by an animation kind prefix:\\n```json\\n{\\n    \\\"movement\\\": {\\n        \\\"enabled\\\": true,\\n        \\\"style\\\": \\\"EaseInSine\\\",\\n        \\\"fps\\\": 60,\\n        \\\"duration\\\": 250\\n    }\\n}\\n```\",\n      \"anyOf\": [\n        {\n          \"description\": \"Animation configuration prefixed for a specific animation kind\",\n          \"type\": \"object\",\n          \"properties\": {\n            \"movement\": {\n              \"type\": \"integer\",\n              \"format\": \"uint64\",\n              \"minimum\": 0\n            },\n            \"transparency\": {\n              \"type\": \"integer\",\n              \"format\": \"uint64\",\n              \"minimum\": 0\n            }\n          },\n          \"additionalProperties\": false\n        },\n        {\n          \"description\": \"Animation configuration for all animation kinds\",\n          \"type\": \"integer\",\n          \"format\": \"uint64\",\n          \"minimum\": 0\n        }\n      ]\n    },\n    \"PerAnimationPrefixConfig3\": {\n      \"description\": \"Animation configuration\\n\\nThis can be either global:\\n```json\\n{\\n    \\\"enabled\\\": true,\\n    \\\"style\\\": \\\"EaseInSine\\\",\\n    \\\"fps\\\": 60,\\n    \\\"duration\\\": 250\\n}\\n```\\n\\nOr scoped by an animation kind prefix:\\n```json\\n{\\n    \\\"movement\\\": {\\n        \\\"enabled\\\": true,\\n        \\\"style\\\": \\\"EaseInSine\\\",\\n        \\\"fps\\\": 60,\\n        \\\"duration\\\": 250\\n    }\\n}\\n```\",\n      \"anyOf\": [\n        {\n          \"description\": \"Animation configuration prefixed for a specific animation kind\",\n          \"type\": \"object\",\n          \"properties\": {\n            \"movement\": {\n              \"$ref\": \"#/$defs/AnimationStyle\"\n            },\n            \"transparency\": {\n              \"$ref\": \"#/$defs/AnimationStyle\"\n            }\n          },\n          \"additionalProperties\": false\n        },\n        {\n          \"description\": \"Animation configuration for all animation kinds\",\n          \"$ref\": \"#/$defs/AnimationStyle\"\n        }\n      ]\n    },\n    \"Placement\": {\n      \"description\": \"Placement behaviour for floating windows\",\n      \"oneOf\": [\n        {\n          \"description\": \"Does not change the size or position of the window\",\n          \"type\": \"string\",\n          \"const\": \"None\"\n        },\n        {\n          \"description\": \"Center the window without changing the size\",\n          \"type\": \"string\",\n          \"const\": \"Center\"\n        },\n        {\n          \"description\": \"Center the window and resize it according to the `AspectRatio`\",\n          \"type\": \"string\",\n          \"const\": \"CenterAndResize\"\n        }\n      ]\n    },\n    \"PredefinedAspectRatio\": {\n      \"description\": \"Predefined aspect ratio\",\n      \"oneOf\": [\n        {\n          \"description\": \"21:9\",\n          \"type\": \"string\",\n          \"const\": \"Ultrawide\"\n        },\n        {\n          \"description\": \"16:9\",\n          \"type\": \"string\",\n          \"const\": \"Widescreen\"\n        },\n        {\n          \"description\": \"4:3\",\n          \"type\": \"string\",\n          \"const\": \"Standard\"\n        }\n      ]\n    },\n    \"Rect\": {\n      \"description\": \"Rectangle dimensions\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"bottom\": {\n          \"description\": \"Height of the rectangle (from the top point)\",\n          \"type\": \"integer\",\n          \"format\": \"int32\"\n        },\n        \"left\": {\n          \"description\": \"Left point of the rectangle\",\n          \"type\": \"integer\",\n          \"format\": \"int32\"\n        },\n        \"right\": {\n          \"description\": \"Width of the recentangle (from the left point)\",\n          \"type\": \"integer\",\n          \"format\": \"int32\"\n        },\n        \"top\": {\n          \"description\": \"Top point of the rectangle\",\n          \"type\": \"integer\",\n          \"format\": \"int32\"\n        }\n      },\n      \"required\": [\n        \"left\",\n        \"top\",\n        \"right\",\n        \"bottom\"\n      ]\n    },\n    \"Rgb\": {\n      \"description\": \"Colour represented as RGB\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"b\": {\n          \"description\": \"Blue\",\n          \"type\": \"integer\",\n          \"format\": \"uint32\",\n          \"minimum\": 0\n        },\n        \"g\": {\n          \"description\": \"Green\",\n          \"type\": \"integer\",\n          \"format\": \"uint32\",\n          \"minimum\": 0\n        },\n        \"r\": {\n          \"description\": \"Red\",\n          \"type\": \"integer\",\n          \"format\": \"uint32\",\n          \"minimum\": 0\n        }\n      },\n      \"required\": [\n        \"r\",\n        \"g\",\n        \"b\"\n      ]\n    },\n    \"ScrollingLayoutOptions\": {\n      \"description\": \"Options for the Scrolling layout\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"center_focused_column\": {\n          \"description\": \"With an odd number of visible columns, keep the focused window column centered\",\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ]\n        },\n        \"columns\": {\n          \"description\": \"Desired number of visible columns (default: 3)\",\n          \"type\": \"integer\",\n          \"format\": \"uint\",\n          \"minimum\": 0\n        }\n      },\n      \"required\": [\n        \"columns\"\n      ]\n    },\n    \"StackbarConfig\": {\n      \"description\": \"Stackbar configuration\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"height\": {\n          \"description\": \"Stackbar height\",\n          \"type\": [\n            \"integer\",\n            \"null\"\n          ],\n          \"format\": \"int32\"\n        },\n        \"label\": {\n          \"description\": \"Stackbar label\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/StackbarLabel\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"mode\": {\n          \"description\": \"Stackbar mode\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/StackbarMode\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Never\"\n        },\n        \"tabs\": {\n          \"description\": \"Stackbar tab configuration options\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/TabsConfig\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        }\n      }\n    },\n    \"StackbarLabel\": {\n      \"description\": \"Starbar label\",\n      \"oneOf\": [\n        {\n          \"description\": \"Process name\",\n          \"type\": \"string\",\n          \"const\": \"Process\"\n        },\n        {\n          \"description\": \"Window title\",\n          \"type\": \"string\",\n          \"const\": \"Title\"\n        }\n      ]\n    },\n    \"StackbarMode\": {\n      \"description\": \"Stackbar mode\",\n      \"oneOf\": [\n        {\n          \"description\": \"Always show\",\n          \"type\": \"string\",\n          \"const\": \"Always\"\n        },\n        {\n          \"description\": \"Never show\",\n          \"type\": \"string\",\n          \"const\": \"Never\"\n        },\n        {\n          \"description\": \"Show on stack\",\n          \"type\": \"string\",\n          \"const\": \"OnStack\"\n        }\n      ]\n    },\n    \"TabsConfig\": {\n      \"description\": \"Stackbar tabs configuration\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"background\": {\n          \"description\": \"Tab background colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Colour\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"focused_text\": {\n          \"description\": \"Focused tab text colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Colour\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"font_family\": {\n          \"description\": \"Font family\",\n          \"type\": [\n            \"string\",\n            \"null\"\n          ]\n        },\n        \"font_size\": {\n          \"description\": \"Font size\",\n          \"type\": [\n            \"integer\",\n            \"null\"\n          ],\n          \"format\": \"int32\"\n        },\n        \"unfocused_text\": {\n          \"description\": \"Unfocused tab text colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Colour\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"width\": {\n          \"description\": \"Width of a stackbar tab\",\n          \"type\": [\n            \"integer\",\n            \"null\"\n          ],\n          \"format\": \"int32\"\n        }\n      }\n    },\n    \"ThemeOptions\": {\n      \"description\": \"Theme options\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"bar_accent\": {\n          \"description\": \"Komorebi status bar accent\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base0D\"\n        },\n        \"floating_border\": {\n          \"description\": \"Border colour when the window is floating\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base09\"\n        },\n        \"monocle_border\": {\n          \"description\": \"Border colour when the container is in monocle mode\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base0F\"\n        },\n        \"single_border\": {\n          \"description\": \"Border colour when the container contains a single window\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base0D\"\n        },\n        \"stack_border\": {\n          \"description\": \"Border colour when the container contains multiple windows\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base0B\"\n        },\n        \"stackbar_background\": {\n          \"description\": \"Stackbar tab background colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base01\"\n        },\n        \"stackbar_focused_text\": {\n          \"description\": \"Stackbar focused tab text colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base0B\"\n        },\n        \"stackbar_unfocused_text\": {\n          \"description\": \"Stackbar unfocused tab text colour\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base05\"\n        },\n        \"theme_variant\": {\n          \"description\": \"Specify Light or Dark variant for theme generation\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/ThemeVariant\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Dark\"\n        },\n        \"unfocused_border\": {\n          \"description\": \"Border colour when the container is unfocused\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base01\"\n        },\n        \"unfocused_locked_border\": {\n          \"description\": \"Border colour when the container is unfocused and locked\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Base16Value\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Base08\"\n        }\n      }\n    },\n    \"ThemeVariant\": {\n      \"description\": \"Theme variant\",\n      \"oneOf\": [\n        {\n          \"description\": \"Dark variant\",\n          \"type\": \"string\",\n          \"const\": \"Dark\"\n        },\n        {\n          \"description\": \"Light variant\",\n          \"type\": \"string\",\n          \"const\": \"Light\"\n        }\n      ]\n    },\n    \"Wallpaper\": {\n      \"description\": \"Wallpaper configuration\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"generate_theme\": {\n          \"description\": \"Generate and apply Base16 theme for this wallpaper\",\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ],\n          \"default\": true\n        },\n        \"path\": {\n          \"description\": \"Path to the wallpaper image file\",\n          \"$ref\": \"#/$defs/PathBuf\"\n        },\n        \"theme_options\": {\n          \"description\": \"Specify Light or Dark variant for theme generation\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/ThemeOptions\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Dark\"\n        }\n      },\n      \"required\": [\n        \"path\"\n      ]\n    },\n    \"WindowContainerBehaviour\": {\n      \"description\": \"Window container behaviour when a new window is opened\",\n      \"oneOf\": [\n        {\n          \"description\": \"Create a new container for each new window\",\n          \"type\": \"string\",\n          \"const\": \"Create\"\n        },\n        {\n          \"description\": \"Append new windows to the focused window container\",\n          \"type\": \"string\",\n          \"const\": \"Append\"\n        }\n      ]\n    },\n    \"WindowHandlingBehaviour\": {\n      \"description\": \"Window handling behaviour\",\n      \"oneOf\": [\n        {\n          \"description\": \"Synchronous\",\n          \"type\": \"string\",\n          \"const\": \"Sync\"\n        },\n        {\n          \"description\": \"Asynchronous\",\n          \"type\": \"string\",\n          \"const\": \"Async\"\n        }\n      ]\n    },\n    \"WorkspaceConfig\": {\n      \"description\": \"Workspace configuration\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"apply_window_based_work_area_offset\": {\n          \"description\": \"Apply this monitor's window-based work area offset\",\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ],\n          \"default\": true\n        },\n        \"container_padding\": {\n          \"description\": \"Container padding (default: global)\",\n          \"type\": [\n            \"integer\",\n            \"null\"\n          ],\n          \"format\": \"int32\"\n        },\n        \"custom_layout\": {\n          \"description\": \"END OF LIFE FEATURE: Custom Layout\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/PathBuf\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"deprecated\": true\n        },\n        \"custom_layout_rules\": {\n          \"description\": \"END OF LIFE FEATURE: Custom layout rules\",\n          \"type\": [\n            \"object\",\n            \"null\"\n          ],\n          \"additionalProperties\": false,\n          \"deprecated\": true,\n          \"patternProperties\": {\n            \"^\\\\d+$\": {\n              \"type\": \"string\"\n            }\n          }\n        },\n        \"float_override\": {\n          \"description\": \"Enable or disable float override, which makes it so every new window opens in floating mode\",\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ],\n          \"default\": false\n        },\n        \"floating_layer_behaviour\": {\n          \"description\": \"Determine what happens to a new window when the Floating workspace layer is active\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/FloatingLayerBehaviour\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Tile\"\n        },\n        \"initial_workspace_rules\": {\n          \"description\": \"Initial workspace application rules\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"$ref\": \"#/$defs/MatchingRule\"\n          }\n        },\n        \"layout\": {\n          \"description\": \"Layout\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/DefaultLayout\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"BSP\"\n        },\n        \"layout_flip\": {\n          \"description\": \"Specify an axis on which to flip the selected layout\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Axis\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"layout_options\": {\n          \"description\": \"Layout-specific options\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/LayoutOptions\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"layout_rules\": {\n          \"description\": \"Layout rules in the format of threshold => layout\",\n          \"type\": [\n            \"object\",\n            \"null\"\n          ],\n          \"additionalProperties\": false,\n          \"patternProperties\": {\n            \"^\\\\d+$\": {\n              \"$ref\": \"#/$defs/DefaultLayout\"\n            }\n          }\n        },\n        \"name\": {\n          \"description\": \"Name\",\n          \"type\": \"string\"\n        },\n        \"tile\": {\n          \"description\": \"Enable or disable tiling for the workspace\",\n          \"type\": [\n            \"boolean\",\n            \"null\"\n          ],\n          \"default\": true\n        },\n        \"wallpaper\": {\n          \"description\": \"Specify a wallpaper for this workspace\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Wallpaper\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"window_container_behaviour\": {\n          \"description\": \"Determine what happens when a new window is opened\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/WindowContainerBehaviour\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ],\n          \"default\": \"Create\"\n        },\n        \"window_container_behaviour_rules\": {\n          \"description\": \"Window container behaviour rules in the format of threshold => behaviour\",\n          \"type\": [\n            \"object\",\n            \"null\"\n          ],\n          \"additionalProperties\": false,\n          \"patternProperties\": {\n            \"^\\\\d+$\": {\n              \"$ref\": \"#/$defs/WindowContainerBehaviour\"\n            }\n          }\n        },\n        \"work_area_offset\": {\n          \"description\": \"Workspace specific work area offset\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/$defs/Rect\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"workspace_padding\": {\n          \"description\": \"Workspace padding (default: global)\",\n          \"type\": [\n            \"integer\",\n            \"null\"\n          ],\n          \"format\": \"int32\"\n        },\n        \"workspace_rules\": {\n          \"description\": \"Permanent workspace application rules\",\n          \"type\": [\n            \"array\",\n            \"null\"\n          ],\n          \"items\": {\n            \"$ref\": \"#/$defs/MatchingRule\"\n          }\n        }\n      },\n      \"required\": [\n        \"name\"\n      ]\n    },\n    \"ZOrder\": {\n      \"description\": \"Z Order (https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowpos)\",\n      \"oneOf\": [\n        {\n          \"description\": \"HWND_TOP\\n\\nPlaces the window at the top of the Z order.\",\n          \"type\": \"string\",\n          \"const\": \"Top\"\n        },\n        {\n          \"description\": \"HWND_NOTOPMOST\\n\\nPlaces the window above all non-topmost windows (that is, behind all topmost windows).\\nThis flag has no effect if the window is already a non-topmost window.\",\n          \"type\": \"string\",\n          \"const\": \"NoTopMost\"\n        },\n        {\n          \"description\": \"HWND_BOTTOM\\n\\nPlaces the window at the bottom of the Z order. If the hWnd parameter identifies a topmost window,\\nthe window loses its topmost status and is placed at the bottom of all other windows.\",\n          \"type\": \"string\",\n          \"const\": \"Bottom\"\n        },\n        {\n          \"description\": \"HWND_TOPMOST\\n\\nPlaces the window above all non-topmost windows.\\nThe window maintains its topmost position even when it is deactivated.\",\n          \"type\": \"string\",\n          \"const\": \"TopMost\"\n        }\n      ]\n    }\n  }\n}\n"
  },
  {
    "path": "wix/main.wxs",
    "content": "<?xml version='1.0' encoding='windows-1252'?>\n<!--\n  Copyright (C) 2017 Christopher R. Field.\n\n  Licensed under the Apache License, Version 2.0 (the \"License\");\n  you may not use this file except in compliance with the License.\n  You may obtain a copy of the License at\n\n  http://www.apache.org/licenses/LICENSE-2.0\n\n  Unless required by applicable law or agreed to in writing, software\n  distributed under the License is distributed on an \"AS IS\" BASIS,\n  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  See the License for the specific language governing permissions and\n  limitations under the License.\n-->\n\n<!--\n  The \"cargo wix\" subcommand provides a variety of predefined variables available\n  for customization of this template. The values for each variable are set at\n  installer creation time. The following variables are available:\n\n  TargetTriple      = The rustc target triple name.\n  TargetEnv         = The rustc target environment. This is typically either\n                      \"msvc\" or \"gnu\" depending on the toolchain downloaded and\n                      installed.\n  TargetVendor      = The rustc target vendor. This is typically \"pc\", but Rust\n                      does support other vendors, like \"uwp\".\n  CargoTargetBinDir = The complete path to the binary (exe). The default would\n                      be \"target\\release\\<BINARY_NAME>.exe\" where\n                      \"<BINARY_NAME>\" is replaced with the name of each binary\n                      target defined in the package's manifest (Cargo.toml). If\n                      a different rustc target triple is used than the host,\n                      i.e. cross-compiling, then the default path would be\n                      \"target\\<CARGO_TARGET>\\<CARGO_PROFILE>\\<BINARY_NAME>.exe\",\n                      where \"<CARGO_TARGET>\" is replaced with the \"CargoTarget\"\n                      variable value and \"<CARGO_PROFILE>\" is replaced with the\n                      value from the `CargoProfile` variable.\n  CargoTargetDir    = The path to the directory for the build artifacts, i.e.\n                      \"target\".\n  CargoProfile      = Either \"debug\" or `release` depending on the build\n                      profile. The default is \"release\".\n  Version           = The version for the installer. The default is the\n                      \"Major.Minor.Fix\" semantic versioning number of the Rust\n                      package.\n-->\n\n<!--\n  Please do not remove these pre-processor If-Else blocks. These are used with\n  the `cargo wix` subcommand to automatically determine the installation\n  destination for 32-bit versus 64-bit installers. Removal of these lines will\n  cause installation errors.\n-->\n<?if $(sys.BUILDARCH) = x64 or $(sys.BUILDARCH) = arm64?>\n<?define PlatformProgramFilesFolder = \"ProgramFiles64Folder\"?>\n<?else ?>\n<?define PlatformProgramFilesFolder = \"ProgramFilesFolder\"?>\n<?endif ?>\n\n<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>\n\n  <Product Id='*' Name='komorebi' UpgradeCode='F8B967B5-7E7B-4E3A-895B-B789EC898B54' Manufacturer='LGUG2Z' Language='1033' Codepage='1252' Version='$(var.Version)'>\n\n    <Package Id='*' Keywords='Installer' Description='A tiling window manager for Windows' Manufacturer='LGUG2Z' InstallerVersion='500' Languages='1033' Compressed='yes' InstallScope='perMachine' SummaryCodepage='1252' />\n\n    <MajorUpgrade Schedule='afterInstallInitialize' DowngradeErrorMessage='A newer version of [ProductName] is already installed. Setup will now exit.' />\n\n    <Media Id='1' Cabinet='media1.cab' EmbedCab='yes' DiskPrompt='CD-ROM #1' />\n    <Property Id='DiskPrompt' Value='komorebi Installation' />\n\n    <Directory Id='TARGETDIR' Name='SourceDir'>\n      <Directory Id='$(var.PlatformProgramFilesFolder)' Name='PFiles'>\n        <Directory Id='APPLICATIONFOLDER' Name='komorebi'>\n          <!--\n                      Disabling the license sidecar file in the installer is a two step process:\n\n                      1. Comment out or remove the `Component` tag along with its contents.\n                      2. Comment out or remove the `ComponentRef` tag with the \"License\" Id\n                         attribute value further down in this file.\n                    -->\n          <Component Id='License' Guid='*'>\n            <File Id='LicenseFile' Name='License.rtf' DiskId='1' Source='wix\\License.rtf' KeyPath='yes' />\n          </Component>\n\n          <Directory Id='Bin' Name='bin'>\n            <Component Id='Path' Guid='6C6DF276-06C4-4675-BDED-48C5C2BC9BC5' KeyPath='yes'>\n              <Environment Id='PATH' Name='PATH' Value='[Bin]' Permanent='no' Part='last' Action='set' System='yes' />\n            </Component>\n            <Component Id='binary0' Guid='*'>\n              <File Id='exe0' Name='komorebi.exe' DiskId='1' Source='$(var.CargoTargetBinDir)\\komorebi.exe' KeyPath='yes' />\n            </Component>\n            <Component Id='binary1' Guid='*'>\n              <File Id='exe1' Name='komorebic.exe' DiskId='1' Source='$(var.CargoTargetBinDir)\\komorebic.exe' KeyPath='yes' />\n            </Component>\n            <Component Id='binary2' Guid='*'>\n              <File Id='exe2' Name='komorebic-no-console.exe' DiskId='1' Source='$(var.CargoTargetBinDir)\\komorebic-no-console.exe' KeyPath='yes' />\n            </Component>\n            <Component Id='binary3' Guid='*'>\n              <File Id='exe3' Name='komorebi-gui.exe' DiskId='1' Source='$(var.CargoTargetBinDir)\\komorebi-gui.exe' KeyPath='yes' />\n            </Component>\n            <Component Id='binary4' Guid='*'>\n              <File Id='exe4' Name='komorebi-bar.exe' DiskId='1' Source='$(var.CargoTargetBinDir)\\komorebi-bar.exe' KeyPath='yes' />\n            </Component>\n            <Component Id='binary5' Guid='*'>\n              <File Id='exe5' Name='komorebi-shortcuts.exe' DiskId='1' Source='$(var.CargoTargetBinDir)\\komorebi-shortcuts.exe' KeyPath='yes' />\n            </Component>\n          </Directory>\n        </Directory>\n      </Directory>\n    </Directory>\n\n    <Feature Id='Binaries' Title='Application' Description='Installs all binaries and the license.' Level='1' ConfigurableDirectory='APPLICATIONFOLDER' AllowAdvertise='no' Display='expand' Absent='disallow'>\n      <!--\n              Comment out or remove the following `ComponentRef` tag to remove\n              the license sidecar file from the installer.\n            -->\n      <ComponentRef Id='License' />\n\n      <ComponentRef Id='binary0' />\n\n      <ComponentRef Id='binary1' />\n\n      <ComponentRef Id='binary2' />\n\n      <ComponentRef Id='binary3' />\n\n      <ComponentRef Id='binary4' />\n\n      <ComponentRef Id='binary5' />\n\n      <Feature Id='Environment' Title='PATH Environment Variable' Description='Add the install location of the [ProductName] executable to the PATH system environment variable. This allows the [ProductName] executable to be called from any location.' Level='1' Absent='allow'>\n        <ComponentRef Id='Path' />\n      </Feature>\n    </Feature>\n\n    <SetProperty Id='ARPINSTALLLOCATION' Value='[APPLICATIONFOLDER]' After='CostFinalize' />\n\n\n    <!--\n          Uncomment the following `Icon` and `Property` tags to change the product icon.\n\n          The product icon is the graphic that appears in the Add/Remove\n          Programs control panel for the application.\n        -->\n    <!--<Icon Id='ProductICO' SourceFile='wix\\Product.ico'/>-->\n    <!--<Property Id='ARPPRODUCTICON' Value='ProductICO' />-->\n\n    <Property Id='ARPHELPLINK' Value='https://github.com/LGUG2Z/komorebi' />\n\n    <UI>\n      <UIRef Id='WixUI_FeatureTree' />\n      <!--\n              Disabling the EULA dialog in the installer is a two step process:\n\n                 1. Uncomment the following two `Publish` tags\n                 2. Comment out or remove the `<WiXVariable Id='WixUILicenseRtf'...` tag further down\n\n            -->\n      <!--<Publish Dialog='WelcomeDlg' Control='Next' Event='NewDialog' Value='CustomizeDlg' Order='99'>1</Publish>-->\n      <!--<Publish Dialog='CustomizeDlg' Control='Back' Event='NewDialog' Value='WelcomeDlg' Order='99'>1</Publish>-->\n\n    </UI>\n\n    <!--\n          Disabling the EULA dialog in the installer requires commenting out\n          or removing the following `WixVariable` tag\n        -->\n    <WixVariable Id='WixUILicenseRtf' Value='wix\\License.rtf' />\n\n\n    <!--\n          Uncomment the next `WixVaraible` tag to customize the installer's\n          Graphical User Interface (GUI) and add a custom banner image across\n          the top of each screen. See the WiX Toolset documentation for details\n          about customization.\n\n          The banner BMP dimensions are 493 x 58 pixels.\n        -->\n    <!--<WixVariable Id='WixUIBannerBmp' Value='wix\\Banner.bmp'/>-->\n\n\n    <!--\n          Uncomment the next `WixVariable` tag to customize the installer's\n          Graphical User Interface (GUI) and add a custom image to the first\n          dialog, or screen. See the WiX Toolset documentation for details about\n          customization.\n\n          The dialog BMP dimensions are 493 x 312 pixels.\n        -->\n    <!--<WixVariable Id='WixUIDialogBmp' Value='wix\\Dialog.bmp'/>-->\n\n  </Product>\n\n</Wix>\n"
  }
]