[
  {
    "path": ".cargo/config.toml",
    "content": "[env]\r\n# Version number shown in CLI and system tray. Override this by setting the\r\n# version number on build/run (i.e. `VERSION_NUMBER=\"1.0.0\" cargo run`).\r\nVERSION_NUMBER = \"0.0.0\"\r\n"
  },
  {
    "path": ".editorconfig",
    "content": "# Editor configuration, see https://editorconfig.org\r\nroot = true\r\n\r\n[*]\r\ncharset = utf-8\r\nindent_style = space\r\nindent_size = 2\r\ninsert_final_newline = true\r\ntrim_trailing_whitespace = true\r\n\r\n[*.md]\r\nmax_line_length = off\r\n"
  },
  {
    "path": ".gitattributes",
    "content": "# Set CRLF as the line ending to use for all files that Git interprets as text files.\r\n* text=auto eol=crlf\r\n"
  },
  {
    "path": ".github/workflows/build.yaml",
    "content": "name: Build\r\n\r\non:\r\n  workflow_call:\r\n    inputs:\r\n      enable_ui_access:\r\n        type: boolean\r\n        default: false\r\n        description: (Windows only) Enable UIAccess in the application manifest.\r\n      version_number:\r\n        type: string\r\n        default: 0.0.0\r\n        description: Version number to use for the build.\r\n\r\njobs:\r\n  build:\r\n    strategy:\r\n      fail-fast: false\r\n      matrix:\r\n        include:\r\n          # MacOS (Intel)\r\n          - platform: macos-latest\r\n            target: x86_64-apple-darwin\r\n          # MacOS (Apple Silicon)\r\n          - platform: macos-latest\r\n            target: aarch64-apple-darwin\r\n          # 64-bit Windows (Intel & AMD)\r\n          - platform: windows-latest\r\n            target: x86_64-pc-windows-msvc\r\n          # 64-bit Windows (ARM)\r\n          - platform: windows-latest\r\n            target: aarch64-pc-windows-msvc\r\n\r\n    runs-on: ${{ matrix.platform }}\r\n    steps:\r\n      - uses: actions/checkout@v4\r\n\r\n      - uses: dtolnay/rust-toolchain@21dc36fb71dd22e3317045c0c31a3f4249868b17\r\n        with:\r\n          targets: ${{ matrix.target }}\r\n          toolchain: nightly\r\n\r\n      - uses: swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3\r\n        with:\r\n          shared-key: ${{ matrix.target }}-${{ hashFiles('Cargo.lock') }}\r\n\r\n      - name: Build for ${{ matrix.target }}\r\n        env:\r\n          VERSION_NUMBER: ${{ inputs.version_number }}\r\n          # Conditionally enable UIAccess feature for Windows.\r\n          FEATURES: ${{ matrix.platform == 'windows-latest' && inputs.enable_ui_access && '--features ui_access' || '' }}\r\n          # Include `wm-watcher` when building for Windows.\r\n          SCOPE: ${{ matrix.platform == 'windows-latest' && '--workspace' || '' }}\r\n        shell: bash\r\n        run: cargo build --locked --release --target ${{ matrix.target }} $SCOPE $FEATURES\r\n\r\n      - name: Create upload directory\r\n        shell: bash\r\n        run: |\r\n          SOURCE_DIR=\"target/${{ matrix.target }}/release\"\r\n          UPLOAD_DIR=\"temp/$SOURCE_DIR\"\r\n          mkdir -p \"$UPLOAD_DIR\"\r\n\r\n          # Move build artifacts. Suppress errors for missing platform-specific binaries.\r\n          mv \"$SOURCE_DIR\"/glazewm{,-cli} \\\r\n            \"$SOURCE_DIR\"/glazewm{,-cli,-watcher}.exe \\\r\n            \"$UPLOAD_DIR/\" 2>/dev/null || true\r\n\r\n      - uses: actions/upload-artifact@v6\r\n        with:\r\n          if-no-files-found: error\r\n          name: build-${{ matrix.target }}\r\n          path: temp\r\n"
  },
  {
    "path": ".github/workflows/lint-check.yaml",
    "content": "name: Lint check\r\n\r\non:\r\n  push:\r\n  pull_request:\r\n    types: [opened, synchronize, reopened]\r\n\r\njobs:\r\n  lint-check:\r\n    strategy:\r\n      fail-fast: false\r\n      matrix:\r\n        include:\r\n          # MacOS (Apple Silicon)\r\n          - platform: macos-latest\r\n            target: aarch64-apple-darwin\r\n          # Windows (x64)\r\n          - platform: windows-latest\r\n            target: x86_64-pc-windows-msvc\r\n\r\n    runs-on: ${{ matrix.platform }}\r\n    steps:\r\n      - uses: actions/checkout@v4\r\n\r\n      - uses: dtolnay/rust-toolchain@21dc36fb71dd22e3317045c0c31a3f4249868b17\r\n        with:\r\n          components: clippy, rustfmt\r\n          targets: ${{ matrix.target }}\r\n          toolchain: nightly\r\n\r\n      - uses: swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3\r\n        with:\r\n          shared-key: ${{ matrix.target }}-${{ hashFiles('Cargo.lock') }}\r\n\r\n      - name: Check formatting (rustfmt)\r\n        run: cargo fmt --check\r\n\r\n      # Clippy itself runs `cargo check`, so this will also check for compilation errors.\r\n      - name: Check linting (clippy)\r\n        run: cargo clippy --all-targets --all-features -- -D warnings\r\n"
  },
  {
    "path": ".github/workflows/package.yaml",
    "content": "name: Package\r\n\r\non:\r\n  workflow_call:\r\n    inputs:\r\n      version_number:\r\n        type: string\r\n        default: 0.0.0\r\n        description: Version number to use for the build (e.g. `1.0.0`).\r\n  workflow_dispatch:\r\n    inputs:\r\n      version_number:\r\n        type: string\r\n        default: 0.0.0\r\n        description: Version number to use for the build (e.g. `1.0.0`).\r\n\r\njobs:\r\n  build:\r\n    uses: ./.github/workflows/build.yaml\r\n    with:\r\n      enable_ui_access: true\r\n      version_number: ${{ inputs.version_number }}\r\n\r\n  package-windows:\r\n    needs: build\r\n    runs-on: windows-latest\r\n    env:\r\n      VERSION: ${{ inputs.version_number }}\r\n    steps:\r\n      - uses: actions/checkout@v4\r\n\r\n      - name: Download build artifacts\r\n        uses: actions/download-artifact@v8\r\n        with:\r\n          pattern: build-*\r\n          merge-multiple: true\r\n\r\n      - name: Install WiX and its extensions\r\n        run: |\r\n          dotnet tool install --global wix --version 5.0.0\r\n          wix extension add WixToolset.UI.wixext/5 WixToolset.Util.wixext/5 WixToolset.BootstrapperApplications.wixext/5\r\n\r\n      - name: Install AzureSignTool\r\n        run: |\r\n          dotnet tool install --global AzureSignTool --version 5.0.0\r\n\r\n      - name: Run packaging script\r\n        env:\r\n          AZ_VAULT_URL: ${{ secrets.AZ_VAULT_URL }}\r\n          AZ_CERT_NAME: ${{ secrets.AZ_CERT_NAME }}\r\n          AZ_CLIENT_ID: ${{ secrets.AZ_CLIENT_ID }}\r\n          AZ_CLIENT_SECRET: ${{ secrets.AZ_CLIENT_SECRET }}\r\n          AZ_TENANT_ID: ${{ secrets.AZ_TENANT_ID }}\r\n          RFC3161_TIMESTAMP_URL: ${{ vars.RFC3161_TIMESTAMP_URL }}\r\n        run: |\r\n          ./resources/scripts/package.ps1 -VersionNumber $env:VERSION\r\n\r\n      - uses: actions/upload-artifact@v6\r\n        with:\r\n          name: package-windows\r\n          if-no-files-found: error\r\n          path: out/installer-*\r\n\r\n  package-macos:\r\n    needs: build\r\n    runs-on: macos-latest\r\n    env:\r\n      VERSION: ${{ inputs.version_number }}\r\n    steps:\r\n      - uses: actions/checkout@v4\r\n\r\n      - name: Download build artifacts\r\n        uses: actions/download-artifact@v8\r\n        with:\r\n          pattern: build-*\r\n          merge-multiple: true\r\n\r\n      - name: Create universal binaries\r\n        run: |\r\n          mkdir temp\r\n          for binary in glazewm glazewm-cli; do\r\n            lipo -create \\\r\n              \"target/x86_64-apple-darwin/release/$binary\" \\\r\n              \"target/aarch64-apple-darwin/release/$binary\" \\\r\n              -output \"temp/$binary\"\r\n            chmod +x \"temp/$binary\"\r\n          done\r\n\r\n      - name: Import signing certificate\r\n        env:\r\n          APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}\r\n          APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}\r\n        run: |\r\n          # Create a temporary keychain.\r\n          KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db\r\n          KEYCHAIN_PASSWORD=$(openssl rand -base64 32)\r\n\r\n          security create-keychain -p \"$KEYCHAIN_PASSWORD\" \"$KEYCHAIN_PATH\"\r\n          security set-keychain-settings -lut 21600 \"$KEYCHAIN_PATH\"\r\n          security unlock-keychain -p \"$KEYCHAIN_PASSWORD\" \"$KEYCHAIN_PATH\"\r\n\r\n          # Import the certificate.\r\n          echo \"$APPLE_CERTIFICATE\" | base64 --decode > $RUNNER_TEMP/certificate.p12\r\n          security import $RUNNER_TEMP/certificate.p12 \\\r\n            -P \"$APPLE_CERTIFICATE_PASSWORD\" \\\r\n            -A \\\r\n            -t cert \\\r\n            -f pkcs12 \\\r\n            -k \"$KEYCHAIN_PATH\"\r\n\r\n          security list-keychain -d user -s \"$KEYCHAIN_PATH\"\r\n\r\n          # Store the keychain path for later steps.\r\n          echo \"KEYCHAIN_PATH=$KEYCHAIN_PATH\" >> $GITHUB_ENV\r\n\r\n      # Make sure to sign the binaries before they're copied into the app bundle.\r\n      - name: Sign universal binaries\r\n        env:\r\n          APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}\r\n        run: |\r\n          for binary in glazewm glazewm-cli; do\r\n            codesign --force --options runtime --timestamp \\\r\n              --sign \"$APPLE_SIGNING_IDENTITY\" \\\r\n              \"temp/$binary\"\r\n            codesign --verify --verbose=2 \"temp/$binary\"\r\n          done\r\n\r\n      - name: Convert icon to ICNS\r\n        run: |\r\n          ICONSET_DIR=\"temp/icon.iconset\"\r\n          mkdir -p $ICONSET_DIR\r\n          sips -z 16 16     resources/assets/icon.png --out $ICONSET_DIR/icon_16x16.png\r\n          sips -z 32 32     resources/assets/icon.png --out $ICONSET_DIR/icon_16x16@2x.png\r\n          sips -z 32 32     resources/assets/icon.png --out $ICONSET_DIR/icon_32x32.png\r\n          sips -z 64 64     resources/assets/icon.png --out $ICONSET_DIR/icon_32x32@2x.png\r\n          sips -z 128 128   resources/assets/icon.png --out $ICONSET_DIR/icon_128x128.png\r\n          sips -z 256 256   resources/assets/icon.png --out $ICONSET_DIR/icon_128x128@2x.png\r\n          sips -z 256 256   resources/assets/icon.png --out $ICONSET_DIR/icon_256x256.png\r\n          sips -z 512 512   resources/assets/icon.png --out $ICONSET_DIR/icon_256x256@2x.png\r\n          sips -z 512 512   resources/assets/icon.png --out $ICONSET_DIR/icon_512x512.png\r\n          sips -z 1024 1024 resources/assets/icon.png --out $ICONSET_DIR/icon_512x512@2x.png\r\n          iconutil -c icns $ICONSET_DIR -o temp/icon.icns\r\n\r\n      - name: Create app bundle\r\n        run: |\r\n          CONTENTS_DIR=\"temp/GlazeWM.app/Contents\"\r\n          mkdir -p \"$CONTENTS_DIR/MacOS\" \"$CONTENTS_DIR/Resources\"\r\n\r\n          cp temp/glazewm \"$CONTENTS_DIR/MacOS/\"\r\n          cp temp/glazewm-cli \"$CONTENTS_DIR/MacOS/\"\r\n          cp temp/icon.icns \"$CONTENTS_DIR/Resources/icon.icns\"\r\n          chmod +x \"$CONTENTS_DIR/MacOS\"/*\r\n\r\n          # Substitute version placeholder in Info.plist.\r\n          sed \"s/\\${VERSION}/$VERSION/g\" resources/Info.plist > \"$CONTENTS_DIR/Info.plist\"\r\n\r\n          echo -n \"APPL????\" > \"$CONTENTS_DIR/PkgInfo\"\r\n\r\n      - name: Sign app bundle\r\n        env:\r\n          APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}\r\n        run: |\r\n          codesign --force --options runtime --timestamp \\\r\n            --sign \"$APPLE_SIGNING_IDENTITY\" \\\r\n            temp/GlazeWM.app\r\n          codesign --verify --verbose=2 --deep --strict temp/GlazeWM.app\r\n\r\n      - name: Create DMG\r\n        run: |\r\n          DMG_DIR=\"temp/dmg-contents\"\r\n          mkdir -p $DMG_DIR\r\n          cp -R temp/GlazeWM.app $DMG_DIR/\r\n\r\n          # Create a symbolic link to `/Applications` folder.\r\n          ln -s /Applications $DMG_DIR/Applications\r\n\r\n          # Create the DMG.\r\n          hdiutil create -volname GlazeWM \\\r\n            -srcfolder $DMG_DIR \\\r\n            -ov -format UDZO \\\r\n            installer-universal.dmg\r\n\r\n      - name: Sign DMG\r\n        env:\r\n          APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}\r\n        run: |\r\n          codesign --force --timestamp \\\r\n            --sign \"$APPLE_SIGNING_IDENTITY\" \\\r\n            installer-universal.dmg\r\n\r\n      - name: Notarize DMG\r\n        env:\r\n          APPLE_ID: ${{ secrets.APPLE_ID }}\r\n          APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}\r\n          APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}\r\n        run: |\r\n          # Submit for notarization.\r\n          xcrun notarytool submit installer-universal.dmg \\\r\n            --apple-id \"$APPLE_ID\" \\\r\n            --password \"$APPLE_ID_PASSWORD\" \\\r\n            --team-id \"$APPLE_TEAM_ID\" \\\r\n            --wait\r\n\r\n          # Staple the notarization ticket to the DMG.\r\n          xcrun stapler staple installer-universal.dmg\r\n\r\n      - name: Upload DMG\r\n        uses: actions/upload-artifact@v6\r\n        with:\r\n          name: package-macos\r\n          if-no-files-found: error\r\n          path: installer-universal.dmg\r\n"
  },
  {
    "path": ".github/workflows/pr-title-check.yaml",
    "content": "name: PR title check\r\n\r\non:\r\n  pull_request:\r\n    types: [opened, edited, synchronize, reopened]\r\n\r\njobs:\r\n  pr-title-check:\r\n    runs-on: ubuntu-latest\r\n    steps:\r\n      - uses: glzr-io/actions/semantic-prs@main\r\n        with:\r\n          gh-token: ${{ secrets.GITHUB_TOKEN }}\r\n"
  },
  {
    "path": ".github/workflows/release.yaml",
    "content": "name: Release\r\n\r\non:\r\n  workflow_dispatch:\r\n    inputs:\r\n      version_number:\r\n        type: string\r\n        description: Version number to use for the build (e.g. `1.0.0`).\r\n\r\npermissions:\r\n  contents: write\r\n\r\nconcurrency:\r\n  group: release\r\n\r\njobs:\r\n  package:\r\n    uses: ./.github/workflows/package.yaml\r\n    secrets: inherit\r\n    with:\r\n      version_number: ${{ inputs.version_number }}\r\n\r\n  release:\r\n    needs: package\r\n    runs-on: ubuntu-latest\r\n    steps:\r\n      - uses: actions/checkout@v4\r\n\r\n      - name: Download installers\r\n        uses: actions/download-artifact@v8\r\n        with:\r\n          pattern: package-*\r\n          merge-multiple: true\r\n\r\n      - name: Rename installers\r\n        env:\r\n          # Add a `v` prefix to the version number.\r\n          VERSION: v${{ inputs.version_number }}\r\n        run: |\r\n          mv installer-universal.exe \"glazewm-$VERSION.exe\"\r\n          mv installer-arm64.msi \"standalone-glazewm-$VERSION-arm64.msi\"\r\n          mv installer-x64.msi \"standalone-glazewm-$VERSION-x64.msi\"\r\n          mv installer-universal.dmg \"glazewm-$VERSION.dmg\"\r\n\r\n      - name: Create draft release\r\n        env:\r\n          # Add a `v` prefix to the version number.\r\n          VERSION: v${{ inputs.version_number }}\r\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\r\n        run: |\r\n          gh release create \"$VERSION\" \\\r\n            --title \"$VERSION\" \\\r\n            --generate-notes \\\r\n            --draft \\\r\n            \"./glazewm-$VERSION.exe#$VERSION for Windows (standard)\" \\\r\n            \"./standalone-glazewm-$VERSION-arm64.msi#$VERSION for Windows (standalone, arm64)\" \\\r\n            \"./standalone-glazewm-$VERSION-x64.msi#$VERSION for Windows (standalone, x64)\" \\\r\n            \"./glazewm-$VERSION.dmg#$VERSION for macOS\"\r\n"
  },
  {
    "path": ".github/workflows/winget-release.yaml",
    "content": "name: Winget release\r\n\r\non:\r\n  workflow_dispatch:\r\n  release:\r\n    types: [published]\r\n\r\njobs:\r\n  publish:\r\n    runs-on: ubuntu-latest\r\n    steps:\r\n      - uses: vedantmgoyal9/winget-releaser@19e706d4c9121098010096f9c495a70a7518b30f\r\n        with:\r\n          identifier: glzr-io.glazewm\r\n          installers-regex: 'glazewm-v[0-9.]+\\.exe$'\r\n          token: ${{ secrets.WINGET_TOKEN }}\r\n"
  },
  {
    "path": ".gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\r\n\r\n# Build outputs\r\ntarget/\r\nout/\r\n\r\n# Misc\r\n.DS_Store\r\n*.pem\r\n*.env\r\n.wix/\r\n\r\n# IDE files\r\n.vs/\r\n.idea/\r\n"
  },
  {
    "path": ".vscode/launch.json",
    "content": "{\r\n  \"version\": \"0.2.0\",\r\n  \"configurations\": [\r\n    {\r\n      \"name\": \"Launch\",\r\n      \"type\": \"cppvsdbg\",\r\n      \"request\": \"launch\",\r\n      \"program\": \"${workspaceRoot}/target/debug/glazewm.exe\",\r\n      \"preLaunchTask\": \"rust: cargo build\",\r\n      \"args\": [\"start\"],\r\n      \"stopAtEntry\": false,\r\n      \"cwd\": \"${workspaceRoot}\",\r\n      \"environment\": [],\r\n      \"console\": \"integratedTerminal\"\r\n      // \"externalConsole\": true\r\n    }\r\n  ]\r\n}\r\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\r\n  \"rust-analyzer.check.command\": \"clippy\",\r\n  \"editor.insertSpaces\": true,\r\n  \"editor.tabSize\": 2,\r\n  \"files.insertFinalNewline\": true,\r\n  \"files.trimTrailingWhitespace\": true,\r\n}\r\n"
  },
  {
    "path": ".vscode/tasks.json",
    "content": "{\r\n  \"version\": \"2.0.0\",\r\n  \"tasks\": [\r\n    {\r\n      \"type\": \"cargo\",\r\n      \"command\": \"build\",\r\n      \"problemMatcher\": [\"$rustc\"],\r\n      \"group\": \"build\",\r\n      \"label\": \"rust: cargo build\"\r\n    }\r\n  ]\r\n}\r\n"
  },
  {
    "path": "CLAUDE.md",
    "content": "<project_overview>\r\nGlazeWM is a window manager for macOS and Windows, written in Rust.\r\n\r\nCrate structure:\r\n\r\n- **wm** (bin): Main application, which implements the core window management logic. Install path on Windows: `C:\\Program Files\\glzr.io\\glazewm.exe`\r\n- **wm-cli** (bin, lib): CLI for interacting with the main application. Added to `$PATH` by default. Install path on Windows: `C:\\Program Files\\glzr.io\\cli\\glazewm.exe`\r\n- **wm-common** (lib): Shared types, utilities, and constants used across other crates.\r\n- **wm-ipc-client** (lib): WebSocket client library for IPC with the main application.\r\n- **wm-platform** (lib): Wrappers over platform-specific APIs; other crates do not call Windows/macOS APIs directly.\r\n- **wm-watcher** (Windows-only) (bin): Watchdog process ensuring proper cleanup when the main application exits. Install path on Windows: `C:\\Program Files\\glzr.io\\glazewm-watcher.exe`\r\n\r\n</project_overview>\r\n\r\n<output_guidelines>\r\n\r\n- Be extremely concise. Sacrifice grammar for the sake of conciseness.\r\n- Do not leave partial or simplified implementations.\r\n- The required quality standard is high. Low quality code will be rejected.\r\n- Do not proceed with solutions that are hacky. Solutions must be robust, maintainable, and extendable. Ask guiding questions if uncertain about a solution.\r\n\r\n</output_guidelines>\r\n\r\n<code_style_guidelines>\r\n\r\n- Avoid `.unwrap()` wherever possible.\r\n- For error handling:\r\n  - Use `crate::Error` and `crate::Result` within the `wm-platform` crate.\r\n  - Use `anyhow` in all other crates.\r\n- For logging, use `tracing` macros (e.g. `tracing::info!(\"...\")`).\r\n\r\n</code_style_guidelines>\r\n\r\n<code_comment_guidelines>\r\n\r\n- Functions should always be documented.\r\n- Use punctuation mark at the end of all comments.\r\n- If using unsafe features, include a \"SAFETY: ...\" comment.\r\n- Wrap type names in backticks (e.g. `NativeMonitor`).\r\n\r\nComment structure:\r\n\r\n```rs\r\n/// <Concise summary of the function or type>\r\n///\r\n/// (optional) <Notable caveats for usage (kept brief)>\r\n///\r\n/// (optional) <Describe return value if ambiguous (e.g. \"Returns a vector of `NativeMonitor`, sorted by their position from left-to-right.\")>\r\n///\r\n/// (optional) # Example usage\r\n///\r\n/// <Code block with example usage>\r\n///\r\n/// (optional) # Platform-specific\r\n///\r\n/// <Bullet-point list of behavioral differences on macOS vs Windows>\r\npub fn my_function() { ... }\r\n```\r\n\r\n</code_comment_guidelines>\r\n\r\n<test_guidelines>\r\n\r\n- Use `#[cfg(test)]` for test modules.\r\n- Write unit tests for core functionality.\r\n\r\n</test_guidelines>\r\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to GlazeWM\r\n\r\nThanks for your interest in improving GlazeWM 💛\r\n\r\nThere are fundamentally three ways to contribute:\r\n\r\n1. **Opening issues**: If you believe you've found a bug or have a feature request, open an issue to discuss it.\r\n\r\n2. **Helping triage issues**: Add supporting details and suggestions to existing issues.\r\n\r\n3. **Submitting PRs**: Submit a PR that fixes a bug or implements a feature.\r\n\r\nThe [#glazewm-dev channel ⚡](https://discord.com/invite/ud6z3qjRvM) is also available for any concerns not covered in this guide, please join us!\r\n\r\n## Pull requests & dev workflow\r\n\r\nFor PRs, a good place to start are the issues marked as [`good first issue`](https://github.com/glzr-io/glazewm/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) or [`help wanted`](https://github.com/glzr-io/glazewm/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22). PR's don't have a requirement to have a corresponding issue, but if there is one already, please drop a comment in the issue and we can assign it to you.\r\n\r\n### Setup\r\n\r\nFirst fork, then clone the repo:\r\n\r\n```shell\r\ngit clone git@github.com:your-username/glazewm.git\r\n```\r\n\r\nIf not already installed, [install Rust](https://rustup.rs/), then run:\r\n\r\n```shell\r\n# `cargo build` will build all binaries and libraries.\r\n# `cargo run` will run the default binary, which is configured to be the wm.\r\ncargo build && cargo run\r\n```\r\n\r\nAfter making your changes, push to your fork and [submit a pull request](https://github.com/glzr-io/zebar/pulls) against the `main` branch. Please try to address only a single feature or fix in the PR so that it's easy to review.\r\n\r\n### Tips\r\n\r\nIf using VSCode, it's recommended to use the [Rust Analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) extension. Get automatic linting by adding this to your VSCode's `settings.json`:\r\n\r\n```json\r\n{\r\n  \"rust-analyzer.check.command\": \"clippy\"\r\n}\r\n```\r\n\r\n## Codebase overview\r\n\r\nKnowledge of the entire codebase should never be required to make changes. The following should hopefully help with understanding a particular part of the codebase.\r\n\r\n### Crates\r\n\r\nGlazeWM is organized into several Rust crates:\r\n\r\n- `wm` (bin): Main application, which implements the core window management logic.\r\n  - Gets installed to `C:\\Program Files\\glzr.io\\glazewm.exe`.\r\n- `wm-cli` (bin/lib): CLI for interacting with the main application.\r\n  - Gets installed to `C:\\Program Files\\glzr.io\\cli\\glazewm.exe`. This is added to `$PATH` by default.\r\n- `wm-common` (lib): Shared types, utilities, and constants used across other crates.\r\n- `wm-ipc-client` (lib): WebSocket client library for IPC with the main application.\r\n- `wm-platform` (lib): Wrappers over platform-specific API's - other crates don't interact directly with the Windows and macOS API's.\r\n- `wm-watcher` (bin): Watchdog process that ensures proper cleanup when the main application exits.\r\n  - Gets installed to `C:\\Program Files\\glzr.io\\glazewm-watcher.exe`.\r\n\r\n### Commands & events\r\n\r\nGlazeWM uses a command-event architecture. The state of the WM (stored in [`WmState`](https://github.com/glzr-io/glazewm/blob/main/packages/wm/src/wm_state.rs)) is modified via [commands](https://github.com/glzr-io/glazewm/tree/main/packages/wm/src/commands) and [events](https://github.com/glzr-io/glazewm/tree/main/packages/wm/src/events).\r\n\r\n- Commands are run as a result of keybindings, IPC calls, the CLI (which calls IPC internally), or by being called from another command. Most commands are just for internal use and might not have a public-facing API.\r\n- Events arise from the Windows platform (e.g. a window being created, destroyed, focused, etc.). Each of these events have a handler that then modifies the WM state.\r\n\r\nCommands and events are processed in a loop in [`start_wm`](https://github.com/glzr-io/glazewm/blob/main/packages/wm/src/main.rs#L68).\r\n\r\n## Container tree\r\n\r\nWindows in GlazeWM are organized within a tree hierarchy with the following \"container\" types:\r\n\r\n- Root\r\n- Monitors (physical displays)\r\n- Workspaces (virtual groups of windows)\r\n- Split containers (for tiling layouts)\r\n- Windows (application windows)\r\n\r\nHere's an example container tree:\r\n\r\n```\r\n                                            Root\r\n                                             |\r\n                            +----------------+----------------+\r\n                            |                                 |\r\n                        Monitor 1                         Monitor 2\r\n                            |                                 |\r\n                   +--------+--------+                        |\r\n                   |                 |                        |\r\n               Workspace 1       Workspace 2             Workspace 3\r\n               [horizontal]      [vertical]              [horizontal]\r\n                   |                 |                        |\r\n                   |                 |                        |\r\n              +----+----+      Tiling Window            +-----+-----+\r\n              |         |        (Spotify)              |           |\r\n        Tiling Window   |                       Tiling Window  Floating Window\r\n         (Terminal)     |                         (Discord)       (Slack)\r\n                        |\r\n                        |\r\n                  Split Container\r\n                    [vertical]\r\n                        |\r\n                   +----+----+\r\n                   |         |\r\n           Tiling Window  Tiling Window\r\n             (Chrome)      (VS Code)\r\n```\r\n\r\nWindows can be either tiling (nested within split containers) or non-tiling (floating, minimized, maximized, or fullscreen). Non-tiling windows are always direct children of a workspace. Split containers can only have windows as children, and must have at least one child window.\r\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[workspace]\r\nresolver = \"2\"\r\nmembers = [\"packages/*\"]\r\ndefault-members = [\"packages/wm\", \"packages/wm-cli\"]\r\n\r\n[workspace.dependencies]\r\nanyhow = { version = \"1\", features = [\"backtrace\"] }\r\nclap = { version = \"4\", features = [\"derive\"] }\r\nfutures-util = \"0.3\"\r\nhome = \"0.5\"\r\nserde = { version = \"1\", features = [\"derive\"] }\r\nserde_json = { version = \"1\", features = [\"raw_value\"] }\r\ntauri-winres = \"0.1\"\r\nthiserror = \"2\"\r\nregex = \"1\"\r\ntokio = { version = \"1\", features = [\"full\"] }\r\ntokio-tungstenite = \"0.26\"\r\ntracing = \"0.1\"\r\ntracing-subscriber = { version = \"0.3\", features = [\"env-filter\"] }\r\nuuid = { version = \"1\", features = [\"v4\", \"serde\"] }\r\nwm-macros = { path = \"packages/wm-macros\" }\r\n\r\n[workspace.lints]\r\nclippy.all = { level = \"warn\", priority = -1 }\r\nclippy.pedantic = { level = \"warn\", priority = -1 }\r\nclippy.missing_errors_doc = \"allow\"\r\n"
  },
  {
    "path": "LICENSE.md",
    "content": "                    GNU GENERAL PUBLIC LICENSE\r\n                       Version 3, 29 June 2007\r\n\r\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\r\n Everyone is permitted to copy and distribute verbatim copies\r\n of this license document, but changing it is not allowed.\r\n\r\n                            Preamble\r\n\r\n  The GNU General Public License is a free, copyleft license for\r\nsoftware and other kinds of works.\r\n\r\n  The licenses for most software and other practical works are designed\r\nto take away your freedom to share and change the works.  By contrast,\r\nthe GNU General Public License is intended to guarantee your freedom to\r\nshare and change all versions of a program--to make sure it remains free\r\nsoftware for all its users.  We, the Free Software Foundation, use the\r\nGNU General Public License for most of our software; it applies also to\r\nany other work released this way by its authors.  You can apply it to\r\nyour programs, too.\r\n\r\n  When we speak of free software, we are referring to freedom, not\r\nprice.  Our General Public Licenses are designed to make sure that you\r\nhave the freedom to distribute copies of free software (and charge for\r\nthem if you wish), that you receive source code or can get it if you\r\nwant it, that you can change the software or use pieces of it in new\r\nfree programs, and that you know you can do these things.\r\n\r\n  To protect your rights, we need to prevent others from denying you\r\nthese rights or asking you to surrender the rights.  Therefore, you have\r\ncertain responsibilities if you distribute copies of the software, or if\r\nyou modify it: responsibilities to respect the freedom of others.\r\n\r\n  For example, if you distribute copies of such a program, whether\r\ngratis or for a fee, you must pass on to the recipients the same\r\nfreedoms that you received.  You must make sure that they, too, receive\r\nor can get the source code.  And you must show them these terms so they\r\nknow their rights.\r\n\r\n  Developers that use the GNU GPL protect your rights with two steps:\r\n(1) assert copyright on the software, and (2) offer you this License\r\ngiving you legal permission to copy, distribute and/or modify it.\r\n\r\n  For the developers' and authors' protection, the GPL clearly explains\r\nthat there is no warranty for this free software.  For both users' and\r\nauthors' sake, the GPL requires that modified versions be marked as\r\nchanged, so that their problems will not be attributed erroneously to\r\nauthors of previous versions.\r\n\r\n  Some devices are designed to deny users access to install or run\r\nmodified versions of the software inside them, although the manufacturer\r\ncan do so.  This is fundamentally incompatible with the aim of\r\nprotecting users' freedom to change the software.  The systematic\r\npattern of such abuse occurs in the area of products for individuals to\r\nuse, which is precisely where it is most unacceptable.  Therefore, we\r\nhave designed this version of the GPL to prohibit the practice for those\r\nproducts.  If such problems arise substantially in other domains, we\r\nstand ready to extend this provision to those domains in future versions\r\nof the GPL, as needed to protect the freedom of users.\r\n\r\n  Finally, every program is threatened constantly by software patents.\r\nStates should not allow patents to restrict development and use of\r\nsoftware on general-purpose computers, but in those that do, we wish to\r\navoid the special danger that patents applied to a free program could\r\nmake it effectively proprietary.  To prevent this, the GPL assures that\r\npatents cannot be used to render the program non-free.\r\n\r\n  The precise terms and conditions for copying, distribution and\r\nmodification follow.\r\n\r\n                       TERMS AND CONDITIONS\r\n\r\n  0. Definitions.\r\n\r\n  \"This License\" refers to version 3 of the GNU General Public License.\r\n\r\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\r\nworks, such as semiconductor masks.\r\n\r\n  \"The Program\" refers to any copyrightable work licensed under this\r\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\r\n\"recipients\" may be individuals or organizations.\r\n\r\n  To \"modify\" a work means to copy from or adapt all or part of the work\r\nin a fashion requiring copyright permission, other than the making of an\r\nexact copy.  The resulting work is called a \"modified version\" of the\r\nearlier work or a work \"based on\" the earlier work.\r\n\r\n  A \"covered work\" means either the unmodified Program or a work based\r\non the Program.\r\n\r\n  To \"propagate\" a work means to do anything with it that, without\r\npermission, would make you directly or secondarily liable for\r\ninfringement under applicable copyright law, except executing it on a\r\ncomputer or modifying a private copy.  Propagation includes copying,\r\ndistribution (with or without modification), making available to the\r\npublic, and in some countries other activities as well.\r\n\r\n  To \"convey\" a work means any kind of propagation that enables other\r\nparties to make or receive copies.  Mere interaction with a user through\r\na computer network, with no transfer of a copy, is not conveying.\r\n\r\n  An interactive user interface displays \"Appropriate Legal Notices\"\r\nto the extent that it includes a convenient and prominently visible\r\nfeature that (1) displays an appropriate copyright notice, and (2)\r\ntells the user that there is no warranty for the work (except to the\r\nextent that warranties are provided), that licensees may convey the\r\nwork under this License, and how to view a copy of this License.  If\r\nthe interface presents a list of user commands or options, such as a\r\nmenu, a prominent item in the list meets this criterion.\r\n\r\n  1. Source Code.\r\n\r\n  The \"source code\" for a work means the preferred form of the work\r\nfor making modifications to it.  \"Object code\" means any non-source\r\nform of a work.\r\n\r\n  A \"Standard Interface\" means an interface that either is an official\r\nstandard defined by a recognized standards body, or, in the case of\r\ninterfaces specified for a particular programming language, one that\r\nis widely used among developers working in that language.\r\n\r\n  The \"System Libraries\" of an executable work include anything, other\r\nthan the work as a whole, that (a) is included in the normal form of\r\npackaging a Major Component, but which is not part of that Major\r\nComponent, and (b) serves only to enable use of the work with that\r\nMajor Component, or to implement a Standard Interface for which an\r\nimplementation is available to the public in source code form.  A\r\n\"Major Component\", in this context, means a major essential component\r\n(kernel, window system, and so on) of the specific operating system\r\n(if any) on which the executable work runs, or a compiler used to\r\nproduce the work, or an object code interpreter used to run it.\r\n\r\n  The \"Corresponding Source\" for a work in object code form means all\r\nthe source code needed to generate, install, and (for an executable\r\nwork) run the object code and to modify the work, including scripts to\r\ncontrol those activities.  However, it does not include the work's\r\nSystem Libraries, or general-purpose tools or generally available free\r\nprograms which are used unmodified in performing those activities but\r\nwhich are not part of the work.  For example, Corresponding Source\r\nincludes interface definition files associated with source files for\r\nthe work, and the source code for shared libraries and dynamically\r\nlinked subprograms that the work is specifically designed to require,\r\nsuch as by intimate data communication or control flow between those\r\nsubprograms and other parts of the work.\r\n\r\n  The Corresponding Source need not include anything that users\r\ncan regenerate automatically from other parts of the Corresponding\r\nSource.\r\n\r\n  The Corresponding Source for a work in source code form is that\r\nsame work.\r\n\r\n  2. Basic Permissions.\r\n\r\n  All rights granted under this License are granted for the term of\r\ncopyright on the Program, and are irrevocable provided the stated\r\nconditions are met.  This License explicitly affirms your unlimited\r\npermission to run the unmodified Program.  The output from running a\r\ncovered work is covered by this License only if the output, given its\r\ncontent, constitutes a covered work.  This License acknowledges your\r\nrights of fair use or other equivalent, as provided by copyright law.\r\n\r\n  You may make, run and propagate covered works that you do not\r\nconvey, without conditions so long as your license otherwise remains\r\nin force.  You may convey covered works to others for the sole purpose\r\nof having them make modifications exclusively for you, or provide you\r\nwith facilities for running those works, provided that you comply with\r\nthe terms of this License in conveying all material for which you do\r\nnot control copyright.  Those thus making or running the covered works\r\nfor you must do so exclusively on your behalf, under your direction\r\nand control, on terms that prohibit them from making any copies of\r\nyour copyrighted material outside their relationship with you.\r\n\r\n  Conveying under any other circumstances is permitted solely under\r\nthe conditions stated below.  Sublicensing is not allowed; section 10\r\nmakes it unnecessary.\r\n\r\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\r\n\r\n  No covered work shall be deemed part of an effective technological\r\nmeasure under any applicable law fulfilling obligations under article\r\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\r\nsimilar laws prohibiting or restricting circumvention of such\r\nmeasures.\r\n\r\n  When you convey a covered work, you waive any legal power to forbid\r\ncircumvention of technological measures to the extent such circumvention\r\nis effected by exercising rights under this License with respect to\r\nthe covered work, and you disclaim any intention to limit operation or\r\nmodification of the work as a means of enforcing, against the work's\r\nusers, your or third parties' legal rights to forbid circumvention of\r\ntechnological measures.\r\n\r\n  4. Conveying Verbatim Copies.\r\n\r\n  You may convey verbatim copies of the Program's source code as you\r\nreceive it, in any medium, provided that you conspicuously and\r\nappropriately publish on each copy an appropriate copyright notice;\r\nkeep intact all notices stating that this License and any\r\nnon-permissive terms added in accord with section 7 apply to the code;\r\nkeep intact all notices of the absence of any warranty; and give all\r\nrecipients a copy of this License along with the Program.\r\n\r\n  You may charge any price or no price for each copy that you convey,\r\nand you may offer support or warranty protection for a fee.\r\n\r\n  5. Conveying Modified Source Versions.\r\n\r\n  You may convey a work based on the Program, or the modifications to\r\nproduce it from the Program, in the form of source code under the\r\nterms of section 4, provided that you also meet all of these conditions:\r\n\r\n    a) The work must carry prominent notices stating that you modified\r\n    it, and giving a relevant date.\r\n\r\n    b) The work must carry prominent notices stating that it is\r\n    released under this License and any conditions added under section\r\n    7.  This requirement modifies the requirement in section 4 to\r\n    \"keep intact all notices\".\r\n\r\n    c) You must license the entire work, as a whole, under this\r\n    License to anyone who comes into possession of a copy.  This\r\n    License will therefore apply, along with any applicable section 7\r\n    additional terms, to the whole of the work, and all its parts,\r\n    regardless of how they are packaged.  This License gives no\r\n    permission to license the work in any other way, but it does not\r\n    invalidate such permission if you have separately received it.\r\n\r\n    d) If the work has interactive user interfaces, each must display\r\n    Appropriate Legal Notices; however, if the Program has interactive\r\n    interfaces that do not display Appropriate Legal Notices, your\r\n    work need not make them do so.\r\n\r\n  A compilation of a covered work with other separate and independent\r\nworks, which are not by their nature extensions of the covered work,\r\nand which are not combined with it such as to form a larger program,\r\nin or on a volume of a storage or distribution medium, is called an\r\n\"aggregate\" if the compilation and its resulting copyright are not\r\nused to limit the access or legal rights of the compilation's users\r\nbeyond what the individual works permit.  Inclusion of a covered work\r\nin an aggregate does not cause this License to apply to the other\r\nparts of the aggregate.\r\n\r\n  6. Conveying Non-Source Forms.\r\n\r\n  You may convey a covered work in object code form under the terms\r\nof sections 4 and 5, provided that you also convey the\r\nmachine-readable Corresponding Source under the terms of this License,\r\nin one of these ways:\r\n\r\n    a) Convey the object code in, or embodied in, a physical product\r\n    (including a physical distribution medium), accompanied by the\r\n    Corresponding Source fixed on a durable physical medium\r\n    customarily used for software interchange.\r\n\r\n    b) Convey the object code in, or embodied in, a physical product\r\n    (including a physical distribution medium), accompanied by a\r\n    written offer, valid for at least three years and valid for as\r\n    long as you offer spare parts or customer support for that product\r\n    model, to give anyone who possesses the object code either (1) a\r\n    copy of the Corresponding Source for all the software in the\r\n    product that is covered by this License, on a durable physical\r\n    medium customarily used for software interchange, for a price no\r\n    more than your reasonable cost of physically performing this\r\n    conveying of source, or (2) access to copy the\r\n    Corresponding Source from a network server at no charge.\r\n\r\n    c) Convey individual copies of the object code with a copy of the\r\n    written offer to provide the Corresponding Source.  This\r\n    alternative is allowed only occasionally and noncommercially, and\r\n    only if you received the object code with such an offer, in accord\r\n    with subsection 6b.\r\n\r\n    d) Convey the object code by offering access from a designated\r\n    place (gratis or for a charge), and offer equivalent access to the\r\n    Corresponding Source in the same way through the same place at no\r\n    further charge.  You need not require recipients to copy the\r\n    Corresponding Source along with the object code.  If the place to\r\n    copy the object code is a network server, the Corresponding Source\r\n    may be on a different server (operated by you or a third party)\r\n    that supports equivalent copying facilities, provided you maintain\r\n    clear directions next to the object code saying where to find the\r\n    Corresponding Source.  Regardless of what server hosts the\r\n    Corresponding Source, you remain obligated to ensure that it is\r\n    available for as long as needed to satisfy these requirements.\r\n\r\n    e) Convey the object code using peer-to-peer transmission, provided\r\n    you inform other peers where the object code and Corresponding\r\n    Source of the work are being offered to the general public at no\r\n    charge under subsection 6d.\r\n\r\n  A separable portion of the object code, whose source code is excluded\r\nfrom the Corresponding Source as a System Library, need not be\r\nincluded in conveying the object code work.\r\n\r\n  A \"User Product\" is either (1) a \"consumer product\", which means any\r\ntangible personal property which is normally used for personal, family,\r\nor household purposes, or (2) anything designed or sold for incorporation\r\ninto a dwelling.  In determining whether a product is a consumer product,\r\ndoubtful cases shall be resolved in favor of coverage.  For a particular\r\nproduct received by a particular user, \"normally used\" refers to a\r\ntypical or common use of that class of product, regardless of the status\r\nof the particular user or of the way in which the particular user\r\nactually uses, or expects or is expected to use, the product.  A product\r\nis a consumer product regardless of whether the product has substantial\r\ncommercial, industrial or non-consumer uses, unless such uses represent\r\nthe only significant mode of use of the product.\r\n\r\n  \"Installation Information\" for a User Product means any methods,\r\nprocedures, authorization keys, or other information required to install\r\nand execute modified versions of a covered work in that User Product from\r\na modified version of its Corresponding Source.  The information must\r\nsuffice to ensure that the continued functioning of the modified object\r\ncode is in no case prevented or interfered with solely because\r\nmodification has been made.\r\n\r\n  If you convey an object code work under this section in, or with, or\r\nspecifically for use in, a User Product, and the conveying occurs as\r\npart of a transaction in which the right of possession and use of the\r\nUser Product is transferred to the recipient in perpetuity or for a\r\nfixed term (regardless of how the transaction is characterized), the\r\nCorresponding Source conveyed under this section must be accompanied\r\nby the Installation Information.  But this requirement does not apply\r\nif neither you nor any third party retains the ability to install\r\nmodified object code on the User Product (for example, the work has\r\nbeen installed in ROM).\r\n\r\n  The requirement to provide Installation Information does not include a\r\nrequirement to continue to provide support service, warranty, or updates\r\nfor a work that has been modified or installed by the recipient, or for\r\nthe User Product in which it has been modified or installed.  Access to a\r\nnetwork may be denied when the modification itself materially and\r\nadversely affects the operation of the network or violates the rules and\r\nprotocols for communication across the network.\r\n\r\n  Corresponding Source conveyed, and Installation Information provided,\r\nin accord with this section must be in a format that is publicly\r\ndocumented (and with an implementation available to the public in\r\nsource code form), and must require no special password or key for\r\nunpacking, reading or copying.\r\n\r\n  7. Additional Terms.\r\n\r\n  \"Additional permissions\" are terms that supplement the terms of this\r\nLicense by making exceptions from one or more of its conditions.\r\nAdditional permissions that are applicable to the entire Program shall\r\nbe treated as though they were included in this License, to the extent\r\nthat they are valid under applicable law.  If additional permissions\r\napply only to part of the Program, that part may be used separately\r\nunder those permissions, but the entire Program remains governed by\r\nthis License without regard to the additional permissions.\r\n\r\n  When you convey a copy of a covered work, you may at your option\r\nremove any additional permissions from that copy, or from any part of\r\nit.  (Additional permissions may be written to require their own\r\nremoval in certain cases when you modify the work.)  You may place\r\nadditional permissions on material, added by you to a covered work,\r\nfor which you have or can give appropriate copyright permission.\r\n\r\n  Notwithstanding any other provision of this License, for material you\r\nadd to a covered work, you may (if authorized by the copyright holders of\r\nthat material) supplement the terms of this License with terms:\r\n\r\n    a) Disclaiming warranty or limiting liability differently from the\r\n    terms of sections 15 and 16 of this License; or\r\n\r\n    b) Requiring preservation of specified reasonable legal notices or\r\n    author attributions in that material or in the Appropriate Legal\r\n    Notices displayed by works containing it; or\r\n\r\n    c) Prohibiting misrepresentation of the origin of that material, or\r\n    requiring that modified versions of such material be marked in\r\n    reasonable ways as different from the original version; or\r\n\r\n    d) Limiting the use for publicity purposes of names of licensors or\r\n    authors of the material; or\r\n\r\n    e) Declining to grant rights under trademark law for use of some\r\n    trade names, trademarks, or service marks; or\r\n\r\n    f) Requiring indemnification of licensors and authors of that\r\n    material by anyone who conveys the material (or modified versions of\r\n    it) with contractual assumptions of liability to the recipient, for\r\n    any liability that these contractual assumptions directly impose on\r\n    those licensors and authors.\r\n\r\n  All other non-permissive additional terms are considered \"further\r\nrestrictions\" within the meaning of section 10.  If the Program as you\r\nreceived it, or any part of it, contains a notice stating that it is\r\ngoverned by this License along with a term that is a further\r\nrestriction, you may remove that term.  If a license document contains\r\na further restriction but permits relicensing or conveying under this\r\nLicense, you may add to a covered work material governed by the terms\r\nof that license document, provided that the further restriction does\r\nnot survive such relicensing or conveying.\r\n\r\n  If you add terms to a covered work in accord with this section, you\r\nmust place, in the relevant source files, a statement of the\r\nadditional terms that apply to those files, or a notice indicating\r\nwhere to find the applicable terms.\r\n\r\n  Additional terms, permissive or non-permissive, may be stated in the\r\nform of a separately written license, or stated as exceptions;\r\nthe above requirements apply either way.\r\n\r\n  8. Termination.\r\n\r\n  You may not propagate or modify a covered work except as expressly\r\nprovided under this License.  Any attempt otherwise to propagate or\r\nmodify it is void, and will automatically terminate your rights under\r\nthis License (including any patent licenses granted under the third\r\nparagraph of section 11).\r\n\r\n  However, if you cease all violation of this License, then your\r\nlicense from a particular copyright holder is reinstated (a)\r\nprovisionally, unless and until the copyright holder explicitly and\r\nfinally terminates your license, and (b) permanently, if the copyright\r\nholder fails to notify you of the violation by some reasonable means\r\nprior to 60 days after the cessation.\r\n\r\n  Moreover, your license from a particular copyright holder is\r\nreinstated permanently if the copyright holder notifies you of the\r\nviolation by some reasonable means, this is the first time you have\r\nreceived notice of violation of this License (for any work) from that\r\ncopyright holder, and you cure the violation prior to 30 days after\r\nyour receipt of the notice.\r\n\r\n  Termination of your rights under this section does not terminate the\r\nlicenses of parties who have received copies or rights from you under\r\nthis License.  If your rights have been terminated and not permanently\r\nreinstated, you do not qualify to receive new licenses for the same\r\nmaterial under section 10.\r\n\r\n  9. Acceptance Not Required for Having Copies.\r\n\r\n  You are not required to accept this License in order to receive or\r\nrun a copy of the Program.  Ancillary propagation of a covered work\r\noccurring solely as a consequence of using peer-to-peer transmission\r\nto receive a copy likewise does not require acceptance.  However,\r\nnothing other than this License grants you permission to propagate or\r\nmodify any covered work.  These actions infringe copyright if you do\r\nnot accept this License.  Therefore, by modifying or propagating a\r\ncovered work, you indicate your acceptance of this License to do so.\r\n\r\n  10. Automatic Licensing of Downstream Recipients.\r\n\r\n  Each time you convey a covered work, the recipient automatically\r\nreceives a license from the original licensors, to run, modify and\r\npropagate that work, subject to this License.  You are not responsible\r\nfor enforcing compliance by third parties with this License.\r\n\r\n  An \"entity transaction\" is a transaction transferring control of an\r\norganization, or substantially all assets of one, or subdividing an\r\norganization, or merging organizations.  If propagation of a covered\r\nwork results from an entity transaction, each party to that\r\ntransaction who receives a copy of the work also receives whatever\r\nlicenses to the work the party's predecessor in interest had or could\r\ngive under the previous paragraph, plus a right to possession of the\r\nCorresponding Source of the work from the predecessor in interest, if\r\nthe predecessor has it or can get it with reasonable efforts.\r\n\r\n  You may not impose any further restrictions on the exercise of the\r\nrights granted or affirmed under this License.  For example, you may\r\nnot impose a license fee, royalty, or other charge for exercise of\r\nrights granted under this License, and you may not initiate litigation\r\n(including a cross-claim or counterclaim in a lawsuit) alleging that\r\nany patent claim is infringed by making, using, selling, offering for\r\nsale, or importing the Program or any portion of it.\r\n\r\n  11. Patents.\r\n\r\n  A \"contributor\" is a copyright holder who authorizes use under this\r\nLicense of the Program or a work on which the Program is based.  The\r\nwork thus licensed is called the contributor's \"contributor version\".\r\n\r\n  A contributor's \"essential patent claims\" are all patent claims\r\nowned or controlled by the contributor, whether already acquired or\r\nhereafter acquired, that would be infringed by some manner, permitted\r\nby this License, of making, using, or selling its contributor version,\r\nbut do not include claims that would be infringed only as a\r\nconsequence of further modification of the contributor version.  For\r\npurposes of this definition, \"control\" includes the right to grant\r\npatent sublicenses in a manner consistent with the requirements of\r\nthis License.\r\n\r\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\r\npatent license under the contributor's essential patent claims, to\r\nmake, use, sell, offer for sale, import and otherwise run, modify and\r\npropagate the contents of its contributor version.\r\n\r\n  In the following three paragraphs, a \"patent license\" is any express\r\nagreement or commitment, however denominated, not to enforce a patent\r\n(such as an express permission to practice a patent or covenant not to\r\nsue for patent infringement).  To \"grant\" such a patent license to a\r\nparty means to make such an agreement or commitment not to enforce a\r\npatent against the party.\r\n\r\n  If you convey a covered work, knowingly relying on a patent license,\r\nand the Corresponding Source of the work is not available for anyone\r\nto copy, free of charge and under the terms of this License, through a\r\npublicly available network server or other readily accessible means,\r\nthen you must either (1) cause the Corresponding Source to be so\r\navailable, or (2) arrange to deprive yourself of the benefit of the\r\npatent license for this particular work, or (3) arrange, in a manner\r\nconsistent with the requirements of this License, to extend the patent\r\nlicense to downstream recipients.  \"Knowingly relying\" means you have\r\nactual knowledge that, but for the patent license, your conveying the\r\ncovered work in a country, or your recipient's use of the covered work\r\nin a country, would infringe one or more identifiable patents in that\r\ncountry that you have reason to believe are valid.\r\n\r\n  If, pursuant to or in connection with a single transaction or\r\narrangement, you convey, or propagate by procuring conveyance of, a\r\ncovered work, and grant a patent license to some of the parties\r\nreceiving the covered work authorizing them to use, propagate, modify\r\nor convey a specific copy of the covered work, then the patent license\r\nyou grant is automatically extended to all recipients of the covered\r\nwork and works based on it.\r\n\r\n  A patent license is \"discriminatory\" if it does not include within\r\nthe scope of its coverage, prohibits the exercise of, or is\r\nconditioned on the non-exercise of one or more of the rights that are\r\nspecifically granted under this License.  You may not convey a covered\r\nwork if you are a party to an arrangement with a third party that is\r\nin the business of distributing software, under which you make payment\r\nto the third party based on the extent of your activity of conveying\r\nthe work, and under which the third party grants, to any of the\r\nparties who would receive the covered work from you, a discriminatory\r\npatent license (a) in connection with copies of the covered work\r\nconveyed by you (or copies made from those copies), or (b) primarily\r\nfor and in connection with specific products or compilations that\r\ncontain the covered work, unless you entered into that arrangement,\r\nor that patent license was granted, prior to 28 March 2007.\r\n\r\n  Nothing in this License shall be construed as excluding or limiting\r\nany implied license or other defenses to infringement that may\r\notherwise be available to you under applicable patent law.\r\n\r\n  12. No Surrender of Others' Freedom.\r\n\r\n  If conditions are imposed on you (whether by court order, agreement or\r\notherwise) that contradict the conditions of this License, they do not\r\nexcuse you from the conditions of this License.  If you cannot convey a\r\ncovered work so as to satisfy simultaneously your obligations under this\r\nLicense and any other pertinent obligations, then as a consequence you may\r\nnot convey it at all.  For example, if you agree to terms that obligate you\r\nto collect a royalty for further conveying from those to whom you convey\r\nthe Program, the only way you could satisfy both those terms and this\r\nLicense would be to refrain entirely from conveying the Program.\r\n\r\n  13. Use with the GNU Affero General Public License.\r\n\r\n  Notwithstanding any other provision of this License, you have\r\npermission to link or combine any covered work with a work licensed\r\nunder version 3 of the GNU Affero General Public License into a single\r\ncombined work, and to convey the resulting work.  The terms of this\r\nLicense will continue to apply to the part which is the covered work,\r\nbut the special requirements of the GNU Affero General Public License,\r\nsection 13, concerning interaction through a network will apply to the\r\ncombination as such.\r\n\r\n  14. Revised Versions of this License.\r\n\r\n  The Free Software Foundation may publish revised and/or new versions of\r\nthe GNU General Public License from time to time.  Such new versions will\r\nbe similar in spirit to the present version, but may differ in detail to\r\naddress new problems or concerns.\r\n\r\n  Each version is given a distinguishing version number.  If the\r\nProgram specifies that a certain numbered version of the GNU General\r\nPublic License \"or any later version\" applies to it, you have the\r\noption of following the terms and conditions either of that numbered\r\nversion or of any later version published by the Free Software\r\nFoundation.  If the Program does not specify a version number of the\r\nGNU General Public License, you may choose any version ever published\r\nby the Free Software Foundation.\r\n\r\n  If the Program specifies that a proxy can decide which future\r\nversions of the GNU General Public License can be used, that proxy's\r\npublic statement of acceptance of a version permanently authorizes you\r\nto choose that version for the Program.\r\n\r\n  Later license versions may give you additional or different\r\npermissions.  However, no additional obligations are imposed on any\r\nauthor or copyright holder as a result of your choosing to follow a\r\nlater version.\r\n\r\n  15. Disclaimer of Warranty.\r\n\r\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\r\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\r\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\r\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\r\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\r\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\r\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\r\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\r\n\r\n  16. Limitation of Liability.\r\n\r\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\r\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\r\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\r\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\r\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\r\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\r\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\r\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\r\nSUCH DAMAGES.\r\n\r\n  17. Interpretation of Sections 15 and 16.\r\n\r\n  If the disclaimer of warranty and limitation of liability provided\r\nabove cannot be given local legal effect according to their terms,\r\nreviewing courts shall apply local law that most closely approximates\r\nan absolute waiver of all civil liability in connection with the\r\nProgram, unless a warranty or assumption of liability accompanies a\r\ncopy of the Program in return for a fee.\r\n\r\n                     END OF TERMS AND CONDITIONS\r\n\r\n            How to Apply These Terms to Your New Programs\r\n\r\n  If you develop a new program, and you want it to be of the greatest\r\npossible use to the public, the best way to achieve this is to make it\r\nfree software which everyone can redistribute and change under these terms.\r\n\r\n  To do so, attach the following notices to the program.  It is safest\r\nto attach them to the start of each source file to most effectively\r\nstate the exclusion of warranty; and each file should have at least\r\nthe \"copyright\" line and a pointer to where the full notice is found.\r\n\r\n    <one line to give the program's name and a brief idea of what it does.>\r\n    Copyright (C) <year>  <name of author>\r\n\r\n    This program is free software: you can redistribute it and/or modify\r\n    it under the terms of the GNU General Public License as published by\r\n    the Free Software Foundation, either version 3 of the License, or\r\n    (at your option) any later version.\r\n\r\n    This program is distributed in the hope that it will be useful,\r\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r\n    GNU General Public License for more details.\r\n\r\n    You should have received a copy of the GNU General Public License\r\n    along with this program.  If not, see <https://www.gnu.org/licenses/>.\r\n\r\nAlso add information on how to contact you by electronic and paper mail.\r\n\r\n  If the program does terminal interaction, make it output a short\r\nnotice like this when it starts in an interactive mode:\r\n\r\n    <program>  Copyright (C) <year>  <name of author>\r\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\r\n    This is free software, and you are welcome to redistribute it\r\n    under certain conditions; type `show c' for details.\r\n\r\nThe hypothetical commands `show w' and `show c' should show the appropriate\r\nparts of the General Public License.  Of course, your program's commands\r\nmight be different; for a GUI interface, you would use an \"about box\".\r\n\r\n  You should also get your employer (if you work as a programmer) or school,\r\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\r\nFor more information on this, and how to apply and follow the GNU GPL, see\r\n<https://www.gnu.org/licenses/>.\r\n\r\n  The GNU General Public License does not permit incorporating your program\r\ninto proprietary programs.  If your program is a subroutine library, you\r\nmay consider it more useful to permit linking proprietary applications with\r\nthe library.  If this is what you want to do, use the GNU Lesser General\r\nPublic License instead of this License.  But first, please read\r\n<https://www.gnu.org/licenses/why-not-lgpl.html>.\r\n"
  },
  {
    "path": "README.md",
    "content": "<div align=\"center\">\r\n\r\n> V3 is finally out - check out the changelog [here](https://github.com/glzr-io/GlazeWM/releases) 🔥\r\n\r\n  <br>\r\n  <img src=\"./resources/assets/logo.svg\" width=\"230\" alt=\"GlazeWM logo\" />\r\n  <br>\r\n\r\n# GlazeWM\r\n\r\n**A tiling window manager for Windows inspired by i3wm.**\r\n\r\n[![Discord invite][discord-badge]][discord-link]\r\n[![Downloads][downloads-badge]][downloads-link]\r\n[![Good first issues][issues-badge]][issues-link]\r\n\r\nGlazeWM lets you easily organize windows and adjust their layout on the fly by using keyboard-driven commands.\r\n\r\n[Installation](#installation) •\r\n[Default keybindings](#default-keybindings) •\r\n[Config documentation](#config-documentation) •\r\n[FAQ](#faq) •\r\n[Contributing ↗](https://github.com/glzr-io/glazewm/blob/main/CONTRIBUTING.md)\r\n\r\n![Demo video][demo-video]\r\n\r\n</div>\r\n\r\n### 🌟 Key features\r\n\r\n- Simple YAML configuration\r\n- Multi-monitor support\r\n- Customizable rules for specific windows\r\n- Easy one-click installation\r\n- Integration with [Zebar](https://github.com/glzr-io/zebar) as a status bar\r\n\r\n## Installation\r\n\r\n**The latest version of GlazeWM is downloadable via [releases](https://github.com/glzr-io/GlazeWM/releases).** Zebar can optionally be installed as well via a checkbox during installation.\r\n\r\nGlazeWM is also available through several package managers:\r\n\r\n**Winget**\r\n\r\n```sh\r\nwinget install GlazeWM\r\n```\r\n\r\n**Chocolatey**\r\n\r\n```sh\r\nchoco install glazewm\r\n```\r\n\r\n**Scoop**\r\n\r\n```sh\r\nscoop bucket add extras\r\nscoop install extras/glazewm\r\n```\r\n\r\n## Contributing\r\n\r\nHelp fix something that annoys you, or add a feature you've been wanting for a long time! Contributions are very welcome.\r\n\r\nLocal development and guidelines are available in the [contributing guide](https://github.com/glzr-io/glazewm/blob/main/CONTRIBUTING.md).\r\n\r\n## Default keybindings\r\n\r\nOn the first launch of GlazeWM, a default configuration can optionally be generated.\r\n\r\nBelow is a cheat sheet of all available commands and their default keybindings.\r\n\r\n![Infographic](/resources/assets/cheatsheet.png)\r\n\r\n## Config documentation\r\n\r\nThe [default config](https://github.com/glzr-io/glazewm/blob/main/resources/assets/sample-config.yaml) file is generated at `%userprofile%\\.glzr\\glazewm\\config.yaml`.\r\n\r\nTo use a different config file location, you can launch the GlazeWM executable with the CLI argument `--config=\"...\"`, like so:\r\n\r\n```sh\r\n./glazewm.exe start --config=\"C:\\<PATH_TO_CONFIG>\\config.yaml\"\r\n```\r\n\r\nOr pass a value for the `GLAZEWM_CONFIG_PATH` environment variable:\r\n\r\n```sh\r\nsetx GLAZEWM_CONFIG_PATH \"C:\\<PATH_TO_CONFIG>\\config.yaml\"\r\n```\r\n\r\nWith the benefit of using a custom path being that you can choose a different name for the config file, such as `glazewm.yaml`.\r\n\r\n### Config: General\r\n\r\n```yaml\r\ngeneral:\r\n  # Commands to run when the WM has started (e.g. to run a script or launch\r\n  # another application).\r\n  startup_commands: []\r\n\r\n  # Commands to run just before the WM is shutdown.\r\n  shutdown_commands: []\r\n\r\n  # Commands to run after the WM config has reloaded.\r\n  config_reload_commands: []\r\n\r\n  # Whether to automatically focus windows underneath the cursor.\r\n  focus_follows_cursor: false\r\n\r\n  # Whether to switch back and forth between the previously focused\r\n  # workspace when focusing the current workspace.\r\n  toggle_workspace_on_refocus: false\r\n\r\n  cursor_jump:\r\n    # Whether to automatically move the cursor on the specified trigger.\r\n    enabled: true\r\n\r\n    # Trigger for cursor jump:\r\n    # - 'monitor_focus': Jump when focus changes between monitors.\r\n    # - 'window_focus': Jump when focus changes between windows.\r\n    trigger: \"monitor_focus\"\r\n```\r\n\r\n### Config: Keybindings\r\n\r\nThe available keyboard shortcuts can be customized via the `keybindings` option. A keybinding consists of one or more key combinations and one or more commands to run when pressed.\r\n\r\nIt's recommended to use the alt key for keybindings. The Windows key is unfortunately a pain to remap, since the OS reserves certain keybindings (e.g. `lwin+l`).\r\n\r\n```yaml\r\nkeybindings:\r\n  # Command(s) to run.\r\n  - commands: [\"focus --workspace 1\"]\r\n\r\n    # Key combination(s) to trigger the keybinding.\r\n    bindings: [\"alt+1\"]\r\n\r\n  # Multiple commands can be run in a sequence (e.g. to move a window to a\r\n  # workspace + focus workspace).\r\n  - commands: [\"move --workspace 1\", \"focus --workspace 1\"]\r\n    bindings: [\"alt+shift+1\"]\r\n```\r\n\r\n**Full list of keys that can be used for keybindings:**\r\n\r\n<details>\r\n<summary>Keys list</summary>\r\n\r\n| Key                   | Description                                                               |\r\n| --------------------- | ------------------------------------------------------------------------- |\r\n| `a` - `z`             | Alphabetical letter keys                                                  |\r\n| `0` - `9`             | Number keys                                                               |\r\n| `numpad0` - `numpad9` | Numerical keypad keys                                                     |\r\n| `f1` - `f24`          | Function keys                                                             |\r\n| `shift`               | Either left or right SHIFT key                                            |\r\n| `lshift`              | The left SHIFT key                                                        |\r\n| `rshift`              | The right SHIFT key                                                       |\r\n| `control`             | Either left or right CTRL key                                             |\r\n| `lctrl`               | The left CTRL key                                                         |\r\n| `rctrl`               | The right CTRL key                                                        |\r\n| `alt`                 | Either left or right ALT key                                              |\r\n| `lalt`                | The left ALT key                                                          |\r\n| `ralt`                | The right ALT key                                                         |\r\n| `lwin`                | The left ⊞ Windows logo key                                               |\r\n| `rwin`                | The right ⊞ Windows logo key                                              |\r\n| `space`               | The spacebar key                                                          |\r\n| `escape`              | The ESCAPE key                                                            |\r\n| `back`                | The BACKSPACE key                                                         |\r\n| `tab`                 | The TAB key                                                               |\r\n| `enter`               | The ENTER key                                                             |\r\n| `left`                | The ← arrow key                                                           |\r\n| `right`               | The → arrow key                                                           |\r\n| `up`                  | The ↑ arrow key                                                           |\r\n| `down`                | The ↓ arrow key                                                           |\r\n| `num_lock`            | The NUM LOCK key                                                          |\r\n| `scroll_lock`         | The SCROLL LOCK key                                                       |\r\n| `caps_lock`           | The CAPS LOCK key                                                         |\r\n| `page_up`             | The PAGE UP key                                                           |\r\n| `page_down`           | The PAGE DOWN key                                                         |\r\n| `insert`              | The INSERT key                                                            |\r\n| `delete`              | The DELETE key                                                            |\r\n| `end`                 | The END key                                                               |\r\n| `home`                | The HOME key                                                              |\r\n| `print_screen`        | The PRINT SCREEN key                                                      |\r\n| `multiply`            | The `*` key (only on numpad)                                              |\r\n| `add`                 | The `+` key (only on numpad)                                              |\r\n| `subtract`            | The `-` key (only on numpad)                                              |\r\n| `decimal`             | The DEL key (only on numpad)                                              |\r\n| `divide`              | The `/` key (only on numpad)                                              |\r\n| `volume_up`           | The volume up key                                                         |\r\n| `volume_down`         | The volume down key                                                       |\r\n| `volume_mute`         | The volume mute key                                                       |\r\n| `media_next_track`    | The media next track key                                                  |\r\n| `media_prev_track`    | The media prev track key                                                  |\r\n| `media_stop`          | The media stop key                                                        |\r\n| `media_play_pause`    | The media play/pause key                                                  |\r\n| `oem_semicolon`       | The `;`/`:` key on a US standard keyboard (varies by keyboard)            |\r\n| `oem_question`        | The `/`/`?` key on a US standard keyboard (varies by keyboard)            |\r\n| `oem_tilde`           | The `` ` ``/`~` key on a US standard keyboard (varies by keyboard)        |\r\n| `oem_open_brackets`   | The `[`/`{` key on a US standard keyboard (varies by keyboard)            |\r\n| `oem_pipe`            | The `\\`/`\\|` key on a US standard keyboard (varies by keyboard)           |\r\n| `oem_close_brackets`  | The `]`/`}` key on a US standard keyboard (varies by keyboard)            |\r\n| `oem_quotes`          | The `'`/`\"` key on a US standard keyboard (varies by keyboard)            |\r\n| `oem_8`               | The `` ` ``/`¬` key on a UK keyboard (varies by keyboard)                 |\r\n| `oem_102`             | The `\\`/`\\|` key next to left Shift on ISO keyboards (varies by keyboard) |\r\n| `oem_plus`            | The `=`/`+` key on a US standard keyboard (varies by keyboard)            |\r\n| `oem_comma`           | The `,`/`<` key on a US standard keyboard (varies by keyboard)            |\r\n| `oem_minus`           | The `-`/`_` key on a US standard keyboard (varies by keyboard)            |\r\n| `oem_period`          | The `.`/`>` key on a US standard keyboard (varies by keyboard)            |\r\n| `muhenkan`            | The 無変換 (non-convert) key for Japanese keyboard layouts                |\r\n| `henkan`              | The 変換 (convert) key for Japanese keyboard layouts                      |\r\n\r\n</details>\r\n\r\nIf a key is not in the list above, it is likely still supported if you use its character in a keybinding (e.g. `alt+å` for the Norwegian Å character).\r\n\r\n> German and US international keyboards treat the right-side alt key differently. For these keyboard layouts, use `ralt+ctrl` instead of `ralt` to bind the right-side alt key.\r\n\r\n### Config: Gaps\r\n\r\nThe gaps between windows can be changed via the `gaps` property in the config file. Inner and outer gaps are set separately.\r\n\r\n```yaml\r\ngaps:\r\n  # Gap between adjacent windows.\r\n  inner_gap: \"20px\"\r\n\r\n  # Gap between windows and the screen edge.\r\n  outer_gap:\r\n    top: \"20px\"\r\n    right: \"20px\"\r\n    bottom: \"20px\"\r\n    left: \"20px\"\r\n```\r\n\r\n### Config: Workspaces\r\n\r\nWorkspaces need to be predefined via the `workspaces` property in the config file. A workspace is automatically assigned to each monitor on startup.\r\n\r\n```yaml\r\nworkspaces:\r\n  # This is the unique ID for the workspace. It's used in keybinding\r\n  # commands, and is also the label shown in 3rd-party apps (e.g. Zebar) if\r\n  # `display_name` is not provided.\r\n  - name: \"1\"\r\n\r\n    # Optional override for the workspace label used in 3rd-party apps.\r\n    # Does not need to be unique.\r\n    display_name: \"Work\"\r\n\r\n    # Optionally force the workspace on a specific monitor if it exists.\r\n    # 0 is your leftmost screen, 1 is the next one to the right, and so on.\r\n    bind_to_monitor: 0\r\n\r\n    # Optionally prevent workspace from being deactivated when empty.\r\n    keep_alive: false\r\n```\r\n\r\n### Config: Window rules\r\n\r\nCommands can be run when a window is first launched. This is useful for adding window-specific behaviors like always starting a window as fullscreen or assigning to a specific workspace.\r\n\r\nWindows can be targeted by their process, class, and title. Multiple matching criteria can be used together to target a window more precisely.\r\n\r\n```yaml\r\nwindow_rules:\r\n  - commands: [\"move --workspace 1\"]\r\n    match:\r\n      # Move browsers to workspace 1.\r\n      - window_process: { regex: \"msedge|brave|chrome\" }\r\n\r\n  - commands: [\"ignore\"]\r\n    match:\r\n      # Ignores any Zebar windows.\r\n      - window_process: { equals: \"zebar\" }\r\n\r\n      # Ignores picture-in-picture windows for browsers.\r\n      # Note that *both* the title and class must match for the rule to run.\r\n      - window_title: { regex: \"[Pp]icture.in.[Pp]icture\" }\r\n        window_class: { regex: \"Chrome_WidgetWin_1|MozillaDialogClass\" }\r\n```\r\n\r\n### Config: Window effects\r\n\r\nVisual effects can be applied to windows via the `window_effects` option. Currently, colored borders are the only effect available with more to come in the future.\r\n\r\n> Note: Window effects are exclusive to Windows 11.\r\n\r\n```yaml\r\nwindow_effects:\r\n  # Visual effects to apply to the focused window.\r\n  focused_window:\r\n    # Highlight the window with a colored border.\r\n    border:\r\n      enabled: true\r\n      color: \"#0000ff\"\r\n\r\n  # Visual effects to apply to non-focused windows.\r\n  other_windows:\r\n    border:\r\n      enabled: false\r\n      color: \"#d3d3d3\"\r\n```\r\n\r\n### Config: Window behavior\r\n\r\nThe `window_behavior` config option exists to customize the states that a window can be in (`tiling`, `floating`, `minimized`, and `fullscreen`).\r\n\r\n```yaml\r\nwindow_behavior:\r\n  # New windows are created in this state whenever possible.\r\n  # Allowed values: 'tiling', 'floating'.\r\n  initial_state: \"tiling\"\r\n\r\n  # Sets the default options for when a new window is created. This also\r\n  # changes the defaults for when the state change commands, like\r\n  # `set-floating`, are used without any flags.\r\n  state_defaults:\r\n    floating:\r\n      # Whether to center floating windows by default.\r\n      centered: true\r\n\r\n      # Whether to show floating windows as always on top.\r\n      shown_on_top: false\r\n\r\n    fullscreen:\r\n      # Maximize the window if possible. If the window doesn't have a\r\n      # maximize button, then it'll be made fullscreen normally instead.\r\n      maximized: false\r\n```\r\n\r\n### Config: Binding modes\r\n\r\nBinding modes are used to modify keybindings while GlazeWM is running.\r\n\r\nA binding mode can be enabled with `wm-enable-binding-mode --name <NAME>` and disabled with `wm-disable-binding-mode --name <NAME>`.\r\n\r\n```yaml\r\nbinding_modes:\r\n  # When enabled, the focused window can be resized via arrow keys or HJKL.\r\n  - name: \"resize\"\r\n    keybindings:\r\n      - commands: [\"resize --width -2%\"]\r\n        bindings: [\"h\", \"left\"]\r\n      - commands: [\"resize --width +2%\"]\r\n        bindings: [\"l\", \"right\"]\r\n      - commands: [\"resize --height +2%\"]\r\n        bindings: [\"k\", \"up\"]\r\n      - commands: [\"resize --height -2%\"]\r\n        bindings: [\"j\", \"down\"]\r\n      # Press enter/escape to return to default keybindings.\r\n      - commands: [\"wm-disable-binding-mode --name resize\"]\r\n        bindings: [\"escape\", \"enter\"]\r\n```\r\n\r\n## FAQ\r\n\r\n**Q: How do I run GlazeWM on startup?**\r\n\r\nRight-click the GlazeWM icon in the system tray and select \"Run on system startup\".\r\n\r\n**Q: How can I create `<insert layout>`?**\r\n\r\nYou can create custom layouts by changing the tiling direction with `alt+v`. This changes where the next window is placed _in relation to the current window_. If the current window's direction is horizontal, the new window will be placed to the right of it. If it is vertical, it will be placed below it. This also applies when moving windows; the tiling direction of the stationary window will affect where the moved window will be placed.\r\n\r\nCommunity-made scripts like [Dutch-Raptor/GAT-GWM](https://github.com/Dutch-Raptor/GAT-GWM) and [burgr033/GlazeWM-autotiling-python](https://github.com/burgr033/GlazeWM-autotiling-python) can be used to automatically change the tiling direction. Native support for automatic layouts isn't _currently_ supported.\r\n\r\n**Q: How do I create a rule for `<insert application>`?**\r\n\r\nTo match a specific application, you need a command to execute and either the window's process name, title, or class name. For example, if you use Flow-Launcher and want to make the settings window float, you can do the following:\r\n\r\n```yaml\r\nwindow_rules:\r\n  - commands: [\"set-floating\"]\r\n    match:\r\n      - window_process: { equals: \"Flow.Launcher\" }\r\n        window_title: { equals: \"Settings\" }\r\n```\r\n\r\nPrograms like Winlister or AutoHotkey's Window Spy can be useful for getting info about a window.\r\n\r\n**Q: How can I ignore GlazeWM's keybindings when `<insert application>` is focused?**\r\n\r\nThis isn't currently supported, however, the keybinding `alt+shift+p` in the default config is used to disable all other keybindings until `alt+shift+p` is pressed again.\r\n\r\n[discord-badge]: https://img.shields.io/discord/1041662798196908052.svg?logo=discord&colorB=7289DA\r\n[discord-link]: https://discord.gg/ud6z3qjRvM\r\n[downloads-badge]: https://img.shields.io/github/downloads/glzr-io/glazewm/total?logo=github&logoColor=white\r\n[downloads-link]: https://github.com/glzr-io/glazewm/releases\r\n[issues-badge]: https://img.shields.io/badge/good_first_issues-7057ff\r\n[issues-link]: https://github.com/orgs/glzr-io/projects/4/views/1?sliceBy%5Bvalue%5D=good+first+issue\r\n[demo-video]: resources/assets/demo.webp\r\n"
  },
  {
    "path": "README_zh.md",
    "content": "<div align=\"center\">\r\n\r\n> V3 终于发布了 - 查看更新日志 [这里](https://github.com/glzr-io/GlazeWM/releases) 🔥\r\n\r\n  <br>\r\n  <img src=\"./resources/assets/logo.svg\" width=\"230\" alt=\"GlazeWM logo\" />\r\n  <br>\r\n\r\n# GlazeWM\r\n\r\n**一个受 i3wm 启发的 Windows 平铺窗口管理器。**\r\n\r\n[![Discord invite][discord-badge]][discord-link]\r\n[![Downloads][downloads-badge]][downloads-link]\r\n[![Good first issues][issues-badge]][issues-link]\r\n\r\nGlazeWM 让您可以通过键盘驱动的命令轻松组织窗口并即时调整其布局。\r\n\r\n[安装](#安装) •\r\n[默认快捷键](#默认快捷键) •\r\n[配置文档](#配置文档) •\r\n[常见问题](#常见问题) •\r\n[贡献 ↗](https://github.com/glzr-io/glazewm/blob/main/CONTRIBUTING.md)\r\n\r\n![Demo video][demo-video]\r\n\r\n</div>\r\n\r\n### 🌟 主要特性\r\n\r\n- 简单的 YAML 配置\r\n- 多显示器支持\r\n- 针对特定窗口的可自定义规则\r\n- 简单的一键安装\r\n- 与 [Zebar](https://github.com/glzr-io/zebar) 状态栏集成\r\n\r\n## 安装\r\n\r\n**GlazeWM 的最新版本可通过 [releases](https://github.com/glzr-io/GlazeWM/releases) 下载。** 在安装过程中可以通过复选框选择性安装 Zebar。\r\n\r\nGlazeWM 也可以通过多个包管理器获得：\r\n\r\n**Winget**\r\n\r\n```sh\r\nwinget install GlazeWM\r\n```\r\n\r\n**Chocolatey**\r\n\r\n```sh\r\nchoco install glazewm\r\n```\r\n\r\n**Scoop**\r\n\r\n```sh\r\nscoop bucket add extras\r\nscoop install extras/glazewm\r\n```\r\n\r\n## 贡献\r\n\r\n帮助修复困扰您的问题，或添加您一直想要的功能！我们非常欢迎贡献。\r\n\r\n本地开发和指南可在 [贡献指南](https://github.com/glzr-io/glazewm/blob/main/CONTRIBUTING.md) 中找到。\r\n\r\n## 默认快捷键\r\n\r\n在 GlazeWM 首次启动时，可以选择性地生成默认配置。\r\n\r\n以下是所有可用命令及其默认快捷键的速查表。\r\n\r\n![Infographic](/resources/assets/cheatsheet_cn_ZH.png)\r\n\r\n## 配置文档\r\n\r\n[默认配置](https://github.com/glzr-io/glazewm/blob/main/resources/assets/sample-config.yaml) 文件生成在 `%userprofile%\\.glzr\\glazewm\\config.yaml`。\r\n\r\n要使用不同的配置文件位置，您可以使用 CLI 参数 `--config=\"...\"` 启动 GlazeWM 可执行文件，如下所示：\r\n\r\n```sh\r\n./glazewm.exe start --config=\"C:\\<配置文件路径>\\config.yaml\"\r\n```\r\n\r\n或者为 `GLAZEWM_CONFIG_PATH` 环境变量传递一个值：\r\n\r\n```sh\r\nsetx GLAZEWM_CONFIG_PATH \"C:\\<配置文件路径>\\config.yaml\"\r\n```\r\n\r\n使用自定义路径的好处是您可以为配置文件选择不同的名称，例如 `glazewm.yaml`。\r\n\r\n### 配置：常规\r\n\r\n```yaml\r\ngeneral:\r\n  # WM 启动时运行的命令（例如运行脚本或启动另一个应用程序）。\r\n  startup_commands: []\r\n\r\n  # WM 关闭前运行的命令。\r\n  shutdown_commands: []\r\n\r\n  # WM 配置重新加载后运行的命令。\r\n  config_reload_commands: []\r\n\r\n  # 是否自动聚焦光标下方的窗口。\r\n  focus_follows_cursor: false\r\n\r\n  # 当聚焦当前工作区时，是否在先前聚焦的工作区之间来回切换。\r\n  toggle_workspace_on_refocus: false\r\n\r\n  cursor_jump:\r\n    # 是否在指定触发器上自动移动光标。\r\n    enabled: true\r\n\r\n    # 光标跳转的触发器：\r\n    # - 'monitor_focus': 当焦点在显示器之间切换时跳转。\r\n    # - 'window_focus': 当焦点在窗口之间切换时跳转。\r\n    trigger: \"monitor_focus\"\r\n```\r\n\r\n### 配置：快捷键\r\n\r\n可用的键盘快捷键可以通过 `keybindings` 选项自定义。快捷键由一个或多个按键组合和按下时运行的一个或多个命令组成。\r\n\r\n建议使用 alt 键作为快捷键。不幸的是，Windows 键很难重新映射，因为操作系统保留了某些快捷键（例如 `lwin+l`）。\r\n\r\n```yaml\r\nkeybindings:\r\n  # 要运行的命令。\r\n  - commands: [\"focus --workspace 1\"]\r\n\r\n    # 触发快捷键的按键组合。\r\n    bindings: [\"alt+1\"]\r\n\r\n  # 可以按顺序运行多个命令（例如将窗口移动到工作区 + 聚焦工作区）。\r\n  - commands: [\"move --workspace 1\", \"focus --workspace 1\"]\r\n    bindings: [\"alt+shift+1\"]\r\n```\r\n\r\n**可用于快捷键的完整按键列表：**\r\n\r\n<details>\r\n<summary>按键列表</summary>\r\n\r\n| 按键                  | 描述                                                           |\r\n| --------------------- | -------------------------------------------------------------- |\r\n| `a` - `z`             | 字母键                                                         |\r\n| `0` - `9`             | 数字键                                                         |\r\n| `numpad0` - `numpad9` | 数字小键盘键                                                   |\r\n| `f1` - `f24`          | 功能键                                                         |\r\n| `shift`               | 左或右 SHIFT 键                                                |\r\n| `lshift`              | 左 SHIFT 键                                                    |\r\n| `rshift`              | 右 SHIFT 键                                                    |\r\n| `control`             | 左或右 CTRL 键                                                 |\r\n| `lctrl`               | 左 CTRL 键                                                     |\r\n| `rctrl`               | 右 CTRL 键                                                     |\r\n| `alt`                 | 左或右 ALT 键                                                  |\r\n| `lalt`                | 左 ALT 键                                                      |\r\n| `ralt`                | 右 ALT 键                                                      |\r\n| `lwin`                | 左 ⊞ Windows 徽标键                                            |\r\n| `rwin`                | 右 ⊞ Windows 徽标键                                            |\r\n| `space`               | 空格键                                                         |\r\n| `escape`              | ESCAPE 键                                                      |\r\n| `back`                | BACKSPACE 键                                                   |\r\n| `tab`                 | TAB 键                                                         |\r\n| `enter`               | ENTER 键                                                       |\r\n| `left`                | ← 方向键                                                       |\r\n| `right`               | → 方向键                                                       |\r\n| `up`                  | ↑ 方向键                                                       |\r\n| `down`                | ↓ 方向键                                                       |\r\n| `num_lock`            | NUM LOCK 键                                                    |\r\n| `scroll_lock`         | SCROLL LOCK 键                                                 |\r\n| `caps_lock`           | CAPS LOCK 键                                                   |\r\n| `page_up`             | PAGE UP 键                                                     |\r\n| `page_down`           | PAGE DOWN 键                                                   |\r\n| `insert`              | INSERT 键                                                      |\r\n| `delete`              | DELETE 键                                                      |\r\n| `end`                 | END 键                                                         |\r\n| `home`                | HOME 键                                                        |\r\n| `print_screen`        | PRINT SCREEN 键                                                |\r\n| `multiply`            | `*` 键（仅限数字小键盘）                                       |\r\n| `add`                 | `+` 键（仅限数字小键盘）                                       |\r\n| `subtract`            | `-` 键（仅限数字小键盘）                                       |\r\n| `decimal`             | DEL 键（仅限数字小键盘）                                       |\r\n| `divide`              | `/` 键（仅限数字小键盘）                                       |\r\n| `volume_up`           | 音量增加键                                                     |\r\n| `volume_down`         | 音量减少键                                                     |\r\n| `volume_mute`         | 静音键                                                         |\r\n| `media_next_track`    | 媒体下一曲键                                                   |\r\n| `media_prev_track`    | 媒体上一曲键                                                   |\r\n| `media_stop`          | 媒体停止键                                                     |\r\n| `media_play_pause`    | 媒体播放/暂停键                                                |\r\n| `oem_semicolon`       | 美式标准键盘上的 `;`/`:` 键（因键盘而异）                      |\r\n| `oem_question`        | 美式标准键盘上的 `/`/`?` 键（因键盘而异）                      |\r\n| `oem_tilde`           | 美式标准键盘上的 `` ` ``/`~` 键（因键盘而异）                  |\r\n| `oem_open_brackets`   | 美式标准键盘上的 `[`/`{` 键（因键盘而异）                      |\r\n| `oem_pipe`            | 美式标准键盘上的 `\\`/`\\|` 键（因键盘而异）                     |\r\n| `oem_close_brackets`  | 美式标准键盘上的 `]`/`}` 键（因键盘而异）                      |\r\n| `oem_quotes`          | 美式标准键盘上的 `'`/`\"` 键（因键盘而异）                      |\r\n| `oem_plus`            | 美式标准键盘上的 `=`/`+` 键（因键盘而异）                      |\r\n| `oem_comma`           | 美式标准键盘上的 `,`/`<` 键（因键盘而异）                      |\r\n| `oem_minus`           | 美式标准键盘上的 `-`/`_` 键（因键盘而异）                      |\r\n| `oem_period`          | 美式标准键盘上的 `.`/`>` 键（因键盘而异）                      |\r\n\r\n</details>\r\n\r\n如果某个按键不在上述列表中，如果您在快捷键中使用其字符，它很可能仍然受支持（例如挪威语 Å 字符的 `alt+å`）。\r\n\r\n> 德语和美式国际键盘对右侧 alt 键的处理不同。对于这些键盘布局，请使用 `ralt+ctrl` 而不是 `ralt` 来绑定右侧 alt 键。\r\n\r\n### 配置：间隙\r\n\r\n窗口之间的间隙可以通过配置文件中的 `gaps` 属性更改。内部和外部间隙分别设置。\r\n\r\n```yaml\r\ngaps:\r\n  # 相邻窗口之间的间隙。\r\n  inner_gap: \"20px\"\r\n\r\n  # 窗口与屏幕边缘之间的间隙。\r\n  outer_gap:\r\n    top: \"20px\"\r\n    right: \"20px\"\r\n    bottom: \"20px\"\r\n    left: \"20px\"\r\n```\r\n\r\n### 配置：工作区\r\n\r\n工作区需要通过配置文件中的 `workspaces` 属性预定义。启动时，每个显示器会自动分配一个工作区。\r\n\r\n```yaml\r\nworkspaces:\r\n  # 这是工作区的唯一 ID。它用于快捷键命令，如果未提供 `display_name`，\r\n  # 它也是第三方应用程序（例如 Zebar）中显示的标签。\r\n  - name: \"1\"\r\n\r\n    # 第三方应用程序中使用的工作区标签的可选覆盖。\r\n    # 不需要是唯一的。\r\n    display_name: \"工作\"\r\n\r\n    # 如果存在，可选择强制工作区在特定显示器上。\r\n    # 0 是您最左边的屏幕，1 是右边的下一个，依此类推。\r\n    bind_to_monitor: 0\r\n\r\n    # 可选择防止工作区在空时被停用。\r\n    keep_alive: false\r\n```\r\n\r\n### 配置：窗口规则\r\n\r\n可以在窗口首次启动时运行命令。这对于添加特定于窗口的行为很有用，比如始终以全屏模式启动窗口或分配到特定工作区。\r\n\r\n窗口可以通过其进程、类和标题进行定位。可以一起使用多个匹配条件来更精确地定位窗口。\r\n\r\n```yaml\r\nwindow_rules:\r\n  - commands: [\"move --workspace 1\"]\r\n    match:\r\n      # 将浏览器移动到工作区 1。\r\n      - window_process: { regex: \"msedge|brave|chrome\" }\r\n\r\n  - commands: [\"ignore\"]\r\n    match:\r\n      # 忽略任何 Zebar 窗口。\r\n      - window_process: { equals: \"zebar\" }\r\n\r\n      # 忽略浏览器的画中画窗口。\r\n      # 注意标题和类都必须匹配才能运行规则。\r\n      - window_title: { regex: \"[Pp]icture.in.[Pp]icture\" }\r\n        window_class: { regex: \"Chrome_WidgetWin_1|MozillaDialogClass\" }\r\n```\r\n\r\n### 配置：窗口效果\r\n\r\n可以通过 `window_effects` 选项对窗口应用视觉效果。目前，彩色边框是唯一可用的效果，未来会有更多效果。\r\n\r\n> 注意：窗口效果仅适用于 Windows 11。\r\n\r\n```yaml\r\nwindow_effects:\r\n  # 应用于聚焦窗口的视觉效果。\r\n  focused_window:\r\n    # 用彩色边框突出显示窗口。\r\n    border:\r\n      enabled: true\r\n      color: \"#0000ff\"\r\n\r\n  # 应用于非聚焦窗口的视觉效果。\r\n  other_windows:\r\n    border:\r\n      enabled: false\r\n      color: \"#d3d3d3\"\r\n```\r\n\r\n### 配置：窗口行为\r\n\r\n`window_behavior` 配置选项用于自定义窗口可以处于的状态（`tiling`、`floating`、`minimized` 和 `fullscreen`）。\r\n\r\n```yaml\r\nwindow_behavior:\r\n  # 新窗口在可能的情况下以此状态创建。\r\n  # 允许的值：'tiling'、'floating'。\r\n  initial_state: \"tiling\"\r\n\r\n  # 设置创建新窗口时的默认选项。这也会更改状态更改命令\r\n  # （如 `set-floating`）在不使用任何标志时的默认值。\r\n  state_defaults:\r\n    floating:\r\n      # 是否默认居中浮动窗口。\r\n      centered: true\r\n\r\n      # 是否将浮动窗口显示为始终在顶部。\r\n      shown_on_top: false\r\n\r\n    fullscreen:\r\n      # 如果可能，最大化窗口。如果窗口没有最大化按钮，\r\n      # 则会正常全屏显示。\r\n      maximized: false\r\n```\r\n\r\n### 配置：绑定模式\r\n\r\n绑定模式用于在 GlazeWM 运行时修改快捷键。\r\n\r\n可以使用 `wm-enable-binding-mode --name <名称>` 启用绑定模式，使用 `wm-disable-binding-mode --name <名称>` 禁用。\r\n\r\n```yaml\r\nbinding_modes:\r\n  # 启用时，可以通过方向键或 HJKL 调整聚焦窗口的大小。\r\n  - name: \"resize\"\r\n    keybindings:\r\n      - commands: [\"resize --width -2%\"]\r\n        bindings: [\"h\", \"left\"]\r\n      - commands: [\"resize --width +2%\"]\r\n        bindings: [\"l\", \"right\"]\r\n      - commands: [\"resize --height +2%\"]\r\n        bindings: [\"k\", \"up\"]\r\n      - commands: [\"resize --height -2%\"]\r\n        bindings: [\"j\", \"down\"]\r\n      # 按 enter/escape 返回默认快捷键。\r\n      - commands: [\"wm-disable-binding-mode --name resize\"]\r\n        bindings: [\"escape\", \"enter\"]\r\n```\r\n\r\n## 常见问题\r\n\r\n**问：如何在启动时运行 GlazeWM？**\r\n\r\n通过右键单击 GlazeWM 可执行文件 -> `创建快捷方式` 为可执行文件创建快捷方式。将快捷方式放在启动文件夹中，您可以通过在文件资源管理器的顶部栏中输入 `shell:startup` 来访问该文件夹。\r\n\r\n**问：如何创建 `<插入布局>`？**\r\n\r\n您可以通过使用 `alt+v` 更改平铺方向来创建自定义布局。这会改变下一个窗口相对于当前窗口的放置位置。如果当前窗口的方向是水平的，新窗口将放置在其右侧。如果是垂直的，将放置在其下方。这也适用于移动窗口；固定窗口的平铺方向将影响移动窗口的放置位置。\r\n\r\n社区制作的脚本如 [Dutch-Raptor/GAT-GWM](https://github.com/Dutch-Raptor/GAT-GWM) 和 [burgr033/GlazeWM-autotiling-python](https://github.com/burgr033/GlazeWM-autotiling-python) 可用于自动更改平铺方向。目前不支持自动布局的原生支持。\r\n\r\n**问：如何为 `<插入应用程序>` 创建规则？**\r\n\r\n要匹配特定应用程序，您需要一个要执行的命令以及窗口的进程名称、标题或类名称。例如，如果您使用 Flow-Launcher 并希望设置窗口浮动，您可以执行以下操作：\r\n\r\n```yaml\r\nwindow_rules:\r\n  - commands: [\"set-floating\"]\r\n    match:\r\n      - window_process: { equals: \"Flow.Launcher\" }\r\n        window_title: { equals: \"Settings\" }\r\n```\r\n\r\n像 Winlister 或 AutoHotkey 的 Window Spy 这样的程序对于获取窗口信息很有用。\r\n\r\n**问：当 `<插入应用程序>` 聚焦时，如何忽略 GlazeWM 的快捷键？**\r\n\r\n目前不支持此功能，但是，默认配置中的快捷键 `alt+shift+p` 用于禁用所有其他快捷键，直到再次按下 `alt+shift+p`。\r\n\r\n[discord-badge]: https://img.shields.io/discord/1041662798196908052.svg?logo=discord&colorB=7289DA\r\n[discord-link]: https://discord.gg/ud6z3qjRvM\r\n[downloads-badge]: https://img.shields.io/github/downloads/glzr-io/glazewm/total?logo=github&logoColor=white\r\n[downloads-link]: https://github.com/glzr-io/glazewm/releases\r\n[issues-badge]: https://img.shields.io/badge/good_first_issues-7057ff\r\n[issues-link]: https://github.com/orgs/glzr-io/projects/4/views/1?sliceBy%5Bvalue%5D=good+first+issue\r\n[demo-video]: resources/assets/demo.webp\r\n"
  },
  {
    "path": "clippy.toml",
    "content": "# TODO: Would ideally move this to `Cargo.toml`, but it's not supported yet.\r\n# Ref: https://github.com/rust-lang/cargo/issues/12917#issuecomment-1795069197\r\ndoc-valid-idents = [\"AppKit\", \"DisplayPort\", \"..\"]\r\n"
  },
  {
    "path": "packages/wm/Cargo.toml",
    "content": "[package]\r\nname = \"wm\"\r\nversion = \"0.0.0\"\r\ndescription = \"GlazeWM is a tiling window manager for Windows inspired by i3 and Polybar.\"\r\nrepository = \"https://github.com/glzr-io/glazewm\"\r\nlicense = \"GPL-3\"\r\nedition = \"2021\"\r\ndefault-run = \"glazewm\"\r\n\r\n[[bin]]\r\nname = \"glazewm\"\r\npath = \"src/main.rs\"\r\n\r\n[features]\r\nui_access = []\r\n\r\n[build-dependencies]\r\ntauri-winres = { workspace = true }\r\n\r\n[dependencies]\r\nanyhow = { workspace = true }\r\nauto-launch = \"0.5\"\r\nambassador = \"0.4\"\r\nclap = { workspace = true }\r\nenum-as-inner = \"0.6\"\r\nfutures-util = { workspace = true }\r\nhome = { workspace = true }\r\nimage = \"0.25\"\r\nserde = { workspace = true }\r\nserde_json = { workspace = true }\r\nserde_yaml = \"0.9\"\r\nshell-util = \"0.0\"\r\ntokio = { workspace = true }\r\ntokio-tungstenite = { workspace = true }\r\ntracing = { workspace = true }\r\ntracing-appender = \"0.2\"\r\ntracing-subscriber = { workspace = true }\r\ntray-icon = \"0.21\"\r\nuuid = { workspace = true }\r\nwm-cli = { path = \"../wm-cli\" }\r\nwm-common = { path = \"../wm-common\" }\r\nwm-ipc-client = { path = \"../wm-ipc-client\" }\r\nwm-macros = { workspace = true }\r\nwm-platform = { path = \"../wm-platform\" }\r\n"
  },
  {
    "path": "packages/wm/build.rs",
    "content": "use tauri_winres::VersionInfo;\r\n\r\nfn main() {\r\n  println!(\"cargo:rerun-if-env-changed=VERSION_NUMBER\");\r\n  let mut res = tauri_winres::WindowsResource::new();\r\n\r\n  // When the `ui_access` feature is enabled, the `uiAccess` attribute is\r\n  // set to `true`. UIAccess is disabled by default because it requires the\r\n  // application to be signed and installed in a secure location.\r\n  let ui_access = {\r\n    #[cfg(feature = \"ui_access\")]\r\n    {\r\n      \"true\"\r\n    }\r\n    #[cfg(not(feature = \"ui_access\"))]\r\n    {\r\n      \"false\"\r\n    }\r\n  };\r\n\r\n  // Conditionally enable UIAccess, which grants privilege to set the\r\n  // foreground window and to set the position of elevated windows.\r\n  //\r\n  // Ref: https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-10/security/threat-protection/security-policy-settings/user-account-control-only-elevate-uiaccess-applications-that-are-installed-in-secure-locations\r\n  //\r\n  // Additionally, declare support for per-monitor DPI awareness.\r\n  let manifest_str = format!(\r\n    r#\"\r\n<assembly\r\n  xmlns=\"urn:schemas-microsoft-com:asm.v1\"\r\n  manifestVersion=\"1.0\"\r\n  xmlns:asmv3=\"urn:schemas-microsoft-com:asm.v3\"\r\n>\r\n  <asmv3:trustInfo>\r\n    <security>\r\n      <requestedPrivileges>\r\n        <requestedExecutionLevel level=\"asInvoker\" uiAccess=\"{ui_access}\" />\r\n      </requestedPrivileges>\r\n    </security>\r\n  </asmv3:trustInfo>\r\n\r\n  <asmv3:application>\r\n    <windowsSettings\r\n      xmlns:ws2005=\"http://schemas.microsoft.com/SMI/2005/WindowsSettings\"\r\n      xmlns:ws2016=\"http://schemas.microsoft.com/SMI/2016/WindowsSettings\"\r\n    >\r\n      <ws2005:dpiAware>true</ws2005:dpiAware>\r\n      <ws2016:dpiAwareness>PerMonitorV2</ws2016:dpiAwareness>\r\n    </windowsSettings>\r\n  </asmv3:application>\r\n</assembly>\r\n\"#\r\n  );\r\n\r\n  res.set_manifest(&manifest_str);\r\n  res.set_icon(\"../../resources/assets/icon.ico\");\r\n\r\n  // Set language to English (US).\r\n  res.set_language(0x0409);\r\n\r\n  res.set(\"OriginalFilename\", \"glazewm.exe\");\r\n  res.set(\"ProductName\", \"GlazeWM\");\r\n  res.set(\"FileDescription\", \"GlazeWM\");\r\n\r\n  let version_parts = env!(\"VERSION_NUMBER\")\r\n    .split('.')\r\n    .take(3)\r\n    .map(|part| part.parse().unwrap_or(0))\r\n    .collect::<Vec<u16>>();\r\n\r\n  let [major, minor, patch] =\r\n    <[u16; 3]>::try_from(version_parts).unwrap_or([0, 0, 0]);\r\n\r\n  let version_str = format!(\"{major}.{minor}.{patch}.0\");\r\n  res.set(\"FileVersion\", &version_str);\r\n  res.set(\"ProductVersion\", &version_str);\r\n\r\n  let version_u64 = (u64::from(major) << 48)\r\n    | (u64::from(minor) << 32)\r\n    | (u64::from(patch) << 16);\r\n\r\n  res.set_version_info(VersionInfo::FILEVERSION, version_u64);\r\n  res.set_version_info(VersionInfo::PRODUCTVERSION, version_u64);\r\n\r\n  res.compile().unwrap();\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/commands/container/attach_container.rs",
    "content": "use anyhow::bail;\r\n\r\nuse super::resize_tiling_container;\r\nuse crate::{\r\n  models::Container,\r\n  traits::{CommonGetters, TilingSizeGetters},\r\n};\r\n\r\n/// Inserts a child container at the specified index.\r\n///\r\n/// The inserted child will be resized to fit the available space.\r\npub fn attach_container(\r\n  child: &Container,\r\n  target_parent: &Container,\r\n  target_index: Option<usize>,\r\n) -> anyhow::Result<()> {\r\n  if !child.is_detached() {\r\n    bail!(\"Cannot attach an already attached container.\");\r\n  }\r\n\r\n  if let Some(target_index) = target_index {\r\n    // Ensure target index is within the bounds of the parent's children.\r\n    let target_index = target_index.clamp(0, target_parent.child_count());\r\n\r\n    // Insert the child at the specified index.\r\n    target_parent\r\n      .borrow_children_mut()\r\n      .insert(target_index, child.clone());\r\n  } else {\r\n    target_parent.borrow_children_mut().push_back(child.clone());\r\n  }\r\n\r\n  target_parent\r\n    .borrow_child_focus_order_mut()\r\n    .push_back(child.id());\r\n\r\n  *child.borrow_parent_mut() = Some(target_parent.clone());\r\n\r\n  // Resize the child and its siblings if it is a tiling container.\r\n  if let Ok(child) = child.as_tiling_container() {\r\n    let tiling_siblings = child.tiling_siblings().collect::<Vec<_>>();\r\n\r\n    if tiling_siblings.is_empty() {\r\n      child.set_tiling_size(1.0);\r\n      return Ok(());\r\n    }\r\n\r\n    // Set initial tiling size to 0, and then size up the container\r\n    // to the target size.\r\n    #[allow(clippy::cast_precision_loss)]\r\n    let target_size = 1.0 / (tiling_siblings.len() + 1) as f32;\r\n    child.set_tiling_size(0.0);\r\n    resize_tiling_container(&child, target_size);\r\n  }\r\n\r\n  Ok(())\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/commands/container/detach_container.rs",
    "content": "use anyhow::Context;\r\n\r\nuse super::flatten_split_container;\r\nuse crate::{\r\n  models::Container,\r\n  traits::{CommonGetters, TilingSizeGetters, MIN_TILING_SIZE},\r\n};\r\n\r\n/// Removes a container from the tree.\r\n///\r\n/// If the container is a tiling container, the siblings will be resized to\r\n/// fill the freed up space. Will flatten empty parent split containers.\r\n#[allow(clippy::needless_pass_by_value)]\r\npub fn detach_container(child_to_remove: Container) -> anyhow::Result<()> {\r\n  // Flatten the parent split container if it'll be empty after removing\r\n  // the child.\r\n  if let Some(split_parent) = child_to_remove\r\n    .parent()\r\n    .and_then(|parent| parent.as_split().cloned())\r\n  {\r\n    if split_parent.child_count() == 1 {\r\n      flatten_split_container(split_parent)?;\r\n    }\r\n  }\r\n\r\n  let parent = child_to_remove.parent().context(\"No parent.\")?;\r\n\r\n  parent\r\n    .borrow_children_mut()\r\n    .retain(|c| c.id() != child_to_remove.id());\r\n\r\n  parent\r\n    .borrow_child_focus_order_mut()\r\n    .retain(|id| *id != child_to_remove.id());\r\n\r\n  *child_to_remove.borrow_parent_mut() = None;\r\n\r\n  // Resize the siblings if it is a tiling container.\r\n  if let Ok(child_to_remove) = child_to_remove.as_tiling_container() {\r\n    let tiling_siblings = parent.tiling_children().collect::<Vec<_>>();\r\n\r\n    // TODO: Share logic with `resize_tiling_container`.\r\n    let available_size =\r\n      tiling_siblings.iter().fold(0.0, |sum, container| {\r\n        sum + container.tiling_size() - MIN_TILING_SIZE\r\n      });\r\n\r\n    // Adjust size of the siblings based on the freed up space.\r\n    for sibling in &tiling_siblings {\r\n      let resize_factor =\r\n        (sibling.tiling_size() - MIN_TILING_SIZE) / available_size;\r\n\r\n      let size_delta = resize_factor * child_to_remove.tiling_size();\r\n      sibling.set_tiling_size(sibling.tiling_size() + size_delta);\r\n    }\r\n  }\r\n\r\n  Ok(())\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/commands/container/flatten_child_split_containers.rs",
    "content": "use super::flatten_split_container;\r\nuse crate::{\r\n  models::Container,\r\n  traits::{CommonGetters, TilingDirectionGetters},\r\n};\r\n\r\n/// Flattens any redundant split containers at the top-level of the given\r\n/// parent container.\r\n///\r\n/// For example:\r\n/// ```ignore,compile_fail\r\n/// H[1 H[V[2, 3]]] -> H[1, 2, 3]\r\n/// H[1 H[2, 3]] -> H[1, 2, 3]\r\n/// H[V[1]] -> V[1]\r\n/// ```\r\npub fn flatten_child_split_containers(\r\n  parent: &Container,\r\n) -> anyhow::Result<()> {\r\n  if let Ok(parent) = parent.as_direction_container() {\r\n    // Get children that are either tiling windows or split containers.\r\n    let tiling_children = parent\r\n      .children()\r\n      .into_iter()\r\n      .filter(|child| child.is_tiling_window() || child.is_split())\r\n      .collect::<Vec<_>>();\r\n\r\n    if tiling_children.len() == 1 {\r\n      // Handle case where the parent is a split container and has a\r\n      // single split container child.\r\n      if let Some(split_child) = tiling_children[0].as_split() {\r\n        flatten_split_container(split_child.clone())?;\r\n        parent.set_tiling_direction(parent.tiling_direction().inverse());\r\n      }\r\n    } else {\r\n      let split_children = tiling_children\r\n        .into_iter()\r\n        .filter_map(|child| child.as_split().cloned())\r\n        .collect::<Vec<_>>();\r\n\r\n      for split_child in split_children.iter().filter(|split_child| {\r\n        split_child.tiling_direction() == parent.tiling_direction()\r\n      }) {\r\n        // Additionally flatten redundant top-level split containers in\r\n        // the child.\r\n        if split_child.child_count() == 1 {\r\n          if let Some(split_grandchild) =\r\n            split_child.children()[0].as_split()\r\n          {\r\n            flatten_split_container(split_grandchild.clone())?;\r\n          }\r\n        }\r\n\r\n        flatten_split_container(split_child.clone())?;\r\n      }\r\n    }\r\n  }\r\n\r\n  Ok(())\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/commands/container/flatten_split_container.rs",
    "content": "use std::collections::VecDeque;\r\n\r\nuse anyhow::Context;\r\n\r\nuse crate::{\r\n  models::SplitContainer,\r\n  traits::{CommonGetters, TilingSizeGetters},\r\n};\r\n\r\n/// Removes a split container from the tree and moves its children\r\n/// into the parent container.\r\n///\r\n/// The children will be resized to fit the size of the split container.\r\n#[allow(clippy::needless_pass_by_value)]\r\npub fn flatten_split_container(\r\n  split_container: SplitContainer,\r\n) -> anyhow::Result<()> {\r\n  let parent = split_container.parent().context(\"No parent.\")?;\r\n\r\n  let updated_children =\r\n    split_container.children().into_iter().inspect(|child| {\r\n      *child.borrow_parent_mut() = Some(parent.clone());\r\n\r\n      // Resize tiling children to fit the size of the split container.\r\n      if let Ok(tiling_child) = child.as_tiling_container() {\r\n        tiling_child.set_tiling_size(\r\n          split_container.tiling_size() * tiling_child.tiling_size(),\r\n        );\r\n      }\r\n    });\r\n\r\n  let index = split_container.index();\r\n  let focus_index = split_container.focus_index();\r\n\r\n  // Insert child at its original index in the parent.\r\n  for (child_index, child) in updated_children.enumerate() {\r\n    parent\r\n      .borrow_children_mut()\r\n      .insert(index + child_index, child);\r\n  }\r\n\r\n  // Insert child at its original focus index in the parent.\r\n  for (child_focus_index, child_id) in split_container\r\n    .borrow_child_focus_order()\r\n    .iter()\r\n    .enumerate()\r\n  {\r\n    parent\r\n      .borrow_child_focus_order_mut()\r\n      .insert(focus_index + child_focus_index, *child_id);\r\n  }\r\n\r\n  // Remove the split container from the tree.\r\n  parent\r\n    .borrow_children_mut()\r\n    .retain(|c| c.id() != split_container.id());\r\n\r\n  parent\r\n    .borrow_child_focus_order_mut()\r\n    .retain(|id| *id != split_container.id());\r\n\r\n  *split_container.borrow_parent_mut() = None;\r\n  *split_container.borrow_children_mut() = VecDeque::new();\r\n\r\n  Ok(())\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/commands/container/focus_container_by_id.rs",
    "content": "use anyhow::Context;\r\nuse uuid::Uuid;\r\n\r\nuse super::set_focused_descendant;\r\nuse crate::wm_state::WmState;\r\n\r\npub fn focus_container_by_id(\r\n  container_id: &Uuid,\r\n  state: &mut WmState,\r\n) -> anyhow::Result<()> {\r\n  let focus_target = state\r\n    .container_by_id(*container_id)\r\n    .context(\"No container with given id\")?;\r\n\r\n  // Set focus to the target container.\r\n  set_focused_descendant(&focus_target, None);\r\n  state.pending_sync.queue_focus_change().queue_cursor_jump();\r\n\r\n  Ok(())\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/commands/container/focus_in_direction.rs",
    "content": "use anyhow::Context;\r\nuse wm_common::{TilingDirection, WindowState};\r\nuse wm_platform::Direction;\r\n\r\nuse super::set_focused_descendant;\r\nuse crate::{\r\n  models::{Container, TilingContainer},\r\n  traits::{CommonGetters, TilingDirectionGetters, WindowGetters},\r\n  wm_state::WmState,\r\n};\r\n\r\npub fn focus_in_direction(\r\n  origin_container: &Container,\r\n  direction: &Direction,\r\n  state: &mut WmState,\r\n) -> anyhow::Result<()> {\r\n  let focus_target = match origin_container {\r\n    Container::TilingWindow(_) => {\r\n      // If a suitable focus target isn't found in the current workspace,\r\n      // attempt to find a workspace in the given direction.\r\n      tiling_focus_target(origin_container, direction)?.map_or_else(\r\n        || workspace_focus_target(origin_container, direction, state),\r\n        |container| Ok(Some(container)),\r\n      )?\r\n    }\r\n    Container::NonTilingWindow(ref non_tiling_window) => {\r\n      match non_tiling_window.state() {\r\n        WindowState::Floating(_) => {\r\n          floating_focus_target(origin_container, direction)\r\n        }\r\n        WindowState::Fullscreen(_) => {\r\n          workspace_focus_target(origin_container, direction, state)?\r\n        }\r\n        _ => None,\r\n      }\r\n    }\r\n    Container::Workspace(_) => {\r\n      workspace_focus_target(origin_container, direction, state)?\r\n    }\r\n    _ => None,\r\n  };\r\n\r\n  // Set focus to the target container.\r\n  if let Some(focus_target) = focus_target {\r\n    set_focused_descendant(&focus_target, None);\r\n    state.pending_sync.queue_focus_change().queue_cursor_jump();\r\n  }\r\n\r\n  Ok(())\r\n}\r\n\r\nfn floating_focus_target(\r\n  origin_container: &Container,\r\n  direction: &Direction,\r\n) -> Option<Container> {\r\n  let is_floating = |sibling: &Container| {\r\n    sibling.as_non_tiling_window().is_some_and(|window| {\r\n      matches!(window.state(), WindowState::Floating(_))\r\n    })\r\n  };\r\n\r\n  let mut floating_siblings =\r\n    origin_container.siblings().filter(is_floating);\r\n\r\n  // Wrap if next/previous floating window is not found.\r\n  match direction {\r\n    Direction::Left => origin_container\r\n      .next_siblings()\r\n      .find(is_floating)\r\n      .or_else(|| floating_siblings.last()),\r\n    Direction::Right => origin_container\r\n      .prev_siblings()\r\n      .find(is_floating)\r\n      .or_else(|| floating_siblings.next()),\r\n    // Cannot focus vertically from a floating window.\r\n    _ => None,\r\n  }\r\n}\r\n\r\n/// Gets a focus target within the current workspace. Traverse upwards from\r\n/// the origin container to find an adjacent container that can be focused.\r\nfn tiling_focus_target(\r\n  origin_container: &Container,\r\n  direction: &Direction,\r\n) -> anyhow::Result<Option<Container>> {\r\n  let tiling_direction = TilingDirection::from_direction(direction);\r\n  let mut origin_or_ancestor = origin_container.clone();\r\n\r\n  // Traverse upwards from the focused container. Stop searching when a\r\n  // workspace is encountered.\r\n  while !origin_or_ancestor.is_workspace() {\r\n    let parent = origin_or_ancestor\r\n      .parent()\r\n      .and_then(|parent| parent.as_direction_container().ok())\r\n      .context(\"No direction container.\")?;\r\n\r\n    // Skip if the tiling direction doesn't match.\r\n    if parent.tiling_direction() != tiling_direction {\r\n      origin_or_ancestor = parent.into();\r\n      continue;\r\n    }\r\n\r\n    // Get the next/prev tiling sibling depending on the tiling direction.\r\n    let focus_target = match direction {\r\n      Direction::Up | Direction::Left => origin_or_ancestor\r\n        .prev_siblings()\r\n        .find_map(|c| c.as_tiling_container().ok()),\r\n      _ => origin_or_ancestor\r\n        .next_siblings()\r\n        .find_map(|c| c.as_tiling_container().ok()),\r\n    };\r\n\r\n    match focus_target {\r\n      Some(target) => {\r\n        // Return once a suitable focus target is found.\r\n        return Ok(match target {\r\n          TilingContainer::TilingWindow(_) => Some(target.into()),\r\n          TilingContainer::Split(split) => split\r\n            .descendant_in_direction(&direction.inverse())\r\n            .map(Into::into),\r\n        });\r\n      }\r\n      None => origin_or_ancestor = parent.into(),\r\n    }\r\n  }\r\n\r\n  Ok(None)\r\n}\r\n\r\n/// Gets a focus target outside of the current workspace in the given\r\n/// direction.\r\n///\r\n/// This will descend into the workspace in the given direction, and will\r\n/// always return a tiling container. This makes it different from the\r\n/// `focus_workspace` command with `FocusWorkspaceTarget::Direction`.\r\nfn workspace_focus_target(\r\n  origin_container: &Container,\r\n  direction: &Direction,\r\n  state: &WmState,\r\n) -> anyhow::Result<Option<Container>> {\r\n  let monitor = origin_container.monitor().context(\"No monitor.\")?;\r\n\r\n  let target_workspace = state\r\n    .monitor_in_direction(&monitor, direction)?\r\n    .and_then(|monitor| monitor.displayed_workspace());\r\n\r\n  let focused_fullscreen = target_workspace\r\n    .as_ref()\r\n    .and_then(|workspace| workspace.descendant_focus_order().next())\r\n    .filter(|focused| match focused {\r\n      Container::NonTilingWindow(window) => {\r\n        matches!(window.state(), WindowState::Fullscreen(_))\r\n      }\r\n      _ => false,\r\n    });\r\n\r\n  let focus_target = focused_fullscreen\r\n    .or_else(|| {\r\n      target_workspace.as_ref().and_then(|workspace| {\r\n        workspace\r\n          .descendant_in_direction(&direction.inverse())\r\n          .map(Into::into)\r\n      })\r\n    })\r\n    .or(target_workspace.map(Into::into));\r\n\r\n  Ok(focus_target)\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/commands/container/mod.rs",
    "content": "mod attach_container;\r\nmod detach_container;\r\nmod flatten_child_split_containers;\r\nmod flatten_split_container;\r\nmod focus_container_by_id;\r\nmod focus_in_direction;\r\nmod move_container_within_tree;\r\nmod replace_container;\r\nmod resize_tiling_container;\r\nmod set_focused_descendant;\r\nmod toggle_tiling_direction;\r\nmod wrap_in_split_container;\r\n\r\npub use attach_container::*;\r\npub use detach_container::*;\r\npub use flatten_child_split_containers::*;\r\npub use flatten_split_container::*;\r\npub use focus_container_by_id::*;\r\npub use focus_in_direction::*;\r\npub use move_container_within_tree::*;\r\npub use replace_container::*;\r\npub use resize_tiling_container::*;\r\npub use set_focused_descendant::*;\r\npub use toggle_tiling_direction::*;\r\npub use wrap_in_split_container::*;\r\n"
  },
  {
    "path": "packages/wm/src/commands/container/move_container_within_tree.rs",
    "content": "use anyhow::Context;\r\nuse wm_common::{VecDequeExt, WmEvent};\r\n\r\nuse super::{\r\n  attach_container, detach_container, flatten_child_split_containers,\r\n  set_focused_descendant,\r\n};\r\nuse crate::{models::Container, traits::CommonGetters, wm_state::WmState};\r\n\r\n/// Move a container to a new location in the tree. This detaches the\r\n/// container from its current parent and attaches it to the new parent at\r\n/// the specified index.\r\n///\r\n/// If this container is a tiling container, its siblings are resized on\r\n/// detach, and the container is sized to the default tiling size with its\r\n/// new siblings. No changes to the container's tiling size are made if\r\n/// its parent stays the same.\r\n///\r\n/// This will flatten any redundant split containers after moving the\r\n/// container, which can cause the target parent to become detached. For\r\n/// example, in the layout V[1 H[2]] where container 1 is moved down, the\r\n/// parent gets removed resulting in V[1 2].\r\npub fn move_container_within_tree(\r\n  container_to_move: &Container,\r\n  target_parent: &Container,\r\n  target_index: usize,\r\n  state: &WmState,\r\n) -> anyhow::Result<()> {\r\n  // Create iterator of parent, grandparent, and great-grandparent.\r\n  let ancestors =\r\n    container_to_move.ancestors().take(3).collect::<Vec<_>>();\r\n\r\n  // Get lowest common ancestor (LCA) between `container_to_move` and\r\n  // `target_parent`. This could be the `target_parent` itself.\r\n  let lowest_common_ancestor =\r\n    lowest_common_ancestor(container_to_move, target_parent)\r\n      .context(\"No common ancestor between containers.\")?;\r\n\r\n  // If the container is already a child of the target parent, then shift\r\n  // it to the target index.\r\n  if container_to_move.parent().context(\"No parent.\")? == *target_parent {\r\n    target_parent\r\n      .borrow_children_mut()\r\n      .shift_to_index(target_index, container_to_move.clone());\r\n\r\n    if container_to_move.has_focus(None) {\r\n      state.emit_event(WmEvent::FocusedContainerMoved {\r\n        focused_container: container_to_move.to_dto()?,\r\n      });\r\n    }\r\n\r\n    return Ok(());\r\n  }\r\n\r\n  // Handle case where target parent is the LCA. For example, when swapping\r\n  // sibling containers or moving a container to a direct ancestor.\r\n  if *target_parent == lowest_common_ancestor {\r\n    return move_to_lowest_common_ancestor(\r\n      container_to_move,\r\n      &lowest_common_ancestor,\r\n      target_index,\r\n      state,\r\n    );\r\n  }\r\n\r\n  // Get ancestor of `container_to_move` that is a direct child of the LCA.\r\n  // This could be the `container_to_move` itself.\r\n  let container_to_move_ancestor = container_to_move\r\n    .self_and_ancestors()\r\n    .find(|ancestor| {\r\n      ancestor.parent() == Some(lowest_common_ancestor.clone())\r\n    })\r\n    .context(\"Failed to get ancestor of container to move.\")?;\r\n\r\n  // Likewise, get ancestor of `target_parent` that is a direct child of\r\n  // the LCA.\r\n  let target_parent_ancestor = target_parent\r\n    .self_and_ancestors()\r\n    .find(|ancestor| {\r\n      ancestor.parent() == Some(lowest_common_ancestor.clone())\r\n    })\r\n    .context(\"Failed to get ancestor of target parent.\")?;\r\n\r\n  // Get whether the container is the focused descendant in its original\r\n  // subtree from the LCA.\r\n  let is_focused_descendant = *container_to_move\r\n    == container_to_move_ancestor\r\n    || container_to_move\r\n      .has_focus(Some(container_to_move_ancestor.clone()));\r\n\r\n  // Get whether the ancestor of `container_to_move` appears before\r\n  // `target_parent`'s ancestor in the child focus order of the LCA.\r\n  let original_focus_index = container_to_move_ancestor.focus_index();\r\n  let is_subtree_focused =\r\n    original_focus_index < target_parent_ancestor.focus_index();\r\n\r\n  detach_container(container_to_move.clone())?;\r\n  attach_container(\r\n    &container_to_move.clone(),\r\n    &target_parent.clone(),\r\n    Some(target_index),\r\n  )?;\r\n\r\n  // Set `container_to_move` as focused descendant within target subtree if\r\n  // its original subtree had focus more recently (even if the container is\r\n  // not the last focused within that subtree).\r\n  if is_subtree_focused {\r\n    set_focused_descendant(\r\n      container_to_move,\r\n      Some(&target_parent_ancestor),\r\n    );\r\n  }\r\n\r\n  // If the focused descendant is moved to the targets subtree, then the\r\n  // target's ancestor should be placed before the original ancestor in\r\n  // LCA's child focus order.\r\n  if is_focused_descendant && is_subtree_focused {\r\n    lowest_common_ancestor\r\n      .borrow_child_focus_order_mut()\r\n      .shift_to_index(original_focus_index, target_parent_ancestor.id());\r\n  }\r\n\r\n  // After moving the container, flatten any redundant split containers.\r\n  // For example, in the layout V[1 H[2]] where container 1 is moved down\r\n  // to become V[H[1 2]], this will then need to be flattened to V[1 2].\r\n  for ancestor in ancestors.iter().rev() {\r\n    flatten_child_split_containers(ancestor)?;\r\n  }\r\n\r\n  if container_to_move.has_focus(None) {\r\n    state.emit_event(WmEvent::FocusedContainerMoved {\r\n      focused_container: container_to_move.to_dto()?,\r\n    });\r\n  }\r\n\r\n  Ok(())\r\n}\r\n\r\nfn move_to_lowest_common_ancestor(\r\n  container_to_move: &Container,\r\n  lowest_common_ancestor: &Container,\r\n  target_index: usize,\r\n  state: &WmState,\r\n) -> anyhow::Result<()> {\r\n  // Keep reference to focus index of container's ancestor in LCA's child\r\n  // focus order.\r\n  let original_focus_index = container_to_move\r\n    .self_and_ancestors()\r\n    .find(|ancestor| {\r\n      ancestor.parent() == Some(lowest_common_ancestor.clone())\r\n    })\r\n    .map(|ancestor| ancestor.focus_index())\r\n    .context(\"Failed to get focus index of container's ancestor.\")?;\r\n\r\n  detach_container(container_to_move.clone())?;\r\n\r\n  attach_container(\r\n    &container_to_move.clone(),\r\n    &lowest_common_ancestor.clone(),\r\n    Some(target_index),\r\n  )?;\r\n\r\n  lowest_common_ancestor\r\n    .borrow_child_focus_order_mut()\r\n    .shift_to_index(original_focus_index, container_to_move.id());\r\n\r\n  if container_to_move.has_focus(None) {\r\n    state.emit_event(WmEvent::FocusedContainerMoved {\r\n      focused_container: container_to_move.to_dto()?,\r\n    });\r\n  }\r\n\r\n  Ok(())\r\n}\r\n\r\n/// Gets the lowest container in the tree that has both `container_a` and\r\n/// `container_b` as descendants.\r\npub fn lowest_common_ancestor(\r\n  container_a: &Container,\r\n  container_b: &Container,\r\n) -> Option<Container> {\r\n  let mut ancestor_a = Some(container_a.clone());\r\n\r\n  // Traverse upwards from container A.\r\n  while let Some(current_ancestor_a) = ancestor_a {\r\n    let mut ancestor_b = Some(container_b.clone());\r\n\r\n    // Traverse upwards from container B.\r\n    while let Some(current_ancestor_b) = ancestor_b {\r\n      if current_ancestor_a == current_ancestor_b {\r\n        return Some(current_ancestor_a);\r\n      }\r\n\r\n      ancestor_b = current_ancestor_b.parent();\r\n    }\r\n\r\n    ancestor_a = current_ancestor_a.parent();\r\n  }\r\n\r\n  None\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/commands/container/replace_container.rs",
    "content": "use anyhow::{bail, Context};\r\nuse wm_common::VecDequeExt;\r\n\r\nuse super::{attach_container, detach_container, resize_tiling_container};\r\nuse crate::{\r\n  models::Container,\r\n  traits::{CommonGetters, TilingSizeGetters},\r\n};\r\n\r\n/// Replaces a container at the specified index.\r\n///\r\n/// The replaced container will be detached from the tree.\r\npub fn replace_container(\r\n  replacement_container: &Container,\r\n  target_parent: &Container,\r\n  target_index: usize,\r\n) -> anyhow::Result<()> {\r\n  if !replacement_container.is_detached() {\r\n    bail!(\r\n      \"Cannot use an already attached container as replacement container.\"\r\n    );\r\n  }\r\n\r\n  let container_to_replace = target_parent\r\n    .children()\r\n    .get(target_index)\r\n    .cloned()\r\n    .with_context(|| format!(\"No container at index {target_index}.\"))?;\r\n\r\n  let focus_index = container_to_replace.focus_index();\r\n  let tiling_size = container_to_replace\r\n    .as_tiling_container()\r\n    .map(|c| c.tiling_size());\r\n\r\n  // TODO: This will cause issues if the detach causes a wrapping split\r\n  // container to flatten. Currently, that scenario shouldn't be possible.\r\n  // We also can't attach first before detaching, because detaching\r\n  // removes child based on ID and both containers might have the same ID.\r\n  detach_container(container_to_replace)?;\r\n\r\n  attach_container(\r\n    replacement_container,\r\n    target_parent,\r\n    Some(target_index),\r\n  )?;\r\n\r\n  // Shift to the correct focus index.\r\n  target_parent\r\n    .borrow_child_focus_order_mut()\r\n    .shift_to_index(focus_index, replacement_container.id());\r\n\r\n  // Match the tiling size of the replaced container if the replacement\r\n  // is also a tiling container.\r\n  if let Ok(tiling_size) = tiling_size {\r\n    if let Ok(replacement_container) =\r\n      replacement_container.as_tiling_container()\r\n    {\r\n      resize_tiling_container(&replacement_container, tiling_size);\r\n    }\r\n  }\r\n\r\n  Ok(())\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/commands/container/resize_tiling_container.rs",
    "content": "use crate::{\r\n  models::TilingContainer,\r\n  traits::{CommonGetters, TilingSizeGetters, MIN_TILING_SIZE},\r\n};\r\n\r\npub fn resize_tiling_container(\r\n  container_to_resize: &TilingContainer,\r\n  target_size: f32,\r\n) {\r\n  let tiling_siblings =\r\n    container_to_resize.tiling_siblings().collect::<Vec<_>>();\r\n\r\n  // Ignore cases where the container is the only child.\r\n  if tiling_siblings.is_empty() {\r\n    container_to_resize.set_tiling_size(1.);\r\n    return;\r\n  }\r\n\r\n  // Prevent the container from being smaller than the minimum size, and\r\n  // larger than the space available from sibling containers.\r\n  #[allow(clippy::cast_precision_loss)]\r\n  let clamped_target_size = target_size.clamp(\r\n    MIN_TILING_SIZE,\r\n    1. - (tiling_siblings.len() as f32 * MIN_TILING_SIZE),\r\n  );\r\n\r\n  let size_delta = clamped_target_size - container_to_resize.tiling_size();\r\n  container_to_resize.set_tiling_size(clamped_target_size);\r\n\r\n  // Get available tiling size amongst siblings.\r\n  let available_size =\r\n    tiling_siblings.iter().fold(0.0, |sum, container| {\r\n      sum + container.tiling_size() - MIN_TILING_SIZE\r\n    });\r\n\r\n  // Distribute the available tiling size amongst its siblings.\r\n  for sibling in &tiling_siblings {\r\n    // Get percentage of resize that affects this container. Siblings are\r\n    // resized in proportion to their current size (i.e. larger containers\r\n    // are shrunk more).\r\n    let resize_factor =\r\n      (sibling.tiling_size() - MIN_TILING_SIZE) / available_size;\r\n\r\n    let size_delta = resize_factor * size_delta;\r\n\r\n    sibling.set_tiling_size(sibling.tiling_size() - size_delta);\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/commands/container/set_focused_descendant.rs",
    "content": "use wm_common::VecDequeExt;\r\n\r\nuse crate::{models::Container, traits::CommonGetters};\r\n\r\n/// Set a given container as the focused container up to and including the\r\n/// end ancestor.\r\npub fn set_focused_descendant(\r\n  focused_descendant: &Container,\r\n  end_ancestor: Option<&Container>,\r\n) {\r\n  let mut target = focused_descendant.clone();\r\n\r\n  // Traverse upwards, shifting the container's ancestors to the front in\r\n  // their focus order.\r\n  while let Some(parent) = target.parent() {\r\n    parent\r\n      .borrow_child_focus_order_mut()\r\n      .shift_to_index(0, target.id());\r\n\r\n    // Exit if we've reached the end ancestor.\r\n    if end_ancestor\r\n      .as_ref()\r\n      .is_some_and(|end_ancestor| target.id() == end_ancestor.id())\r\n    {\r\n      break;\r\n    }\r\n\r\n    target = parent;\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/commands/container/toggle_tiling_direction.rs",
    "content": "use anyhow::Context;\r\nuse wm_common::{TilingDirection, WmEvent};\r\n\r\nuse super::{flatten_split_container, wrap_in_split_container};\r\nuse crate::{\r\n  models::{Container, DirectionContainer, SplitContainer, TilingWindow},\r\n  traits::{CommonGetters, TilingDirectionGetters},\r\n  user_config::UserConfig,\r\n  wm_state::WmState,\r\n};\r\n\r\npub fn toggle_tiling_direction(\r\n  container: Container,\r\n  state: &mut WmState,\r\n  config: &UserConfig,\r\n) -> anyhow::Result<()> {\r\n  let direction_container = match container {\r\n    Container::TilingWindow(tiling_window) => {\r\n      toggle_window_direction(tiling_window, config)\r\n    }\r\n    Container::Workspace(workspace) => {\r\n      workspace\r\n        .set_tiling_direction(workspace.tiling_direction().inverse());\r\n\r\n      Ok(workspace.into())\r\n    }\r\n    // Can only toggle tiling direction from a tiling window or workspace.\r\n    _ => return Ok(()),\r\n  }?;\r\n\r\n  state.emit_event(WmEvent::TilingDirectionChanged {\r\n    direction_container: direction_container.to_dto()?,\r\n    new_tiling_direction: direction_container.tiling_direction(),\r\n  });\r\n\r\n  Ok(())\r\n}\r\n\r\nfn toggle_window_direction(\r\n  tiling_window: TilingWindow,\r\n  config: &UserConfig,\r\n) -> anyhow::Result<DirectionContainer> {\r\n  let parent = tiling_window\r\n    .direction_container()\r\n    .context(\"No direction container.\")?;\r\n\r\n  // If the window is an only child, then either change the tiling\r\n  // direction of its parent workspace or flatten its parent split\r\n  // container.\r\n  if tiling_window.tiling_siblings().count() == 0 {\r\n    return match parent {\r\n      DirectionContainer::Workspace(workspace) => {\r\n        workspace\r\n          .set_tiling_direction(workspace.tiling_direction().inverse());\r\n\r\n        Ok(workspace.into())\r\n      }\r\n      DirectionContainer::Split(split_container) => {\r\n        flatten_split_container(split_container.clone())?;\r\n\r\n        tiling_window\r\n          .direction_container()\r\n          .context(\"No direction container.\")\r\n      }\r\n    };\r\n  }\r\n\r\n  // Create a new split container to wrap the window.\r\n  let split_container = SplitContainer::new(\r\n    parent.tiling_direction().inverse(),\r\n    config.value.gaps.clone(),\r\n  );\r\n\r\n  wrap_in_split_container(\r\n    &split_container,\r\n    &parent.into(),\r\n    &[tiling_window.into()],\r\n  )?;\r\n\r\n  Ok(split_container.into())\r\n}\r\n\r\npub fn set_tiling_direction(\r\n  container: Container,\r\n  state: &mut WmState,\r\n  config: &UserConfig,\r\n  tiling_direction: &TilingDirection,\r\n) -> anyhow::Result<()> {\r\n  let direction_container = container\r\n    .direction_container()\r\n    .context(\"No direction container.\")?;\r\n\r\n  if direction_container.tiling_direction() == *tiling_direction {\r\n    Ok(())\r\n  } else {\r\n    toggle_tiling_direction(container, state, config)\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/commands/container/wrap_in_split_container.rs",
    "content": "use std::collections::VecDeque;\r\n\r\nuse anyhow::Context;\r\n\r\nuse crate::{\r\n  models::{Container, SplitContainer, TilingContainer},\r\n  traits::{CommonGetters, TilingSizeGetters},\r\n};\r\n\r\npub fn wrap_in_split_container(\r\n  split_container: &SplitContainer,\r\n  target_parent: &Container,\r\n  target_children: &[TilingContainer],\r\n) -> anyhow::Result<()> {\r\n  let starting_index = target_children\r\n    .iter()\r\n    .map(CommonGetters::index)\r\n    .min()\r\n    .context(\"Failed to get starting index.\")?;\r\n\r\n  target_parent\r\n    .borrow_children_mut()\r\n    .insert(starting_index, split_container.clone().into());\r\n\r\n  let starting_focus_index = target_children\r\n    .iter()\r\n    .map(CommonGetters::focus_index)\r\n    .min()\r\n    .context(\"Failed to get starting focus index.\")?;\r\n\r\n  target_parent\r\n    .borrow_child_focus_order_mut()\r\n    .insert(starting_focus_index, split_container.id());\r\n\r\n  // Get the total tiling size amongst all children.\r\n  let total_tiling_size = target_children\r\n    .iter()\r\n    .map(TilingSizeGetters::tiling_size)\r\n    .sum::<f32>();\r\n\r\n  let target_children_ids = target_children\r\n    .iter()\r\n    .map(CommonGetters::id)\r\n    .collect::<Vec<_>>();\r\n\r\n  let sorted_focus_ids = target_parent\r\n    .borrow_child_focus_order()\r\n    .iter()\r\n    .filter(|id| target_children_ids.contains(id))\r\n    .copied()\r\n    .collect::<VecDeque<_>>();\r\n\r\n  // Set the split container's parent and tiling size.\r\n  *split_container.borrow_parent_mut() = Some(target_parent.clone());\r\n  split_container.set_tiling_size(total_tiling_size);\r\n\r\n  // Move the children from their original parent to the split container.\r\n  for target_child in target_children {\r\n    *target_child.borrow_parent_mut() =\r\n      Some(split_container.clone().into());\r\n\r\n    split_container\r\n      .borrow_children_mut()\r\n      .push_back(target_child.clone().into());\r\n\r\n    target_parent\r\n      .borrow_children_mut()\r\n      .retain(|child| child != &target_child.clone().into());\r\n\r\n    target_parent\r\n      .borrow_child_focus_order_mut()\r\n      .retain(|id| id != &target_child.id());\r\n\r\n    // Scale the tiling size to the new split container.\r\n    target_child\r\n      .set_tiling_size(target_child.tiling_size() / total_tiling_size);\r\n  }\r\n\r\n  // Add original focus order to split container.\r\n  *split_container.borrow_child_focus_order_mut() = sorted_focus_ids;\r\n\r\n  Ok(())\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/commands/general/cycle_focus.rs",
    "content": "use anyhow::Context;\r\nuse wm_common::WindowState;\r\n\r\nuse crate::{\r\n  commands::container::set_focused_descendant,\r\n  traits::{CommonGetters, WindowGetters},\r\n  user_config::UserConfig,\r\n  wm_state::WmState,\r\n};\r\n\r\n/// Cycles focus through windows of different states. In order, this will\r\n/// change from tiling -> floating -> fullscreen -> minimized, then back to\r\n/// tiling.\r\n///\r\n/// Does nothing if a workspace is focused.\r\n#[allow(clippy::fn_params_excessive_bools)]\r\npub fn cycle_focus(\r\n  omit_floating: bool,\r\n  omit_fullscreen: bool,\r\n  omit_minimized: bool,\r\n  omit_tiling: bool,\r\n  state: &mut WmState,\r\n  config: &UserConfig,\r\n) -> anyhow::Result<()> {\r\n  let focused_container =\r\n    state.focused_container().context(\"No focused container.\")?;\r\n\r\n  if let Ok(window) = focused_container.as_window_container() {\r\n    let workspace = window.workspace().context(\"No workspace.\")?;\r\n\r\n    let current = window.state();\r\n    let mut next = next_state(&current, config);\r\n\r\n    loop {\r\n      // Break if we have cycled back to the current state.\r\n      if current.is_same_state(&next) {\r\n        break;\r\n      }\r\n\r\n      // Skip the next state if it is to be omitted.\r\n      if (omit_floating && matches!(next, WindowState::Floating(_)))\r\n        || omit_fullscreen && matches!(next, WindowState::Fullscreen(_))\r\n        || omit_minimized && matches!(next, WindowState::Minimized)\r\n        || omit_tiling && matches!(next, WindowState::Tiling)\r\n      {\r\n        next = next_state(&next, config);\r\n        continue;\r\n      }\r\n\r\n      // Get window that matches the next state.\r\n      let window_of_type = workspace\r\n        .descendant_focus_order()\r\n        .filter_map(|descendant| descendant.as_window_container().ok())\r\n        .find(|descendant| {\r\n          matches!(\r\n            (descendant.state(), &next),\r\n            (WindowState::Floating(_), WindowState::Floating(_))\r\n              | (WindowState::Fullscreen(_), WindowState::Fullscreen(_))\r\n              | (WindowState::Minimized, WindowState::Minimized)\r\n              | (WindowState::Tiling, WindowState::Tiling)\r\n          )\r\n        });\r\n\r\n      if let Some(window) = window_of_type {\r\n        set_focused_descendant(&window.into(), None);\r\n        state.pending_sync.queue_focus_change().queue_cursor_jump();\r\n        break;\r\n      }\r\n\r\n      next = next_state(&next, config);\r\n    }\r\n  }\r\n\r\n  Ok(())\r\n}\r\n\r\nfn next_state(\r\n  current_state: &WindowState,\r\n  config: &UserConfig,\r\n) -> WindowState {\r\n  match current_state {\r\n    WindowState::Floating(_) => WindowState::Fullscreen(\r\n      config\r\n        .value\r\n        .window_behavior\r\n        .state_defaults\r\n        .fullscreen\r\n        .clone(),\r\n    ),\r\n    WindowState::Fullscreen(_) => WindowState::Minimized,\r\n    WindowState::Minimized => WindowState::Tiling,\r\n    WindowState::Tiling => WindowState::Floating(\r\n      config.value.window_behavior.state_defaults.floating.clone(),\r\n    ),\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/commands/general/disable_binding_mode.rs",
    "content": "use wm_common::WmEvent;\r\n\r\nuse crate::wm_state::WmState;\r\n\r\npub fn disable_binding_mode(name: &str, state: &mut WmState) {\r\n  state.binding_modes = state\r\n    .binding_modes\r\n    .iter()\r\n    .filter(|config| config.name != name)\r\n    .cloned()\r\n    .collect::<Vec<_>>();\r\n\r\n  state.emit_event(WmEvent::BindingModesChanged {\r\n    new_binding_modes: state.binding_modes.clone(),\r\n  });\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/commands/general/enable_binding_mode.rs",
    "content": "use anyhow::Context;\r\nuse wm_common::WmEvent;\r\n\r\nuse crate::{user_config::UserConfig, wm_state::WmState};\r\n\r\npub fn enable_binding_mode(\r\n  name: &str,\r\n  state: &mut WmState,\r\n  config: &UserConfig,\r\n) -> anyhow::Result<()> {\r\n  let binding_mode = config\r\n    .value\r\n    .binding_modes\r\n    .iter()\r\n    .find(|config| name == config.name)\r\n    .with_context(|| {\r\n      format!(\"No binding mode found with the name '{name}'.\")\r\n    })?;\r\n\r\n  state.binding_modes = vec![binding_mode.clone()];\r\n\r\n  state.emit_event(WmEvent::BindingModesChanged {\r\n    new_binding_modes: state.binding_modes.clone(),\r\n  });\r\n\r\n  Ok(())\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/commands/general/mod.rs",
    "content": "mod cycle_focus;\r\nmod disable_binding_mode;\r\nmod enable_binding_mode;\r\nmod platform_sync;\r\nmod reload_config;\r\nmod shell_exec;\r\nmod toggle_pause;\r\n\r\npub use cycle_focus::*;\r\npub use disable_binding_mode::*;\r\npub use enable_binding_mode::*;\r\npub use platform_sync::*;\r\npub use reload_config::*;\r\npub use shell_exec::*;\r\npub use toggle_pause::*;\r\n"
  },
  {
    "path": "packages/wm/src/commands/general/platform_sync.rs",
    "content": "use anyhow::Context;\r\n#[cfg(target_os = \"windows\")]\r\nuse wm_common::WindowEffectConfig;\r\nuse wm_common::{\r\n  CursorJumpTrigger, DisplayState, HideCorner, HideMethod, UniqueExt,\r\n  WindowState, WmEvent,\r\n};\r\n#[cfg(target_os = \"windows\")]\r\nuse wm_platform::NativeWindowWindowsExt;\r\n#[cfg(target_os = \"windows\")]\r\nuse wm_platform::{CornerStyle, OpacityValue};\r\nuse wm_platform::{Rect, WindowZOrder};\r\n\r\nuse crate::{\r\n  models::{Container, WindowContainer},\r\n  traits::{CommonGetters, PositionGetters, WindowGetters},\r\n  user_config::UserConfig,\r\n  wm_state::WmState,\r\n};\r\n\r\npub fn platform_sync(\r\n  state: &mut WmState,\r\n  config: &UserConfig,\r\n) -> anyhow::Result<()> {\r\n  let focused_container =\r\n    state.focused_container().context(\"No focused container.\")?;\r\n\r\n  if state.pending_sync.needs_focus_update() {\r\n    sync_focus(&focused_container, state)?;\r\n  }\r\n\r\n  if !state.pending_sync.containers_to_redraw().is_empty()\r\n    || !state.pending_sync.workspaces_to_reorder().is_empty()\r\n  {\r\n    redraw_containers(&focused_container, state, config)?;\r\n  }\r\n\r\n  if state.pending_sync.needs_cursor_jump()\r\n    && config.value.general.cursor_jump.enabled\r\n  {\r\n    jump_cursor(focused_container.clone(), state, config)?;\r\n  }\r\n\r\n  if state.pending_sync.needs_focused_effect_update()\r\n    || state.pending_sync.needs_all_effects_update()\r\n  {\r\n    // Keep reference to the previous window that had focus effects\r\n    // applied.\r\n    let prev_effects_window = state.prev_effects_window.clone();\r\n\r\n    if let Ok(window) = focused_container.as_window_container() {\r\n      apply_window_effects(&window, true, config);\r\n      state.prev_effects_window = Some(window.clone());\r\n    } else {\r\n      state.prev_effects_window = None;\r\n    }\r\n\r\n    // Get windows that should have the unfocused border applied to them.\r\n    // For the sake of performance, we only update the border of the\r\n    // previously focused window. If the `reset_window_effects` flag is\r\n    // passed, the unfocused border is applied to all unfocused windows.\r\n    let unfocused_windows =\r\n      if state.pending_sync.needs_all_effects_update() {\r\n        state.windows()\r\n      } else {\r\n        prev_effects_window.into_iter().collect()\r\n      }\r\n      .into_iter()\r\n      .filter(|window| window.id() != focused_container.id());\r\n\r\n    for window in unfocused_windows {\r\n      apply_window_effects(&window, false, config);\r\n    }\r\n  }\r\n\r\n  state.pending_sync.clear();\r\n\r\n  Ok(())\r\n}\r\n\r\nfn sync_focus(\r\n  focused_container: &Container,\r\n  state: &mut WmState,\r\n) -> anyhow::Result<()> {\r\n  let native_window = focused_container.as_window_container().ok();\r\n\r\n  // Sets focus to the appropriate target:\r\n  // - If the container is a window, focuses that window.\r\n  // - If the container is a workspace, \"resets\" focus by focusing the\r\n  //   desktop window.\r\n  //\r\n  // In either case, a `PlatformEvent::WindowFocused` event is subsequently\r\n  // triggered.\r\n  let result = if let Some(window) = native_window {\r\n    tracing::info!(\"Setting focus to window: {window}\");\r\n    window.native().focus()\r\n  } else {\r\n    tracing::info!(\"Setting focus to the desktop window.\");\r\n    state.dispatcher.reset_focus()\r\n  };\r\n\r\n  if let Err(err) = result {\r\n    tracing::warn!(\"Failed to set focus: {}\", err);\r\n  }\r\n\r\n  state.emit_event(WmEvent::FocusChanged {\r\n    focused_container: focused_container.to_dto()?,\r\n  });\r\n\r\n  Ok(())\r\n}\r\n\r\n/// Finds windows that should be brought to the top of their workspace's\r\n/// z-order.\r\n///\r\n/// Windows are brought to front if they match the focused window's state\r\n/// (floating/tiling) and any of these conditions are met:\r\n///  * Focus has changed to a different window.\r\n///  * Focused window's state has changed (e.g. tiling -> floating).\r\n///  * Focused window has moved to a different workspace.\r\nfn windows_to_bring_to_front(\r\n  focused_container: &Container,\r\n  state: &WmState,\r\n) -> anyhow::Result<Vec<WindowContainer>> {\r\n  let focused_workspace =\r\n    focused_container.workspace().context(\"No workspace.\")?;\r\n\r\n  // Add focused workspace if there's been a focus change.\r\n  let workspaces_to_reorder = state\r\n    .pending_sync\r\n    .workspaces_to_reorder()\r\n    .iter()\r\n    .chain(\r\n      state\r\n        .pending_sync\r\n        .needs_focus_update()\r\n        .then_some(&focused_workspace),\r\n    )\r\n    .unique_by(|workspace| workspace.id());\r\n\r\n  // Bring forward windows that match the focused state. Only do this for\r\n  // tiling/floating windows.\r\n  let windows_to_bring_to_front = workspaces_to_reorder\r\n    .flat_map(|workspace| {\r\n      let focused_descendant = workspace\r\n        .descendant_focus_order()\r\n        .next()\r\n        .and_then(|container| container.as_window_container().ok());\r\n\r\n      match focused_descendant {\r\n        Some(focused_descendant) => workspace\r\n          .descendants()\r\n          .filter_map(|descendant| descendant.as_window_container().ok())\r\n          .filter(|window| {\r\n            let is_floating_or_tiling = matches!(\r\n              window.state(),\r\n              WindowState::Floating(_) | WindowState::Tiling\r\n            );\r\n\r\n            is_floating_or_tiling\r\n              && window.state().is_same_state(&focused_descendant.state())\r\n          })\r\n          .collect(),\r\n        None => vec![],\r\n      }\r\n    })\r\n    .collect::<Vec<_>>();\r\n\r\n  Ok(windows_to_bring_to_front)\r\n}\r\n\r\n#[allow(clippy::too_many_lines)]\r\nfn redraw_containers(\r\n  focused_container: &Container,\r\n  state: &mut WmState,\r\n  config: &UserConfig,\r\n) -> anyhow::Result<()> {\r\n  let windows_to_redraw = state.windows_to_redraw();\r\n  let windows_to_bring_to_front =\r\n    windows_to_bring_to_front(focused_container, state)?;\r\n\r\n  let windows_to_update = {\r\n    let mut windows = windows_to_redraw\r\n      .iter()\r\n      .chain(&windows_to_bring_to_front)\r\n      .unique_by(|window| window.id())\r\n      .collect::<Vec<_>>();\r\n\r\n    let descendant_focus_order = state\r\n      .root_container\r\n      .descendant_focus_order()\r\n      .collect::<Vec<_>>();\r\n\r\n    // Sort the windows to update by their focus order. The most recently\r\n    // focused window will be updated first.\r\n    // TODO: To reduce flicker, redraw windows that will be shown first,\r\n    // then redraw the ones to be hidden last.\r\n    windows.sort_by_key(|window| {\r\n      descendant_focus_order\r\n        .iter()\r\n        .position(|order| order.id() == window.id())\r\n    });\r\n\r\n    windows\r\n  };\r\n\r\n  // Get monitors by their optimal hide corner.\r\n  let monitors_by_hide_corner = state.monitors_by_hide_corner();\r\n\r\n  for window in windows_to_update.iter().rev() {\r\n    let should_bring_to_front = windows_to_bring_to_front.contains(window);\r\n\r\n    let workspace =\r\n      window.workspace().context(\"Window has no workspace.\")?;\r\n\r\n    let monitor = window.monitor().context(\"No monitor.\")?;\r\n    let hide_corner = monitors_by_hide_corner\r\n      .iter()\r\n      .find(|(m, _)| m.id() == monitor.id())\r\n      .map(|(_, hide_corner)| hide_corner)\r\n      .context(\"Monitor not found in hide corner map.\")?;\r\n\r\n    // Whether the window should be shown above all other windows.\r\n    let z_order = match window.state() {\r\n      WindowState::Floating(config) if config.shown_on_top => {\r\n        WindowZOrder::TopMost\r\n      }\r\n      WindowState::Fullscreen(config) if config.shown_on_top => {\r\n        WindowZOrder::TopMost\r\n      }\r\n      _ if should_bring_to_front => {\r\n        let focused_descendant = workspace\r\n          .descendant_focus_order()\r\n          .next()\r\n          .and_then(|container| container.as_window_container().ok());\r\n\r\n        if let Some(focused_descendant) = focused_descendant {\r\n          if window.id() == focused_descendant.id() {\r\n            WindowZOrder::Normal\r\n          } else {\r\n            WindowZOrder::AfterWindow(focused_descendant.native().id())\r\n          }\r\n        } else {\r\n          WindowZOrder::Normal\r\n        }\r\n      }\r\n      _ => WindowZOrder::Normal,\r\n    };\r\n\r\n    // Set the z-order of the window.\r\n    //\r\n    // NOTE: macOS doesn't have a robust public API for setting the z-order\r\n    // of a window. See `NativeWindow::raise` for more details.\r\n    #[cfg(target_os = \"windows\")]\r\n    if should_bring_to_front && !windows_to_redraw.contains(window) {\r\n      tracing::info!(\"Updating window z-order: {window}\");\r\n\r\n      if let Err(err) = window.native().set_z_order(&z_order) {\r\n        tracing::warn!(\"Failed to set window z-order: {}\", err);\r\n      }\r\n    }\r\n\r\n    // Skip updating the window's position if it only required a z-order\r\n    // change.\r\n    if !windows_to_redraw.contains(window) {\r\n      continue;\r\n    }\r\n\r\n    // Transition display state depending on whether window will be\r\n    // shown or hidden.\r\n    window.set_display_state(\r\n      match (window.display_state(), workspace.is_displayed()) {\r\n        (DisplayState::Hidden | DisplayState::Hiding, true) => {\r\n          DisplayState::Showing\r\n        }\r\n        (DisplayState::Shown | DisplayState::Showing, false) => {\r\n          DisplayState::Hiding\r\n        }\r\n        _ => window.display_state(),\r\n      },\r\n    );\r\n\r\n    let is_visible = matches!(\r\n      window.display_state(),\r\n      DisplayState::Showing | DisplayState::Shown\r\n    );\r\n\r\n    if let Err(err) =\r\n      reposition_window(window, *hide_corner, &z_order, is_visible, config)\r\n    {\r\n      tracing::warn!(\"Failed to set window position: {}\", err);\r\n    }\r\n\r\n    // Whether the window is either transitioning to or from fullscreen.\r\n    // TODO: This check can be improved since `prev_state` can be\r\n    // fullscreen without it needing to be marked as not fullscreen.\r\n    #[cfg(target_os = \"windows\")]\r\n    {\r\n      let is_transitioning_fullscreen =\r\n        match (window.prev_state(), window.state()) {\r\n          (Some(_), WindowState::Fullscreen(s)) if !s.maximized => true,\r\n          (Some(WindowState::Fullscreen(_)), _) => true,\r\n          _ => false,\r\n        };\r\n\r\n      if is_transitioning_fullscreen {\r\n        if let Err(err) = window.native().mark_fullscreen(matches!(\r\n          window.state(),\r\n          WindowState::Fullscreen(_)\r\n        )) {\r\n          tracing::warn!(\"Failed to mark window as fullscreen: {}\", err);\r\n        }\r\n      }\r\n    }\r\n\r\n    // Skip setting taskbar visibility if the window is hidden (has no\r\n    // effect). Since cloaked windows are normally always visible in the\r\n    // taskbar, we only need to set visibility if `show_all_in_taskbar` is\r\n    // `false`.\r\n    #[cfg(target_os = \"windows\")]\r\n    if config.value.general.hide_method == HideMethod::Cloak\r\n      && !config.value.general.show_all_in_taskbar\r\n      && matches!(\r\n        window.display_state(),\r\n        DisplayState::Showing | DisplayState::Hiding\r\n      )\r\n    {\r\n      if let Err(err) = window.native().set_taskbar_visibility(is_visible)\r\n      {\r\n        tracing::warn!(\"Failed to set taskbar visibility: {}\", err);\r\n      }\r\n    }\r\n  }\r\n\r\n  Ok(())\r\n}\r\n\r\nfn reposition_window(\r\n  window: &WindowContainer,\r\n  hide_corner: HideCorner,\r\n  // LINT: `z_order` is only used on Windows.\r\n  #[cfg_attr(not(target_os = \"windows\"), allow(unused_variables))]\r\n  z_order: &WindowZOrder,\r\n  is_visible: bool,\r\n  config: &UserConfig,\r\n) -> anyhow::Result<()> {\r\n  let rect = window\r\n    .to_rect()?\r\n    .apply_delta(&window.total_border_delta()?, None);\r\n\r\n  // For `HideMethod::PlaceInCorner`, we need to reposition hidden windows\r\n  // to the corner of the monitor.\r\n  if config.value.general.hide_method == HideMethod::PlaceInCorner\r\n    && !is_visible\r\n  {\r\n    const VISIBLE_SLIVER: i32 = 1;\r\n\r\n    let monitor_rect = window\r\n      .monitor()\r\n      .context(\"No monitor.\")?\r\n      .native_properties()\r\n      .working_area;\r\n\r\n    let frame = window.native_properties().frame;\r\n\r\n    let position_y = monitor_rect.bottom - VISIBLE_SLIVER;\r\n    let position_x = match hide_corner {\r\n      HideCorner::BottomLeft => {\r\n        monitor_rect.left + VISIBLE_SLIVER - frame.width()\r\n      }\r\n      HideCorner::BottomRight => monitor_rect.right - VISIBLE_SLIVER,\r\n    };\r\n\r\n    // Even though the window size is unchanged, `NativeWindow::set_frame`\r\n    // is used instead of `NativeWindow::reposition` because the latter\r\n    // resulted in occasional incorrect positionings on macOS.\r\n    window.native().set_frame(&Rect::from_xy(\r\n      position_x,\r\n      position_y,\r\n      frame.width(),\r\n      frame.height(),\r\n    ))?;\r\n\r\n    return Ok(());\r\n  }\r\n\r\n  if window.active_drag().is_some() {\r\n    window.native().resize(rect.width(), rect.height())?;\r\n  } else {\r\n    #[cfg(target_os = \"macos\")]\r\n    window.native().set_frame(&rect)?;\r\n\r\n    #[cfg(target_os = \"windows\")]\r\n    {\r\n      use wm_platform::{\r\n        SWP_ASYNCWINDOWPOS, SWP_FRAMECHANGED, SWP_NOACTIVATE,\r\n        SWP_NOCOPYBITS, SWP_NOSENDCHANGING, WS_MAXIMIZEBOX,\r\n      };\r\n\r\n      // Restore window if it's minimized/maximized and shouldn't be. This\r\n      // is needed to be able to move and resize it.\r\n      let should_restore = match &window.state() {\r\n        // Need to restore window if transitioning from maximized\r\n        // fullscreen to non-maximized fullscreen.\r\n        WindowState::Fullscreen(fullscreen) => {\r\n          !fullscreen.maximized && window.native().is_maximized()?\r\n        }\r\n        // No need to restore window if it'll be minimized. Transitioning\r\n        // from maximized to minimized works without having to\r\n        // restore.\r\n        WindowState::Minimized => false,\r\n        _ => {\r\n          window.native().is_minimized()?\r\n            || window.native().is_maximized()?\r\n        }\r\n      };\r\n\r\n      if should_restore {\r\n        // Restoring to position has the same effect as `ShowWindow` with\r\n        // `SW_RESTORE`, but doesn't cause a flicker.\r\n        window.native().restore(Some(&rect))?;\r\n      }\r\n\r\n      let mut swp_flags = SWP_NOACTIVATE\r\n        | SWP_NOCOPYBITS\r\n        | SWP_NOSENDCHANGING\r\n        | SWP_ASYNCWINDOWPOS;\r\n\r\n      match &window.state() {\r\n        WindowState::Minimized => {\r\n          if !window.native().is_minimized()? {\r\n            window.native().minimize()?;\r\n          }\r\n        }\r\n        WindowState::Fullscreen(fullscreen)\r\n          if fullscreen.maximized\r\n            && window.native().has_window_style(WS_MAXIMIZEBOX) =>\r\n        {\r\n          if !window.native().is_maximized()? {\r\n            window.native().maximize()?;\r\n          }\r\n\r\n          window.native().set_window_pos(z_order, &rect, swp_flags)?;\r\n        }\r\n        _ => {\r\n          swp_flags |= SWP_FRAMECHANGED;\r\n\r\n          window.native().set_window_pos(z_order, &rect, swp_flags)?;\r\n\r\n          // When there's a mismatch between the DPI of the monitor and the\r\n          // window, the window might be sized incorrectly after the first\r\n          // move. If we set the position twice, inconsistencies after the\r\n          // first move are resolved.\r\n          if window.has_pending_dpi_adjustment() {\r\n            window.native().set_window_pos(z_order, &rect, swp_flags)?;\r\n          }\r\n        }\r\n      }\r\n\r\n      // Set visibility based on the hide method.\r\n      if config.value.general.hide_method == HideMethod::Cloak {\r\n        window.native().set_cloaked(!is_visible)?;\r\n      } else if is_visible {\r\n        window.native().show()?;\r\n      } else {\r\n        window.native().hide()?;\r\n      }\r\n    }\r\n  }\r\n\r\n  Ok(())\r\n}\r\n\r\nfn jump_cursor(\r\n  focused_container: Container,\r\n  state: &WmState,\r\n  config: &UserConfig,\r\n) -> anyhow::Result<()> {\r\n  let cursor_jump = &config.value.general.cursor_jump;\r\n\r\n  let jump_target = match cursor_jump.trigger {\r\n    CursorJumpTrigger::WindowFocus => Some(focused_container),\r\n    CursorJumpTrigger::MonitorFocus => {\r\n      let target_monitor =\r\n        focused_container.monitor().context(\"No monitor.\")?;\r\n\r\n      let cursor_monitor = state\r\n        .dispatcher\r\n        .cursor_position()\r\n        .ok()\r\n        .and_then(|pos| state.monitor_at_point(&pos));\r\n\r\n      // Jump to the target monitor if the cursor is not already on it.\r\n      cursor_monitor\r\n        .filter(|monitor| monitor.id() != target_monitor.id())\r\n        .map(|_| target_monitor.into())\r\n    }\r\n  };\r\n\r\n  if let Some(jump_target) = jump_target {\r\n    let center = jump_target.to_rect()?.center_point();\r\n\r\n    if let Err(err) = state.dispatcher.set_cursor_position(&center) {\r\n      tracing::warn!(\"Failed to set cursor position: {}\", err);\r\n    }\r\n  }\r\n\r\n  Ok(())\r\n}\r\n\r\nfn apply_window_effects(\r\n  // LINT: `window` is only used on Windows.\r\n  #[cfg_attr(not(target_os = \"windows\"), allow(unused_variables))]\r\n  window: &WindowContainer,\r\n  is_focused: bool,\r\n  config: &UserConfig,\r\n) {\r\n  let window_effects = &config.value.window_effects;\r\n\r\n  // LINT: `effect_config` is only used on Windows.\r\n  #[cfg_attr(not(target_os = \"windows\"), allow(unused_variables))]\r\n  let effect_config = if is_focused {\r\n    &window_effects.focused_window\r\n  } else {\r\n    &window_effects.other_windows\r\n  };\r\n\r\n  // Skip if both focused + non-focused window effects are disabled.\r\n  #[cfg(target_os = \"windows\")]\r\n  if window_effects.focused_window.border.enabled\r\n    || window_effects.other_windows.border.enabled\r\n  {\r\n    apply_border_effect(window, effect_config);\r\n  }\r\n\r\n  #[cfg(target_os = \"windows\")]\r\n  if window_effects.focused_window.hide_title_bar.enabled\r\n    || window_effects.other_windows.hide_title_bar.enabled\r\n  {\r\n    apply_hide_title_bar_effect(window, effect_config);\r\n  }\r\n\r\n  #[cfg(target_os = \"windows\")]\r\n  if window_effects.focused_window.corner_style.enabled\r\n    || window_effects.other_windows.corner_style.enabled\r\n  {\r\n    apply_corner_effect(window, effect_config);\r\n  }\r\n\r\n  #[cfg(target_os = \"windows\")]\r\n  if window_effects.focused_window.transparency.enabled\r\n    || window_effects.other_windows.transparency.enabled\r\n  {\r\n    apply_transparency_effect(window, effect_config);\r\n  }\r\n}\r\n\r\n#[cfg(target_os = \"windows\")]\r\nfn apply_border_effect(\r\n  window: &WindowContainer,\r\n  effect_config: &WindowEffectConfig,\r\n) {\r\n  let border_color = if effect_config.border.enabled {\r\n    Some(&effect_config.border.color)\r\n  } else {\r\n    None\r\n  };\r\n\r\n  _ = window.native().set_border_color(border_color);\r\n\r\n  let native = window.native().clone();\r\n  let border_color = border_color.cloned();\r\n\r\n  // Re-apply border color after a short delay to better handle\r\n  // windows that change it themselves.\r\n  tokio::task::spawn(async move {\r\n    tokio::time::sleep(std::time::Duration::from_millis(50)).await;\r\n    _ = native.set_border_color(border_color.as_ref());\r\n  });\r\n}\r\n\r\n#[cfg(target_os = \"windows\")]\r\nfn apply_hide_title_bar_effect(\r\n  window: &WindowContainer,\r\n  effect_config: &WindowEffectConfig,\r\n) {\r\n  _ = window\r\n    .native()\r\n    .set_title_bar_visibility(!effect_config.hide_title_bar.enabled);\r\n}\r\n\r\n#[cfg(target_os = \"windows\")]\r\nfn apply_corner_effect(\r\n  window: &WindowContainer,\r\n  effect_config: &WindowEffectConfig,\r\n) {\r\n  let corner_style = if effect_config.corner_style.enabled {\r\n    &effect_config.corner_style.style\r\n  } else {\r\n    &CornerStyle::Default\r\n  };\r\n\r\n  _ = window.native().set_corner_style(corner_style);\r\n}\r\n\r\n#[cfg(target_os = \"windows\")]\r\nfn apply_transparency_effect(\r\n  window: &WindowContainer,\r\n  effect_config: &WindowEffectConfig,\r\n) {\r\n  let transparency = if effect_config.transparency.enabled {\r\n    &effect_config.transparency.opacity\r\n  } else {\r\n    // Reset the transparency to default.\r\n    &OpacityValue::from_alpha(u8::MAX)\r\n  };\r\n\r\n  _ = window.native().set_transparency(transparency);\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/commands/general/reload_config.rs",
    "content": "use anyhow::Context;\r\nuse tracing::{info, warn};\r\n#[cfg(target_os = \"windows\")]\r\nuse wm_common::{HideMethod, ParsedConfig};\r\nuse wm_common::{WindowRuleEvent, WmEvent};\r\n#[cfg(target_os = \"windows\")]\r\nuse wm_platform::NativeWindowWindowsExt;\r\n\r\nuse crate::{\r\n  commands::{window::run_window_rules, workspace::sort_workspaces},\r\n  traits::{CommonGetters, TilingSizeGetters, WindowGetters},\r\n  user_config::UserConfig,\r\n  wm::WindowManager,\r\n  wm_state::WmState,\r\n};\r\n\r\npub fn reload_config(\r\n  state: &mut WmState,\r\n  config: &mut UserConfig,\r\n) -> anyhow::Result<()> {\r\n  info!(\"Config reloaded.\");\r\n\r\n  // Keep reference to old config for comparison.\r\n  #[cfg(target_os = \"windows\")]\r\n  let old_config = config.value.clone();\r\n\r\n  // Re-evaluate user config file and set its values in state.\r\n  config.reload()?;\r\n\r\n  // Re-run window rules on all active windows.\r\n  for window in state.windows() {\r\n    window.set_done_window_rules(Vec::new());\r\n    run_window_rules(window, &WindowRuleEvent::Manage, state, config)?;\r\n  }\r\n\r\n  update_workspace_configs(state, config)?;\r\n\r\n  update_container_gaps(state, config);\r\n\r\n  #[cfg(target_os = \"windows\")]\r\n  update_window_effects(&old_config, state, config)?;\r\n\r\n  // Ensure all windows are shown when hide method is changed.\r\n  #[cfg(target_os = \"windows\")]\r\n  if old_config.general.hide_method != config.value.general.hide_method\r\n    && config.value.general.hide_method == HideMethod::Cloak\r\n  {\r\n    for window in state.windows() {\r\n      let _ = window.native().show();\r\n    }\r\n  }\r\n\r\n  // Ensure all windows are shown in taskbar when `show_all_in_taskbar` is\r\n  // changed.\r\n  #[cfg(target_os = \"windows\")]\r\n  if old_config.general.show_all_in_taskbar\r\n    != config.value.general.show_all_in_taskbar\r\n    && config.value.general.show_all_in_taskbar\r\n  {\r\n    for window in state.windows() {\r\n      let _ = window.native().set_taskbar_visibility(true);\r\n    }\r\n  }\r\n\r\n  // Clear active binding modes.\r\n  state.binding_modes = Vec::new();\r\n\r\n  // Redraw full container tree.\r\n  state\r\n    .pending_sync\r\n    .queue_container_to_redraw(state.root_container.clone());\r\n\r\n  // Emit the updated config.\r\n  state.emit_event(WmEvent::UserConfigChanged {\r\n    config_path: config\r\n      .path\r\n      .to_str()\r\n      .context(\"Invalid config path.\")?\r\n      .to_string(),\r\n    config_string: config.value_str.clone(),\r\n    parsed_config: config.value.clone(),\r\n  });\r\n\r\n  // Run config reload commands.\r\n  WindowManager::run_commands(\r\n    &config.value.general.config_reload_commands.clone(),\r\n    state.focused_container().context(\"No focused container.\")?,\r\n    state,\r\n    config,\r\n  )?;\r\n\r\n  Ok(())\r\n}\r\n\r\n/// Update configs of active workspaces.\r\nfn update_workspace_configs(\r\n  state: &mut WmState,\r\n  config: &UserConfig,\r\n) -> anyhow::Result<()> {\r\n  let workspaces = state.workspaces();\r\n\r\n  for workspace in &workspaces {\r\n    let monitor = workspace.monitor().context(\"No monitor.\")?;\r\n\r\n    let workspace_config = config\r\n      .value\r\n      .workspaces\r\n      .iter()\r\n      .find(|config| config.name == workspace.config().name)\r\n      .or_else(|| {\r\n        // When the workspace config is not found, the current name of the\r\n        // workspace has been removed. So, we reassign the first suitable\r\n        // workspace config to the workspace.\r\n        config\r\n          .workspace_config_for_monitor(&monitor, &workspaces)\r\n          .or_else(|| config.next_inactive_workspace_config(&workspaces))\r\n      });\r\n\r\n    match workspace_config {\r\n      None => {\r\n        warn!(\r\n          \"Unable to update workspace config. No available workspace configs.\"\r\n        );\r\n      }\r\n      Some(workspace_config) => {\r\n        if *workspace_config != workspace.config() {\r\n          workspace.set_config(workspace_config.clone());\r\n\r\n          sort_workspaces(&monitor, config)?;\r\n\r\n          state.emit_event(WmEvent::WorkspaceUpdated {\r\n            updated_workspace: workspace.to_dto()?,\r\n          });\r\n        }\r\n      }\r\n    }\r\n  }\r\n\r\n  Ok(())\r\n}\r\n\r\n/// Updates outer gap of workspaces and inner gaps of tiling containers.\r\nfn update_container_gaps(state: &mut WmState, config: &UserConfig) {\r\n  let tiling_containers = state\r\n    .root_container\r\n    .self_and_descendants()\r\n    .filter_map(|container| container.as_tiling_container().ok());\r\n\r\n  for container in tiling_containers {\r\n    container.set_gaps_config(config.value.gaps.clone());\r\n  }\r\n\r\n  for workspace in state.workspaces() {\r\n    workspace.set_gaps_config(config.value.gaps.clone());\r\n  }\r\n}\r\n\r\n#[cfg(target_os = \"windows\")]\r\nfn update_window_effects(\r\n  old_config: &ParsedConfig,\r\n  state: &mut WmState,\r\n  config: &UserConfig,\r\n) -> anyhow::Result<()> {\r\n  let focused_container =\r\n    state.focused_container().context(\"No focused container.\")?;\r\n\r\n  let window_effects = &config.value.window_effects;\r\n  let old_window_effects = &old_config.window_effects;\r\n\r\n  // Window border effects are left at system defaults if disabled in the\r\n  // config. However, when transitioning from colored borders to having\r\n  // them disabled, it's best to reset to the system defaults.\r\n  if !window_effects.focused_window.border.enabled\r\n    && old_window_effects.focused_window.border.enabled\r\n  {\r\n    if let Ok(window) = focused_container.as_window_container() {\r\n      _ = window.native().set_border_color(None);\r\n    }\r\n  }\r\n\r\n  if !window_effects.other_windows.border.enabled\r\n    && old_window_effects.other_windows.border.enabled\r\n  {\r\n    let unfocused_windows = state\r\n      .windows()\r\n      .into_iter()\r\n      .filter(|window| window.id() != focused_container.id());\r\n\r\n    for window in unfocused_windows {\r\n      _ = window.native().set_border_color(None);\r\n    }\r\n  }\r\n\r\n  state.pending_sync.queue_all_effects_update();\r\n\r\n  Ok(())\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/commands/general/shell_exec.rs",
    "content": "use std::path::Path;\r\n\r\n#[cfg(target_os = \"windows\")]\r\nuse anyhow::Context;\r\n#[cfg(target_os = \"macos\")]\r\nuse shell_util::{CommandOptions, Shell};\r\n#[cfg(target_os = \"windows\")]\r\nuse wm_platform::DispatcherExtWindows;\r\n\r\nuse crate::wm_state::WmState;\r\n\r\npub fn shell_exec(\r\n  command: &str,\r\n  // LINT: `hide_window` is only used on Windows.\r\n  #[cfg_attr(not(target_os = \"windows\"), allow(unused_variables))]\r\n  hide_window: bool,\r\n  state: &WmState,\r\n) -> anyhow::Result<()> {\r\n  let (program, args) = parse_command(command, state)?;\r\n  tracing::info!(\r\n    \"Parsed command program: '{}', args: '{}'.\",\r\n    program,\r\n    args\r\n  );\r\n\r\n  // NOTE: The standard library's `Command::new` is not used because it\r\n  // launches the program as a subprocess. This prevents cleanup of handles\r\n  // held by our process (e.g. the IPC server port) until the subprocess\r\n  // exits.\r\n  let result = {\r\n    #[cfg(target_os = \"macos\")]\r\n    {\r\n      Shell::spawn(\r\n        &program,\r\n        args.split_whitespace(),\r\n        &CommandOptions::default(),\r\n      )\r\n    }\r\n    #[cfg(target_os = \"windows\")]\r\n    {\r\n      let home_dir =\r\n        home::home_dir().context(\"Unable to get home directory.\")?;\r\n\r\n      // TODO: Use `Shell::spawn` instead. `ShellExecuteExW` is still used\r\n      // to be able to launch programs from the App Paths registry\r\n      // (`HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths`), like\r\n      // `chrome` without it being in $PATH.\r\n      state.dispatcher.shell_execute_ex(\r\n        &program,\r\n        &args,\r\n        &home_dir,\r\n        hide_window,\r\n      )\r\n    }\r\n  };\r\n\r\n  result.map_err(|err| {\r\n    anyhow::anyhow!(\r\n      \"Shell exec failed for '{command}'. Make sure the program exists and is \\\r\n      accessible from your shell. Error: {err}\",\r\n    )\r\n  })?;\r\n\r\n  Ok(())\r\n}\r\n\r\n/// Parses a command string into a program name/path and arguments. This\r\n/// also expands any environment variables found in the command string if\r\n/// they are wrapped in `%` characters. If the command string is a path,\r\n/// a file extension is required.\r\n///\r\n/// This is similar to the `SHEvaluateSystemCommandTemplate` Win32\r\n/// function. It also parses program name/path and arguments, but can't\r\n/// handle `/` as file path delimiters and it errors for certain programs\r\n/// (e.g. `code`).\r\n///\r\n/// Returns a tuple containing the program name/path and arguments.\r\n///\r\n/// # Examples\r\n///\r\n/// ```no_run\r\n/// let (prog, args) = parse_command(\"code .\")?;\r\n/// assert_eq!(prog, \"code\");\r\n/// assert_eq!(args, \".\");\r\n///\r\n/// let (prog, args) = parse_command(\r\n///   r#\"C:\\Program Files\\Git\\git-bash --cd=C:\\Users\\larsb\\.glaze-wm\"#,\r\n/// )?;\r\n/// assert_eq!(prog, r#\"C:\\Program Files\\Git\\git-bash\"#);\r\n/// assert_eq!(args, r#\"--cd=C:\\Users\\larsb\\.glaze-wm\"#);\r\n/// ```\r\nfn parse_command(\r\n  command: &str,\r\n  // LINT: `state` is only used on Windows.\r\n  #[cfg_attr(not(target_os = \"windows\"), allow(unused_variables))]\r\n  state: &WmState,\r\n) -> anyhow::Result<(String, String)> {\r\n  // Expand environment variables in the command string.\r\n  let expanded_command = {\r\n    #[cfg(target_os = \"windows\")]\r\n    {\r\n      state.dispatcher.expand_env_strings(command)?\r\n    }\r\n    #[cfg(target_os = \"macos\")]\r\n    {\r\n      // TODO: Expand env variables on macOS.\r\n      command.to_string()\r\n    }\r\n  };\r\n\r\n  let command_parts =\r\n    expanded_command.split_whitespace().collect::<Vec<_>>();\r\n\r\n  // If the command starts with double quotes, then the program name/path\r\n  // is wrapped in double quotes (e.g. `\"C:\\path\\to\\app.exe\" --flag`).\r\n  if expanded_command.starts_with('\"') {\r\n    // Find the closing double quote.\r\n    let (closing_index, _) =\r\n      expanded_command.match_indices('\"').nth(2).ok_or_else(|| {\r\n        anyhow::anyhow!(\r\n          \"Shell exec failed for '{command}': command doesn't have an ending `\\\"`.\"\r\n        )\r\n      })?;\r\n\r\n    return Ok((\r\n      expanded_command[1..closing_index].to_string(),\r\n      expanded_command[closing_index + 1..].trim().to_string(),\r\n    ));\r\n  }\r\n\r\n  // The first part is the program name if it doesn't contain a slash or\r\n  // backslash.\r\n  if let Some(first_part) = command_parts.first() {\r\n    if !first_part.contains(&['/', '\\\\'][..]) {\r\n      let args = command_parts[1..].join(\" \");\r\n      return Ok(((*first_part).to_string(), args));\r\n    }\r\n  }\r\n\r\n  let mut cumulative_path = Vec::new();\r\n\r\n  // Lastly, iterate over the command until a valid file path is found.\r\n  for (part_index, &part) in command_parts.iter().enumerate() {\r\n    cumulative_path.push(part);\r\n\r\n    if Path::new(&cumulative_path.join(\" \")).is_file() {\r\n      return Ok((\r\n        cumulative_path.join(\" \"),\r\n        command_parts[part_index + 1..].join(\" \"),\r\n      ));\r\n    }\r\n  }\r\n\r\n  anyhow::bail!(\r\n    \"Shell exec failed for '{command}': program path is not valid.\"\r\n  )\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/commands/general/toggle_pause.rs",
    "content": "use wm_common::WmEvent;\r\n\r\nuse crate::wm_state::WmState;\r\n\r\n/// Pauses or unpauses the WM.\r\npub fn toggle_pause(state: &mut WmState) {\r\n  let is_paused = !state.is_paused;\r\n  state.is_paused = is_paused;\r\n\r\n  // Redraw full container tree on unpause.\r\n  if !is_paused {\r\n    state\r\n      .pending_sync\r\n      .queue_container_to_redraw(state.root_container.clone());\r\n  }\r\n\r\n  state.emit_event(WmEvent::PauseChanged { is_paused });\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/commands/mod.rs",
    "content": "pub mod container;\r\npub mod general;\r\npub mod monitor;\r\npub mod window;\r\npub mod workspace;\r\n"
  },
  {
    "path": "packages/wm/src/commands/monitor/add_monitor.rs",
    "content": "use anyhow::Context;\r\nuse tracing::info;\r\nuse wm_common::WmEvent;\r\nuse wm_platform::Display;\r\n\r\nuse crate::{\r\n  commands::{\r\n    container::{attach_container, move_container_within_tree},\r\n    workspace::{activate_workspace, sort_workspaces},\r\n  },\r\n  models::{Monitor, NativeMonitorProperties, Workspace},\r\n  traits::{CommonGetters, PositionGetters, WindowGetters},\r\n  user_config::UserConfig,\r\n  wm_state::WmState,\r\n};\r\n\r\npub fn add_monitor(\r\n  native_display: Display,\r\n  native_properties: NativeMonitorProperties,\r\n  state: &mut WmState,\r\n) -> anyhow::Result<Monitor> {\r\n  // Create `Monitor` instance. This uses the working area of the monitor\r\n  // instead of the bounds of the display. The working area excludes\r\n  // taskbars and other reserved display space.\r\n  let monitor = Monitor::new(native_display, native_properties);\r\n\r\n  attach_container(\r\n    &monitor.clone().into(),\r\n    &state.root_container.clone().into(),\r\n    None,\r\n  )?;\r\n\r\n  info!(\"Monitor added: {monitor}\");\r\n\r\n  state.emit_event(WmEvent::MonitorAdded {\r\n    added_monitor: monitor.to_dto()?,\r\n  });\r\n\r\n  Ok(monitor)\r\n}\r\n\r\npub fn move_bounded_workspaces_to_new_monitor(\r\n  monitor: &Monitor,\r\n  state: &mut WmState,\r\n  config: &UserConfig,\r\n) -> anyhow::Result<()> {\r\n  let bound_workspace_configs = config\r\n    .value\r\n    .workspaces\r\n    .iter()\r\n    .filter(|config| {\r\n      config.bind_to_monitor.is_some_and(|monitor_index| {\r\n        monitor.index() == monitor_index as usize\r\n      })\r\n    })\r\n    .collect::<Vec<_>>();\r\n\r\n  for workspace_config in bound_workspace_configs {\r\n    let existing_workspace =\r\n      state.workspace_by_name(&workspace_config.name);\r\n\r\n    if let Some(existing_workspace) = existing_workspace {\r\n      // Move workspaces that should be bound to the newly added monitor.\r\n      move_workspace_to_monitor(\r\n        &existing_workspace,\r\n        monitor,\r\n        state,\r\n        config,\r\n      )?;\r\n    } else if workspace_config.keep_alive {\r\n      // Activate all `keep_alive` workspaces for this monitor.\r\n      activate_workspace(\r\n        Some(&workspace_config.name),\r\n        Some(monitor.clone()),\r\n        state,\r\n        config,\r\n      )?;\r\n    }\r\n  }\r\n\r\n  // Make sure the monitor has at least one workspace. This will\r\n  // automatically prioritize bound workspace configs and fall back to the\r\n  // first available one if needed.\r\n  if monitor.child_count() == 0 {\r\n    activate_workspace(None, Some(monitor.clone()), state, config)?;\r\n  }\r\n\r\n  Ok(())\r\n}\r\n\r\n// TODO: Move to its own file once `swap-workspace` PR is merged.\r\n// Ref: https://github.com/glzr-io/glazewm/pull/980.\r\npub fn move_workspace_to_monitor(\r\n  workspace: &Workspace,\r\n  target_monitor: &Monitor,\r\n  state: &mut WmState,\r\n  config: &UserConfig,\r\n) -> anyhow::Result<()> {\r\n  let origin_monitor = workspace.monitor().context(\"No monitor.\")?;\r\n\r\n  move_container_within_tree(\r\n    &workspace.clone().into(),\r\n    &target_monitor.clone().into(),\r\n    target_monitor.child_count(),\r\n    state,\r\n  )?;\r\n\r\n  let windows = workspace\r\n    .descendants()\r\n    .filter_map(|descendant| descendant.as_window_container().ok());\r\n\r\n  for window in windows {\r\n    window.set_has_pending_dpi_adjustment(true);\r\n\r\n    window.set_floating_placement(\r\n      window\r\n        .floating_placement()\r\n        .translate_to_center(&workspace.to_rect()?),\r\n    );\r\n  }\r\n\r\n  // Get currently displayed workspace on the target monitor.\r\n  let displayed_workspace = target_monitor\r\n    .displayed_workspace()\r\n    .context(\"No displayed workspace.\")?;\r\n\r\n  state\r\n    .pending_sync\r\n    .queue_container_to_redraw(workspace.clone())\r\n    .queue_container_to_redraw(displayed_workspace);\r\n\r\n  match origin_monitor.child_count() {\r\n    0 => {\r\n      // Prevent origin monitor from having no workspaces.\r\n      activate_workspace(None, Some(origin_monitor), state, config)?;\r\n    }\r\n    _ => {\r\n      // Redraw the workspace on the origin monitor.\r\n      state.pending_sync.queue_container_to_redraw(\r\n        origin_monitor\r\n          .displayed_workspace()\r\n          .context(\"No displayed workspace.\")?,\r\n      );\r\n    }\r\n  }\r\n\r\n  sort_workspaces(target_monitor, config)?;\r\n\r\n  state.emit_event(WmEvent::WorkspaceUpdated {\r\n    updated_workspace: workspace.to_dto()?,\r\n  });\r\n\r\n  Ok(())\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/commands/monitor/focus_monitor.rs",
    "content": "use anyhow::Context;\r\n\r\nuse crate::{\r\n  commands::workspace::focus_workspace, models::WorkspaceTarget,\r\n  user_config::UserConfig, wm_state::WmState,\r\n};\r\n\r\n/// Focuses a monitor by a given monitor index.\r\npub fn focus_monitor(\r\n  monitor_index: usize,\r\n  state: &mut WmState,\r\n  config: &UserConfig,\r\n) -> anyhow::Result<()> {\r\n  let monitors = state.monitors();\r\n\r\n  let target_monitor = monitors.get(monitor_index).with_context(|| {\r\n    format!(\"Monitor at index {monitor_index} was not found.\")\r\n  })?;\r\n\r\n  let workspace_name = target_monitor\r\n    .displayed_workspace()\r\n    .map(|workspace| workspace.config().name)\r\n    .context(\"Failed to get target workspace name.\")?;\r\n\r\n  focus_workspace(WorkspaceTarget::Name(workspace_name), state, config)\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/commands/monitor/mod.rs",
    "content": "mod add_monitor;\r\nmod focus_monitor;\r\nmod remove_monitor;\r\nmod sort_monitors;\r\nmod update_monitor;\r\n\r\npub use add_monitor::*;\r\npub use focus_monitor::*;\r\npub use remove_monitor::*;\r\npub use sort_monitors::*;\r\npub use update_monitor::*;\r\n"
  },
  {
    "path": "packages/wm/src/commands/monitor/remove_monitor.rs",
    "content": "use anyhow::Context;\r\nuse tracing::info;\r\nuse wm_common::WmEvent;\r\n\r\nuse crate::{\r\n  commands::{\r\n    container::{detach_container, move_container_within_tree},\r\n    workspace::sort_workspaces,\r\n  },\r\n  models::Monitor,\r\n  traits::CommonGetters,\r\n  user_config::UserConfig,\r\n  wm_state::WmState,\r\n};\r\n\r\n#[allow(clippy::needless_pass_by_value)]\r\npub fn remove_monitor(\r\n  monitor: Monitor,\r\n  state: &mut WmState,\r\n  config: &UserConfig,\r\n) -> anyhow::Result<()> {\r\n  info!(\"Removing monitor: {monitor}\");\r\n\r\n  let target_monitor = state\r\n    .monitors()\r\n    .into_iter()\r\n    .find(|m| m.id() != monitor.id())\r\n    .context(\"No target monitor to move workspaces.\")?;\r\n\r\n  // Avoid moving empty workspaces.\r\n  let workspaces_to_move =\r\n    monitor.workspaces().into_iter().filter(|workspace| {\r\n      workspace.has_children() || workspace.config().keep_alive\r\n    });\r\n\r\n  for workspace in workspaces_to_move {\r\n    // Move workspace to target monitor.\r\n    move_container_within_tree(\r\n      &workspace.clone().into(),\r\n      &target_monitor.clone().into(),\r\n      target_monitor.child_count(),\r\n      state,\r\n    )?;\r\n\r\n    sort_workspaces(&target_monitor, config)?;\r\n\r\n    state.emit_event(WmEvent::WorkspaceUpdated {\r\n      updated_workspace: workspace.to_dto()?,\r\n    });\r\n  }\r\n\r\n  detach_container(monitor.clone().into())?;\r\n\r\n  state.emit_event(WmEvent::MonitorRemoved {\r\n    removed_id: monitor.id(),\r\n    removed_device_name: monitor.native_properties().device_name,\r\n  });\r\n\r\n  Ok(())\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/commands/monitor/sort_monitors.rs",
    "content": "use crate::{\r\n  models::RootContainer,\r\n  traits::{CommonGetters, PositionGetters},\r\n};\r\n\r\n/// Sorts the root container's monitors from left-to-right and\r\n/// top-to-bottom.\r\npub fn sort_monitors(root: &RootContainer) -> anyhow::Result<()> {\r\n  let monitors = root.monitors();\r\n\r\n  // Create a tuple of monitors and their rects.\r\n  let mut monitors_with_rect = monitors\r\n    .into_iter()\r\n    .map(|monitor| {\r\n      let rect = monitor.to_rect()?.clone();\r\n      anyhow::Ok((monitor, rect))\r\n    })\r\n    .try_collect::<Vec<_>>()?;\r\n\r\n  // Sort monitors from left-to-right, top-to-bottom.\r\n  monitors_with_rect.sort_by(|(_, rect_a), (_, rect_b)| {\r\n    if rect_a.x() == rect_b.x() {\r\n      rect_a.y().cmp(&rect_b.y())\r\n    } else {\r\n      rect_a.x().cmp(&rect_b.x())\r\n    }\r\n  });\r\n\r\n  *root.borrow_children_mut() = monitors_with_rect\r\n    .into_iter()\r\n    .map(|(monitor, _)| monitor.into())\r\n    .collect();\r\n\r\n  Ok(())\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/commands/monitor/update_monitor.rs",
    "content": "use tracing::info;\r\nuse wm_common::WmEvent;\r\nuse wm_platform::Display;\r\n\r\nuse crate::{\r\n  models::{Monitor, NativeMonitorProperties},\r\n  wm_state::WmState,\r\n};\r\n\r\npub fn update_monitor(\r\n  monitor: &Monitor,\r\n  native_display: &Display,\r\n  native_properties: NativeMonitorProperties,\r\n  state: &mut WmState,\r\n) -> anyhow::Result<()> {\r\n  monitor.set_native(native_display.clone());\r\n  monitor.set_native_properties(native_properties);\r\n\r\n  info!(\"Monitor updated: {monitor}\");\r\n\r\n  // TODO: Check that a property on the monitor actually changed.\r\n  state.emit_event(WmEvent::MonitorUpdated {\r\n    updated_monitor: monitor.to_dto()?,\r\n  });\r\n\r\n  Ok(())\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/commands/window/ignore_window.rs",
    "content": "use anyhow::Context;\r\nuse wm_common::WindowState;\r\n\r\nuse crate::{\r\n  commands::container::{\r\n    detach_container, flatten_child_split_containers,\r\n  },\r\n  models::WindowContainer,\r\n  traits::{CommonGetters, WindowGetters},\r\n  wm_state::WmState,\r\n};\r\n\r\n#[allow(clippy::needless_pass_by_value)]\r\npub fn ignore_window(\r\n  window: WindowContainer,\r\n  state: &mut WmState,\r\n) -> anyhow::Result<()> {\r\n  // Create iterator of parent, grandparent, and great-grandparent.\r\n  let ancestors = window.ancestors().take(3).collect::<Vec<_>>();\r\n\r\n  state.ignored_windows.push(window.native().clone());\r\n  detach_container(window.clone().into())?;\r\n\r\n  // After detaching the container, flatten any redundant split containers.\r\n  // For example, in the layout V[1 H[2]] where container 1 is detached to\r\n  // become V[H[2]], this will then need to be flattened to V[2].\r\n  for ancestor in ancestors.iter().rev() {\r\n    flatten_child_split_containers(ancestor)?;\r\n  }\r\n\r\n  // Sibling containers need to be redrawn if the window was tiling.\r\n  if window.state() == WindowState::Tiling {\r\n    let ancestor_to_redraw = ancestors\r\n      .into_iter()\r\n      .find(|ancestor| !ancestor.is_detached())\r\n      .context(\"No ancestor to redraw.\")?;\r\n\r\n    state\r\n      .pending_sync\r\n      .queue_containers_to_redraw(ancestor_to_redraw.tiling_children());\r\n  }\r\n\r\n  Ok(())\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/commands/window/manage_window.rs",
    "content": "use anyhow::Context;\r\nuse tracing::info;\r\nuse wm_common::{try_warn, WindowRuleEvent, WindowState, WmEvent};\r\nuse wm_platform::{NativeWindow, RectDelta};\r\n\r\nuse crate::{\r\n  commands::{\r\n    container::{attach_container, set_focused_descendant},\r\n    window::run_window_rules,\r\n  },\r\n  models::{\r\n    Container, Monitor, NativeWindowProperties, NonTilingWindow,\r\n    TilingWindow, WindowContainer,\r\n  },\r\n  traits::{CommonGetters, PositionGetters, WindowGetters},\r\n  user_config::UserConfig,\r\n  wm_state::WmState,\r\n};\r\n\r\npub fn manage_window(\r\n  native_window: NativeWindow,\r\n  target_parent: Option<Container>,\r\n  state: &mut WmState,\r\n  config: &mut UserConfig,\r\n) -> anyhow::Result<()> {\r\n  let Some(native_properties) =\r\n    check_is_manageable(&native_window).unwrap_or(None)\r\n  else {\r\n    return Ok(());\r\n  };\r\n\r\n  // Create the window instance. This may fail if the window handle has\r\n  // already been destroyed.\r\n  let window = try_warn!(create_window(\r\n    native_window,\r\n    native_properties,\r\n    target_parent,\r\n    state,\r\n    config\r\n  ));\r\n\r\n  // Set the newly added window as focus descendant. This means the window\r\n  // rules will be run as if the window is focused.\r\n  set_focused_descendant(&window.clone().into(), None);\r\n\r\n  // Window might be detached if `ignore` command has been invoked.\r\n  let updated_window = run_window_rules(\r\n    window.clone(),\r\n    &WindowRuleEvent::Manage,\r\n    state,\r\n    config,\r\n  )?;\r\n\r\n  if let Some(window) = updated_window {\r\n    info!(\"New window managed: {window}\");\r\n\r\n    state.emit_event(WmEvent::WindowManaged {\r\n      managed_window: window.to_dto()?,\r\n    });\r\n\r\n    // OS focus should be set to the newly added window in case it's not\r\n    // already focused.\r\n    state.pending_sync.queue_focus_change();\r\n\r\n    // Normally, a `PlatformEvent::WindowFocused` event is what triggers\r\n    // focus effects and workspace reordering to be applied. However, when\r\n    // a window is first launched, this event can come before the\r\n    // window is managed, and so we need to force an update here.\r\n    state.pending_sync.queue_focused_effect_update();\r\n    state.pending_sync.queue_workspace_to_reorder(\r\n      window.workspace().context(\"No workspace.\")?,\r\n    );\r\n\r\n    // Sibling containers need to be redrawn if the window is tiling.\r\n    state.pending_sync.queue_container_to_redraw(\r\n      if window.state() == WindowState::Tiling {\r\n        window.parent().context(\"No parent.\")?\r\n      } else {\r\n        window.into()\r\n      },\r\n    );\r\n  }\r\n\r\n  Ok(())\r\n}\r\n\r\n/// Checks if a window is manageable and retrieves its native properties.\r\n///\r\n/// Returns `Ok(Some(properties))` if the window is manageable and its\r\n/// properties were retrieved successfully.\r\nfn check_is_manageable(\r\n  native_window: &NativeWindow,\r\n) -> anyhow::Result<Option<NativeWindowProperties>> {\r\n  if !native_window.is_visible()? {\r\n    return Ok(None);\r\n  }\r\n\r\n  #[cfg(target_os = \"macos\")]\r\n  {\r\n    use wm_platform::NativeWindowExtMacOs;\r\n\r\n    let is_standard_window = native_window.role()? == \"AXWindow\"\r\n      && native_window.subrole()? == \"AXStandardWindow\";\r\n\r\n    if !is_standard_window {\r\n      return Ok(None);\r\n    }\r\n  }\r\n\r\n  // Ensure window has a valid process name, title, etc.\r\n  let native_properties = NativeWindowProperties::try_from(native_window)?;\r\n\r\n  #[cfg(target_os = \"windows\")]\r\n  {\r\n    use wm_platform::{\r\n      NativeWindowWindowsExt, WS_CAPTION, WS_CHILD, WS_EX_NOACTIVATE,\r\n      WS_EX_TOOLWINDOW,\r\n    };\r\n\r\n    // TODO: Temporary fix for managing Flow Launcher until a force manage\r\n    // command is added.\r\n    let is_flow_launcher = native_properties.process_name\r\n      == \"Flow.Launcher\"\r\n      && native_properties.title == \"Flow.Launcher\";\r\n\r\n    if !is_flow_launcher {\r\n      // Ensure window is top-level (i.e. not a child window). Ignore\r\n      // windows that cannot be focused or if they're unavailable in\r\n      // task switcher (alt+tab menu).\r\n      if native_window.has_window_style(WS_CHILD)\r\n        || native_window\r\n          .has_window_style_ex(WS_EX_NOACTIVATE | WS_EX_TOOLWINDOW)\r\n      {\r\n        return Ok(None);\r\n      }\r\n\r\n      // Some applications spawn top-level windows for menus that\r\n      // should be ignored. This includes the autocomplete popup in\r\n      // Notepad++ and title bar menu in Keepass. Although not\r\n      // foolproof, these can typically be identified by having an\r\n      // owner window and no title bar.\r\n      if native_window.has_owner_window()\r\n        && !native_window.has_window_style(WS_CAPTION)\r\n      {\r\n        return Ok(None);\r\n      }\r\n    }\r\n  }\r\n\r\n  Ok(Some(native_properties))\r\n}\r\n\r\nfn create_window(\r\n  native_window: NativeWindow,\r\n  native_properties: NativeWindowProperties,\r\n  target_parent: Option<Container>,\r\n  state: &mut WmState,\r\n  config: &UserConfig,\r\n) -> anyhow::Result<WindowContainer> {\r\n  let nearest_monitor = state\r\n    .nearest_monitor(&native_window)\r\n    .context(\"No nearest monitor.\")?;\r\n\r\n  let nearest_workspace = nearest_monitor\r\n    .displayed_workspace()\r\n    .context(\"No nearest workspace.\")?;\r\n\r\n  let gaps_config = config.value.gaps.clone();\r\n  let window_state =\r\n    window_state_to_create(&native_properties, &nearest_monitor, config)?;\r\n\r\n  // Attach the new window as the first child of the target parent (if\r\n  // provided), otherwise, add as a sibling of the focused container.\r\n  let (target_parent, target_index) = match target_parent {\r\n    Some(parent) => (parent, 0),\r\n    None => insertion_target(&window_state, state)?,\r\n  };\r\n\r\n  let target_workspace =\r\n    target_parent.workspace().context(\"No target workspace.\")?;\r\n\r\n  let prefers_centered = config\r\n    .value\r\n    .window_behavior\r\n    .state_defaults\r\n    .floating\r\n    .centered;\r\n\r\n  // Calculate where window should be placed when floating is enabled. Use\r\n  // the original width/height of the window and optionally position it in\r\n  // the center of the workspace.\r\n  let is_same_workspace = nearest_workspace.id() == target_workspace.id();\r\n  let floating_placement = {\r\n    let placement = if !is_same_workspace || prefers_centered {\r\n      native_properties\r\n        .frame\r\n        .translate_to_center(&target_workspace.to_rect()?)\r\n    } else {\r\n      native_properties.frame.clone()\r\n    };\r\n\r\n    // Clamp the window size to be within the workspace's outer gaps. 10px\r\n    // is arbitrary - helps differentiate from tiling windows.\r\n    let max_workspace_rect = target_workspace.max_workspace_rect()?;\r\n    placement.clamp_size(\r\n      max_workspace_rect.width() - 10,\r\n      max_workspace_rect.height() - 10,\r\n    )\r\n  };\r\n\r\n  // Window has no border delta unless it's later changed via the\r\n  // `adjust_borders` command.\r\n  let border_delta = RectDelta::zero();\r\n\r\n  let window_container: WindowContainer = match window_state {\r\n    WindowState::Tiling => TilingWindow::new(\r\n      None,\r\n      native_window,\r\n      native_properties,\r\n      None,\r\n      border_delta,\r\n      floating_placement,\r\n      false,\r\n      gaps_config,\r\n      Vec::new(),\r\n      None,\r\n    )\r\n    .into(),\r\n    _ => NonTilingWindow::new(\r\n      None,\r\n      native_window,\r\n      native_properties,\r\n      window_state,\r\n      None,\r\n      border_delta,\r\n      None,\r\n      floating_placement,\r\n      !prefers_centered,\r\n      Vec::new(),\r\n      None,\r\n    )\r\n    .into(),\r\n  };\r\n\r\n  attach_container(\r\n    &window_container.clone().into(),\r\n    &target_parent,\r\n    Some(target_index),\r\n  )?;\r\n\r\n  // The OS might spawn the window on a different monitor to the target\r\n  // parent, so adjustments might need to be made because of DPI.\r\n  if nearest_monitor\r\n    .has_dpi_difference(&window_container.clone().into())?\r\n  {\r\n    window_container.set_has_pending_dpi_adjustment(true);\r\n  }\r\n\r\n  Ok(window_container)\r\n}\r\n\r\n/// Gets the initial state for a window based on its native state.\r\n///\r\n/// Note that maximized windows are initialized as tiling.\r\nfn window_state_to_create(\r\n  native_properties: &NativeWindowProperties,\r\n  nearest_monitor: &Monitor,\r\n  config: &UserConfig,\r\n) -> anyhow::Result<WindowState> {\r\n  if native_properties.is_minimized {\r\n    return Ok(WindowState::Minimized);\r\n  }\r\n\r\n  let nearest_workspace = nearest_monitor\r\n    .displayed_workspace()\r\n    .context(\"No workspace.\")?;\r\n\r\n  // Only initialize as fullscreen if the window *exceeds* the workspace\r\n  // bounds (due to the 1px inset).\r\n  //\r\n  // For example, with 0px outer gaps and a window that covers the entire\r\n  // workspace, it would still not be initialized as fullscreen. The window\r\n  // needs to be within the workspace's outer gaps by at least 1px on each\r\n  // side.\r\n  if !native_properties.is_maximized\r\n    && native_properties\r\n      .frame\r\n      .inset(1)\r\n      .contains_rect(&nearest_workspace.max_workspace_rect()?)\r\n  {\r\n    return Ok(WindowState::Fullscreen(\r\n      config\r\n        .value\r\n        .window_behavior\r\n        .state_defaults\r\n        .fullscreen\r\n        .clone(),\r\n    ));\r\n  }\r\n\r\n  // Initialize windows that can't be resized as floating.\r\n  if !native_properties.is_resizable {\r\n    return Ok(WindowState::Floating(\r\n      config.value.window_behavior.state_defaults.floating.clone(),\r\n    ));\r\n  }\r\n\r\n  Ok(WindowState::default_from_config(&config.value))\r\n}\r\n\r\n/// Gets where to insert a new window in the container tree.\r\n///\r\n/// Rules:\r\n/// - For non-tiling windows: Always append to the workspace.\r\n/// - For tiling windows:\r\n///   1. Try to insert after the focused tiling window if one exists.\r\n///   2. If a non-tiling window is focused, try to insert after the first\r\n///      tiling window found.\r\n///   3. If no tiling windows exist, append to the workspace.\r\n///\r\n/// Returns tuple of (parent container, insertion index).\r\nfn insertion_target(\r\n  window_state: &WindowState,\r\n  state: &WmState,\r\n) -> anyhow::Result<(Container, usize)> {\r\n  let focused_container =\r\n    state.focused_container().context(\"No focused container.\")?;\r\n\r\n  let focused_workspace =\r\n    focused_container.workspace().context(\"No workspace.\")?;\r\n\r\n  // For tiling windows, try to find a suitable tiling window to insert\r\n  // next to.\r\n  if *window_state == WindowState::Tiling {\r\n    let sibling = match focused_container {\r\n      Container::TilingWindow(_) => Some(focused_container),\r\n      _ => focused_workspace\r\n        .descendant_focus_order()\r\n        .find(Container::is_tiling_window),\r\n    };\r\n\r\n    if let Some(sibling) = sibling {\r\n      return Ok((\r\n        sibling.parent().context(\"No parent.\")?,\r\n        sibling.index() + 1,\r\n      ));\r\n    }\r\n  }\r\n\r\n  // Default to appending to workspace.\r\n  Ok((\r\n    focused_workspace.clone().into(),\r\n    focused_workspace.child_count(),\r\n  ))\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/commands/window/mod.rs",
    "content": "mod ignore_window;\r\nmod manage_window;\r\nmod move_window_in_direction;\r\nmod move_window_to_workspace;\r\nmod resize_window;\r\nmod run_window_rules;\r\nmod set_window_position;\r\nmod set_window_size;\r\nmod unmanage_window;\r\nmod update_window_state;\r\n\r\npub use ignore_window::*;\r\npub use manage_window::*;\r\npub use move_window_in_direction::*;\r\npub use move_window_to_workspace::*;\r\npub use resize_window::*;\r\npub use run_window_rules::*;\r\npub use set_window_position::*;\r\npub use set_window_size::*;\r\npub use unmanage_window::*;\r\npub use update_window_state::*;\r\n"
  },
  {
    "path": "packages/wm/src/commands/window/move_window_in_direction.rs",
    "content": "use anyhow::Context;\r\nuse wm_common::{TilingDirection, WindowState};\r\nuse wm_platform::{Direction, Rect};\r\n\r\nuse crate::{\r\n  commands::container::{\r\n    flatten_child_split_containers, flatten_split_container,\r\n    move_container_within_tree, resize_tiling_container,\r\n    set_focused_descendant, wrap_in_split_container,\r\n  },\r\n  models::{\r\n    DirectionContainer, Monitor, NonTilingWindow, SplitContainer,\r\n    TilingContainer, TilingWindow, WindowContainer,\r\n  },\r\n  traits::{\r\n    CommonGetters, PositionGetters, TilingDirectionGetters, WindowGetters,\r\n  },\r\n  user_config::UserConfig,\r\n  wm_state::WmState,\r\n};\r\n\r\n/// The distance in pixels to snap the window to the monitor's edge.\r\nconst SNAP_DISTANCE: i32 = 15;\r\n\r\npub fn move_window_in_direction(\r\n  window: WindowContainer,\r\n  direction: &Direction,\r\n  state: &mut WmState,\r\n  config: &UserConfig,\r\n) -> anyhow::Result<()> {\r\n  match window {\r\n    WindowContainer::TilingWindow(window) => {\r\n      move_tiling_window(window, direction, state, config)\r\n    }\r\n    WindowContainer::NonTilingWindow(non_tiling_window) => {\r\n      match non_tiling_window.state() {\r\n        WindowState::Floating(_) => {\r\n          move_floating_window(non_tiling_window, direction, state)\r\n        }\r\n        WindowState::Fullscreen(_) => move_to_workspace_in_direction(\r\n          &non_tiling_window.into(),\r\n          direction,\r\n          state,\r\n        ),\r\n        _ => Ok(()),\r\n      }\r\n    }\r\n  }\r\n}\r\n\r\nfn move_tiling_window(\r\n  window_to_move: TilingWindow,\r\n  direction: &Direction,\r\n  state: &mut WmState,\r\n  config: &UserConfig,\r\n) -> anyhow::Result<()> {\r\n  // Flatten the parent split container if it only contains the window.\r\n  if let Some(split_parent) = window_to_move\r\n    .parent()\r\n    .and_then(|parent| parent.as_split().cloned())\r\n  {\r\n    if split_parent.child_count() == 1 {\r\n      flatten_split_container(split_parent)?;\r\n    }\r\n  }\r\n\r\n  let parent = window_to_move\r\n    .direction_container()\r\n    .context(\"No direction container.\")?;\r\n\r\n  let has_matching_tiling_direction = parent.tiling_direction()\r\n    == TilingDirection::from_direction(direction);\r\n\r\n  // Attempt to swap or move the window into a sibling container.\r\n  if has_matching_tiling_direction {\r\n    if let Some(sibling) =\r\n      tiling_sibling_in_direction(&window_to_move, direction)\r\n    {\r\n      return move_to_sibling_container(\r\n        window_to_move,\r\n        sibling,\r\n        direction,\r\n        state,\r\n      );\r\n    }\r\n  }\r\n\r\n  // Attempt to move the window to workspace in given direction.\r\n  if (has_matching_tiling_direction\r\n    || window_to_move.tiling_siblings().count() == 0)\r\n    && parent.is_workspace()\r\n  {\r\n    return move_to_workspace_in_direction(\r\n      &window_to_move.into(),\r\n      direction,\r\n      state,\r\n    );\r\n  }\r\n\r\n  // The window cannot be moved within the parent container, so traverse\r\n  // upwards to find an ancestor that has the correct tiling direction.\r\n  let target_ancestor = parent.ancestors().find_map(|ancestor| {\r\n    ancestor.as_direction_container().ok().filter(|ancestor| {\r\n      ancestor.tiling_direction()\r\n        == TilingDirection::from_direction(direction)\r\n    })\r\n  });\r\n\r\n  match target_ancestor {\r\n    // If there is no suitable ancestor, then change the tiling direction\r\n    // of the workspace.\r\n    None => invert_workspace_tiling_direction(\r\n      window_to_move,\r\n      direction,\r\n      state,\r\n      config,\r\n    ),\r\n    // Otherwise, move the container into the given ancestor. This could\r\n    // simply be the container's direct parent.\r\n    Some(target_ancestor) => insert_into_ancestor(\r\n      &window_to_move,\r\n      &target_ancestor,\r\n      direction,\r\n      state,\r\n    ),\r\n  }\r\n}\r\n\r\n/// Gets the next sibling `TilingWindow` or `SplitContainer` in the given\r\n/// direction.\r\nfn tiling_sibling_in_direction(\r\n  window: &TilingWindow,\r\n  direction: &Direction,\r\n) -> Option<TilingContainer> {\r\n  match direction {\r\n    Direction::Up | Direction::Left => window\r\n      .prev_siblings()\r\n      .find_map(|sibling| sibling.as_tiling_container().ok()),\r\n    _ => window\r\n      .next_siblings()\r\n      .find_map(|sibling| sibling.as_tiling_container().ok()),\r\n  }\r\n}\r\n\r\nfn move_to_sibling_container(\r\n  window_to_move: TilingWindow,\r\n  target_sibling: TilingContainer,\r\n  direction: &Direction,\r\n  state: &mut WmState,\r\n) -> anyhow::Result<()> {\r\n  let parent = window_to_move.parent().context(\"No parent.\")?;\r\n\r\n  match target_sibling {\r\n    TilingContainer::TilingWindow(sibling_window) => {\r\n      // Swap the window with sibling in given direction.\r\n      move_container_within_tree(\r\n        &window_to_move.clone().into(),\r\n        &parent,\r\n        sibling_window.index(),\r\n        state,\r\n      )?;\r\n\r\n      state\r\n        .pending_sync\r\n        .queue_container_to_redraw(sibling_window)\r\n        .queue_container_to_redraw(window_to_move);\r\n    }\r\n    TilingContainer::Split(sibling_split) => {\r\n      let sibling_descendant =\r\n        sibling_split.descendant_in_direction(&direction.inverse());\r\n\r\n      // Move the window into the sibling split container.\r\n      if let Some(sibling_descendant) = sibling_descendant {\r\n        let target_parent = sibling_descendant\r\n          .direction_container()\r\n          .context(\"No direction container.\")?;\r\n\r\n        let has_matching_tiling_direction =\r\n          TilingDirection::from_direction(direction)\r\n            == target_parent.tiling_direction();\r\n\r\n        let target_index = match direction {\r\n          Direction::Down | Direction::Right\r\n            if has_matching_tiling_direction =>\r\n          {\r\n            sibling_descendant.index()\r\n          }\r\n          _ => sibling_descendant.index() + 1,\r\n        };\r\n\r\n        move_container_within_tree(\r\n          &window_to_move.into(),\r\n          &target_parent.clone().into(),\r\n          target_index,\r\n          state,\r\n        )?;\r\n\r\n        state\r\n          .pending_sync\r\n          .queue_container_to_redraw(target_parent)\r\n          .queue_containers_to_redraw(parent.tiling_children());\r\n      }\r\n    }\r\n  }\r\n\r\n  Ok(())\r\n}\r\n\r\nfn move_to_workspace_in_direction(\r\n  window_to_move: &WindowContainer,\r\n  direction: &Direction,\r\n  state: &mut WmState,\r\n) -> anyhow::Result<()> {\r\n  let parent = window_to_move.parent().context(\"No parent.\")?;\r\n  let workspace = window_to_move.workspace().context(\"No workspace.\")?;\r\n  let monitor = parent.monitor().context(\"No monitor.\")?;\r\n\r\n  let target_workspace = state\r\n    .monitor_in_direction(&monitor, direction)?\r\n    .and_then(|monitor| monitor.displayed_workspace());\r\n\r\n  if let Some(target_workspace) = target_workspace {\r\n    // Since the window is crossing monitors, adjustments might need to be\r\n    // made because of DPI.\r\n    if monitor.has_dpi_difference(&target_workspace.clone().into())? {\r\n      window_to_move.set_has_pending_dpi_adjustment(true);\r\n    }\r\n\r\n    // Update floating placement since the window has to cross monitors.\r\n    window_to_move.set_floating_placement(\r\n      window_to_move\r\n        .floating_placement()\r\n        .translate_to_center(&target_workspace.to_rect()?),\r\n    );\r\n\r\n    if let WindowContainer::NonTilingWindow(window_to_move) =\r\n      &window_to_move\r\n    {\r\n      window_to_move.set_insertion_target(None);\r\n    }\r\n\r\n    let target_index = match direction {\r\n      Direction::Down | Direction::Right => 0,\r\n      _ => target_workspace.child_count(),\r\n    };\r\n\r\n    // Focus should be reassigned within the original workspace after the\r\n    // window is moved out. For example, if the focus order is 1. tiling\r\n    // window and 2. fullscreen window, then we'd want to retain focus on a\r\n    // tiling window on move.\r\n    let focus_target = state.focus_target_after_removal(window_to_move);\r\n\r\n    move_container_within_tree(\r\n      &window_to_move.clone().into(),\r\n      &target_workspace.clone().into(),\r\n      target_index,\r\n      state,\r\n    )?;\r\n\r\n    if let Some(focus_target) = focus_target {\r\n      set_focused_descendant(\r\n        &focus_target,\r\n        Some(&workspace.clone().into()),\r\n      );\r\n    }\r\n\r\n    state\r\n      .pending_sync\r\n      .queue_container_to_redraw(window_to_move.clone())\r\n      .queue_containers_to_redraw(target_workspace.tiling_children())\r\n      .queue_containers_to_redraw(parent.tiling_children())\r\n      .queue_cursor_jump()\r\n      .queue_workspace_to_reorder(target_workspace);\r\n  }\r\n\r\n  Ok(())\r\n}\r\n\r\nfn invert_workspace_tiling_direction(\r\n  window_to_move: TilingWindow,\r\n  direction: &Direction,\r\n  state: &mut WmState,\r\n  config: &UserConfig,\r\n) -> anyhow::Result<()> {\r\n  let workspace = window_to_move.workspace().context(\"No workspace.\")?;\r\n\r\n  // Get top-level tiling children of the workspace.\r\n  let workspace_children = workspace\r\n    .tiling_children()\r\n    .filter(|container| container.id() != window_to_move.id())\r\n    .collect::<Vec<_>>();\r\n\r\n  // Create a new split container to wrap the window's siblings. For\r\n  // example, in the layout H[1 V[2 3]] where container 3 is moved down,\r\n  // we create a split container around 1 and 2. This results in\r\n  // H[H[1 V[2 3]]], and V[H[1 V[2]] 3] after the tiling direction change.\r\n  if workspace_children.len() > 1 {\r\n    let split_container = SplitContainer::new(\r\n      workspace.tiling_direction(),\r\n      config.value.gaps.clone(),\r\n    );\r\n\r\n    wrap_in_split_container(\r\n      &split_container,\r\n      &workspace.clone().into(),\r\n      &workspace_children,\r\n    )?;\r\n  }\r\n\r\n  // Invert the tiling direction of the workspace.\r\n  workspace.set_tiling_direction(workspace.tiling_direction().inverse());\r\n\r\n  let target_index = match direction {\r\n    Direction::Left | Direction::Up => 0,\r\n    _ => workspace.child_count(),\r\n  };\r\n\r\n  // Depending on the direction, place the window either before or after\r\n  // the split container.\r\n  move_container_within_tree(\r\n    &window_to_move.clone().into(),\r\n    &workspace.clone().into(),\r\n    target_index,\r\n    state,\r\n  )?;\r\n\r\n  // Workspace might have redundant split containers after the tiling\r\n  // direction change. For example, V[H[1 2] 3] where container 3 is moved\r\n  // up results in H[3 H[1 2]], and needs to be flattened to H[3 1 2].\r\n  flatten_child_split_containers(&workspace.clone().into())?;\r\n\r\n  // Resize the window such that the split container and window are each\r\n  // 0.5.\r\n  resize_tiling_container(&window_to_move.into(), 0.5);\r\n\r\n  state\r\n    .pending_sync\r\n    .queue_containers_to_redraw(workspace.tiling_children());\r\n\r\n  Ok(())\r\n}\r\n\r\nfn insert_into_ancestor(\r\n  window_to_move: &TilingWindow,\r\n  target_ancestor: &DirectionContainer,\r\n  direction: &Direction,\r\n  state: &mut WmState,\r\n) -> anyhow::Result<()> {\r\n  // Traverse upwards to find container whose parent is the target\r\n  // ancestor. Then, depending on the direction, insert before or after\r\n  // that container.\r\n  let window_ancestor = window_to_move\r\n    .ancestors()\r\n    .find(|container| {\r\n      container\r\n        .parent()\r\n        .is_some_and(|parent| parent == target_ancestor.clone().into())\r\n    })\r\n    .context(\"Window ancestor not found.\")?;\r\n\r\n  let target_index = match direction {\r\n    Direction::Up | Direction::Left => window_ancestor.index(),\r\n    _ => window_ancestor.index() + 1,\r\n  };\r\n\r\n  // Move the window into the container above.\r\n  move_container_within_tree(\r\n    &window_to_move.clone().into(),\r\n    &target_ancestor.clone().into(),\r\n    target_index,\r\n    state,\r\n  )?;\r\n\r\n  state\r\n    .pending_sync\r\n    .queue_containers_to_redraw(target_ancestor.tiling_children());\r\n\r\n  Ok(())\r\n}\r\n\r\nfn move_floating_window(\r\n  window_to_move: NonTilingWindow,\r\n  direction: &Direction,\r\n  state: &mut WmState,\r\n) -> anyhow::Result<()> {\r\n  let new_position =\r\n    new_floating_position(&window_to_move, direction, state)?;\r\n\r\n  if let Some((position_rect, target_monitor)) = new_position {\r\n    let monitor = window_to_move.monitor().context(\"No monitor.\")?;\r\n\r\n    // Mark window as needing DPI adjustment if it crosses monitors. The\r\n    // handler for `PlatformEvent::LocationChanged` will update the\r\n    // window's workspace if it goes out of bounds of its current\r\n    // workspace.\r\n    if monitor.id() != target_monitor.id()\r\n      && monitor.has_dpi_difference(&target_monitor.into())?\r\n    {\r\n      window_to_move.set_has_pending_dpi_adjustment(true);\r\n    }\r\n\r\n    window_to_move.set_floating_placement(position_rect);\r\n    state.pending_sync.queue_container_to_redraw(window_to_move);\r\n  }\r\n\r\n  Ok(())\r\n}\r\n\r\n/// Returns a tuple of the new floating position and the target monitor.\r\nfn new_floating_position(\r\n  window_to_move: &NonTilingWindow,\r\n  direction: &Direction,\r\n  state: &mut WmState,\r\n) -> anyhow::Result<Option<(Rect, Monitor)>> {\r\n  let monitor = window_to_move.monitor().context(\"No monitor.\")?;\r\n  let monitor_rect = monitor.native_properties().working_area;\r\n  let window_pos = window_to_move.native_properties().frame;\r\n\r\n  let is_on_monitor_edge = match direction {\r\n    Direction::Up => window_pos.top == monitor_rect.top,\r\n    Direction::Down => window_pos.bottom == monitor_rect.bottom,\r\n    Direction::Left => window_pos.left == monitor_rect.left,\r\n    Direction::Right => window_pos.right == monitor_rect.right,\r\n  };\r\n\r\n  // Window is on the edge of the monitor and should be moved to a\r\n  // different monitor in the given direction.\r\n  if is_on_monitor_edge {\r\n    let next_monitor = state.monitor_in_direction(&monitor, direction)?;\r\n\r\n    if let Some(next_monitor) = next_monitor {\r\n      let monitor_rect = next_monitor.native().working_area()?.clone();\r\n\r\n      let position = snap_to_monitor_edge(\r\n        &window_pos,\r\n        &monitor_rect,\r\n        &direction.inverse(),\r\n      )\r\n      .clamp(&monitor_rect);\r\n\r\n      return Ok(Some((position, next_monitor)));\r\n    }\r\n\r\n    return Ok(None);\r\n  }\r\n\r\n  let (monitor_length, window_length) = match direction {\r\n    Direction::Up | Direction::Down => {\r\n      (monitor_rect.height(), window_pos.height())\r\n    }\r\n    _ => (monitor_rect.width(), window_pos.width()),\r\n  };\r\n\r\n  let length_delta = monitor_length - window_length;\r\n\r\n  // Calculate the distance the window should move based on the ratio of\r\n  // the window's length to the monitor's length.\r\n  #[allow(clippy::cast_precision_loss)]\r\n  let move_distance = match window_length as f32 / monitor_length as f32 {\r\n    x if (0.0..0.2).contains(&x) => length_delta / 5,\r\n    x if (0.2..0.4).contains(&x) => length_delta / 4,\r\n    x if (0.4..0.6).contains(&x) => length_delta / 3,\r\n    _ => length_delta / 2,\r\n  };\r\n\r\n  // Snap the window to the current monitor's edge if it's within 15px of\r\n  // it after the move.\r\n  let should_snap_to_edge = match direction {\r\n    Direction::Up => {\r\n      window_pos.top - move_distance - SNAP_DISTANCE < monitor_rect.top\r\n    }\r\n    Direction::Down => {\r\n      window_pos.bottom + move_distance + SNAP_DISTANCE\r\n        > monitor_rect.bottom\r\n    }\r\n    Direction::Left => {\r\n      window_pos.left - move_distance - SNAP_DISTANCE < monitor_rect.left\r\n    }\r\n    Direction::Right => {\r\n      window_pos.right + move_distance + SNAP_DISTANCE > monitor_rect.right\r\n    }\r\n  };\r\n\r\n  if should_snap_to_edge {\r\n    let position =\r\n      snap_to_monitor_edge(&window_pos, &monitor_rect, direction);\r\n\r\n    return Ok(Some((position, monitor)));\r\n  }\r\n\r\n  // Snap the window to the current monitor's inverse edge if it's in\r\n  // between two monitors or outside the bounds of the current monitor.\r\n  let should_snap_to_inverse_edge = match direction {\r\n    Direction::Up => window_pos.bottom > monitor_rect.bottom,\r\n    Direction::Down => window_pos.top < monitor_rect.top,\r\n    Direction::Left => window_pos.right > monitor_rect.right,\r\n    Direction::Right => window_pos.left < monitor_rect.left,\r\n  };\r\n\r\n  let position = if should_snap_to_inverse_edge {\r\n    snap_to_monitor_edge(&window_pos, &monitor_rect, &direction.inverse())\r\n  } else {\r\n    window_pos.translate_in_direction(direction, move_distance)\r\n  };\r\n\r\n  Ok(Some((position, monitor)))\r\n}\r\n\r\nfn snap_to_monitor_edge(\r\n  window_pos: &Rect,\r\n  monitor_rect: &Rect,\r\n  edge: &Direction,\r\n) -> Rect {\r\n  let (x, y) = match edge {\r\n    Direction::Up => (window_pos.x(), monitor_rect.top),\r\n    Direction::Down => {\r\n      (window_pos.x(), monitor_rect.bottom - window_pos.height())\r\n    }\r\n    Direction::Left => (monitor_rect.left, window_pos.y()),\r\n    Direction::Right => {\r\n      (monitor_rect.right - window_pos.width(), window_pos.y())\r\n    }\r\n  };\r\n\r\n  window_pos.translate_to_coordinates(x, y)\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/commands/window/move_window_to_workspace.rs",
    "content": "use anyhow::Context;\r\nuse tracing::info;\r\nuse wm_common::WindowState;\r\n\r\nuse crate::{\r\n  commands::{\r\n    container::{move_container_within_tree, set_focused_descendant},\r\n    workspace::activate_workspace,\r\n  },\r\n  models::{WindowContainer, WorkspaceTarget},\r\n  traits::{CommonGetters, PositionGetters, WindowGetters},\r\n  user_config::UserConfig,\r\n  wm_state::WmState,\r\n};\r\n\r\npub fn move_window_to_workspace(\r\n  window: WindowContainer,\r\n  target: WorkspaceTarget,\r\n  state: &mut WmState,\r\n  config: &UserConfig,\r\n) -> anyhow::Result<()> {\r\n  let current_workspace = window.workspace().context(\"No workspace.\")?;\r\n  let current_monitor =\r\n    current_workspace.monitor().context(\"No monitor.\")?;\r\n\r\n  let (target_workspace_name, target_workspace) =\r\n    state.workspace_by_target(&current_workspace, target, config)?;\r\n\r\n  // Retrieve or activate the target workspace by its name.\r\n  let target_workspace = match target_workspace {\r\n    Some(_) => anyhow::Ok(target_workspace),\r\n    _ => match target_workspace_name {\r\n      Some(name) => {\r\n        activate_workspace(Some(&name), None, state, config)?;\r\n\r\n        Ok(state.workspace_by_name(&name))\r\n      }\r\n      _ => Ok(None),\r\n    },\r\n  }?;\r\n\r\n  if let Some(target_workspace) = target_workspace {\r\n    if target_workspace.id() == current_workspace.id() {\r\n      return Ok(());\r\n    }\r\n\r\n    info!(\r\n      \"Moving window to workspace: '{}'.\",\r\n      target_workspace.config().name\r\n    );\r\n\r\n    let target_monitor =\r\n      target_workspace.monitor().context(\"No monitor.\")?;\r\n\r\n    // Since target workspace could be on a different monitor, adjustments\r\n    // might need to be made because of DPI.\r\n    if current_monitor\r\n      .has_dpi_difference(&target_monitor.clone().into())?\r\n    {\r\n      window.set_has_pending_dpi_adjustment(true);\r\n    }\r\n\r\n    // Update floating placement if the window has to cross monitors.\r\n    if target_monitor.id() != current_monitor.id() {\r\n      window.set_floating_placement(\r\n        window\r\n          .floating_placement()\r\n          .translate_to_center(&target_workspace.to_rect()?),\r\n      );\r\n    }\r\n\r\n    if let WindowContainer::NonTilingWindow(window) = &window {\r\n      window.set_insertion_target(None);\r\n    }\r\n\r\n    // Focus target is `None` if the window is not focused.\r\n    let focus_target = state.focus_target_after_removal(&window);\r\n\r\n    let focus_reset_target = if target_workspace.is_displayed() {\r\n      None\r\n    } else {\r\n      target_monitor.descendant_focus_order().next()\r\n    };\r\n\r\n    let insertion_sibling = target_workspace\r\n      .descendant_focus_order()\r\n      .filter_map(|descendant| descendant.as_window_container().ok())\r\n      .find(|descendant| descendant.state() == WindowState::Tiling);\r\n\r\n    // Insert the window into the target workspace.\r\n    match (window.is_tiling_window(), insertion_sibling.is_some()) {\r\n      (true, true) => {\r\n        if let Some(insertion_sibling) = insertion_sibling {\r\n          move_container_within_tree(\r\n            &window.clone().into(),\r\n            &insertion_sibling.clone().parent().context(\"No parent.\")?,\r\n            insertion_sibling.index() + 1,\r\n            state,\r\n          )?;\r\n        }\r\n      }\r\n      _ => {\r\n        move_container_within_tree(\r\n          &window.clone().into(),\r\n          &target_workspace.clone().into(),\r\n          target_workspace.child_count(),\r\n          state,\r\n        )?;\r\n      }\r\n    }\r\n\r\n    // When moving a focused window within the tree to another workspace,\r\n    // the target workspace will get displayed. If moving the window e.g.\r\n    // from monitor 1 -> 2, and the target workspace is hidden on that\r\n    // monitor, we want to reset focus to the workspace that was displayed\r\n    // on that monitor.\r\n    if let Some(focus_reset_target) = focus_reset_target {\r\n      set_focused_descendant(\r\n        &focus_reset_target,\r\n        Some(&target_monitor.into()),\r\n      );\r\n    }\r\n\r\n    // Retain focus within the workspace from where the window was moved.\r\n    if let Some(focus_target) = focus_target {\r\n      set_focused_descendant(&focus_target, None);\r\n      state.pending_sync.queue_focus_change();\r\n    }\r\n\r\n    match window {\r\n      WindowContainer::NonTilingWindow(_) => {\r\n        state.pending_sync.queue_container_to_redraw(window);\r\n      }\r\n      WindowContainer::TilingWindow(_) => {\r\n        state\r\n          .pending_sync\r\n          .queue_containers_to_redraw(current_workspace.tiling_children())\r\n          .queue_containers_to_redraw(target_workspace.tiling_children());\r\n      }\r\n    }\r\n\r\n    state\r\n      .pending_sync\r\n      .queue_workspace_to_reorder(target_workspace);\r\n  }\r\n\r\n  Ok(())\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/commands/window/resize_window.rs",
    "content": "use wm_platform::LengthValue;\r\n\r\nuse super::set_window_size;\r\nuse crate::{\r\n  models::WindowContainer,\r\n  traits::{CommonGetters, PositionGetters, TilingSizeGetters},\r\n  wm_state::WmState,\r\n};\r\n\r\npub fn resize_window(\r\n  window: &WindowContainer,\r\n  width_delta: Option<LengthValue>,\r\n  height_delta: Option<LengthValue>,\r\n  state: &mut WmState,\r\n) -> anyhow::Result<()> {\r\n  let window_rect = window.to_rect()?;\r\n\r\n  let target_width = match width_delta {\r\n    Some(delta) => {\r\n      let parent_width = match window.as_tiling_container() {\r\n        Ok(tiling_window) => tiling_window\r\n          .container_to_resize(true)?\r\n          .and_then(|container| container.parent())\r\n          .and_then(|parent| {\r\n            parent.to_rect().ok().map(|rect| rect.width())\r\n          })\r\n          .and_then(|parent_width| {\r\n            let (horizontal_gap, _) = tiling_window.inner_gaps().ok()?;\r\n\r\n            #[allow(\r\n              clippy::cast_possible_wrap,\r\n              clippy::cast_possible_truncation\r\n            )]\r\n            Some(\r\n              parent_width\r\n                - horizontal_gap\r\n                  * tiling_window.tiling_siblings().count() as i32,\r\n            )\r\n          }),\r\n        _ => window.parent().and_then(|parent| {\r\n          parent.to_rect().ok().map(|rect| rect.width())\r\n        }),\r\n      };\r\n\r\n      parent_width.map(|parent_width| {\r\n        window_rect.width() + delta.to_px(parent_width, None)\r\n      })\r\n    }\r\n    _ => None,\r\n  };\r\n\r\n  let target_height = match height_delta {\r\n    Some(delta) => {\r\n      let parent_height = match window.as_tiling_container() {\r\n        Ok(tiling_window) => tiling_window\r\n          .container_to_resize(false)?\r\n          .and_then(|container| container.parent())\r\n          .and_then(|parent| {\r\n            parent.to_rect().ok().map(|rect| rect.height())\r\n          })\r\n          .and_then(|parent_height| {\r\n            let (_, vertical_gap) = tiling_window.inner_gaps().ok()?;\r\n\r\n            #[allow(\r\n              clippy::cast_possible_wrap,\r\n              clippy::cast_possible_truncation\r\n            )]\r\n            Some(\r\n              parent_height\r\n                - vertical_gap\r\n                  * tiling_window.tiling_siblings().count() as i32,\r\n            )\r\n          }),\r\n        _ => window.parent().and_then(|parent| {\r\n          parent.to_rect().ok().map(|rect| rect.height())\r\n        }),\r\n      };\r\n\r\n      parent_height.map(|parent_height| {\r\n        window_rect.height() + delta.to_px(parent_height, None)\r\n      })\r\n    }\r\n    _ => None,\r\n  };\r\n\r\n  set_window_size(\r\n    window.clone(),\r\n    target_width.map(LengthValue::from_px),\r\n    target_height.map(LengthValue::from_px),\r\n    state,\r\n  )?;\r\n\r\n  Ok(())\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/commands/window/run_window_rules.rs",
    "content": "use tracing::info;\r\nuse wm_common::WindowRuleEvent;\r\n\r\nuse crate::{\r\n  models::WindowContainer,\r\n  traits::{CommonGetters, WindowGetters},\r\n  user_config::UserConfig,\r\n  wm::WindowManager,\r\n  wm_state::WmState,\r\n};\r\n\r\n/// Returns the window (if it's still attached) after running the window\r\n/// rules.\r\npub fn run_window_rules(\r\n  window: WindowContainer,\r\n  event_type: &WindowRuleEvent,\r\n  state: &mut WmState,\r\n  config: &mut UserConfig,\r\n) -> anyhow::Result<Option<WindowContainer>> {\r\n  let pending_window_rules =\r\n    config.pending_window_rules(&window, event_type);\r\n\r\n  let mut subject_window = window;\r\n\r\n  for rule in pending_window_rules {\r\n    info!(\"Running window rule with commands: {:?}.\", rule.commands);\r\n\r\n    for command in &rule.commands {\r\n      WindowManager::run_command(\r\n        command,\r\n        subject_window.clone().into(),\r\n        state,\r\n        config,\r\n      )?;\r\n\r\n      // Update the subject container in case the container type changes.\r\n      // For example, when going from a tiling to a floating window.\r\n      subject_window = if subject_window.is_detached() {\r\n        match state.window_from_native(&subject_window.native()) {\r\n          Some(window) => window,\r\n          None => return Ok(None),\r\n        }\r\n      } else {\r\n        subject_window\r\n      }\r\n    }\r\n\r\n    // Add the window rule as done.\r\n    if rule.run_once {\r\n      let window_rules = subject_window\r\n        .done_window_rules()\r\n        .into_iter()\r\n        .chain(std::iter::once(rule));\r\n\r\n      subject_window.set_done_window_rules(window_rules.collect());\r\n    }\r\n  }\r\n\r\n  Ok(Some(subject_window))\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/commands/window/set_window_position.rs",
    "content": "use anyhow::Context;\r\nuse wm_common::WindowState;\r\nuse wm_platform::Rect;\r\n\r\nuse crate::{\r\n  models::WindowContainer,\r\n  traits::{CommonGetters, PositionGetters, WindowGetters},\r\n  wm_state::WmState,\r\n};\r\n\r\npub enum WindowPositionTarget {\r\n  Centered,\r\n  Coordinates(Option<i32>, Option<i32>),\r\n}\r\n\r\npub fn set_window_position(\r\n  window: WindowContainer,\r\n  target: &WindowPositionTarget,\r\n  state: &mut WmState,\r\n) -> anyhow::Result<()> {\r\n  if matches!(window.state(), WindowState::Floating(_)) {\r\n    let placement = window.floating_placement();\r\n\r\n    let new_placement = match target {\r\n      WindowPositionTarget::Centered => placement.translate_to_center(\r\n        &window.workspace().context(\"No workspace.\")?.to_rect()?,\r\n      ),\r\n      WindowPositionTarget::Coordinates(target_x, target_y) => {\r\n        Rect::from_xy(\r\n          target_x.unwrap_or(placement.x()),\r\n          target_y.unwrap_or(placement.y()),\r\n          placement.width(),\r\n          placement.height(),\r\n        )\r\n      }\r\n    };\r\n\r\n    window.set_floating_placement(new_placement);\r\n\r\n    // TODO: `has_custom_floating_placement` should be marked `true` if\r\n    // manually positioned to be centered (e.g. via `position --centered`).\r\n    let is_centered = matches!(target, WindowPositionTarget::Centered);\r\n    window.set_has_custom_floating_placement(!is_centered);\r\n\r\n    state.pending_sync.queue_container_to_redraw(window);\r\n  }\r\n\r\n  Ok(())\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/commands/window/set_window_size.rs",
    "content": "use anyhow::Context;\r\nuse wm_common::WindowState;\r\nuse wm_platform::{LengthValue, Rect};\r\n\r\nuse crate::{\r\n  commands::container::resize_tiling_container,\r\n  models::{NonTilingWindow, TilingWindow, WindowContainer},\r\n  traits::{\r\n    CommonGetters, PositionGetters, TilingSizeGetters, WindowGetters,\r\n  },\r\n  wm_state::WmState,\r\n};\r\n\r\n/// Arbitrary defaults for minimum floating window dimensions.\r\nconst MIN_FLOATING_WIDTH: i32 = 250;\r\nconst MIN_FLOATING_HEIGHT: i32 = 140;\r\n\r\npub fn set_window_size(\r\n  window: WindowContainer,\r\n  target_width: Option<LengthValue>,\r\n  target_height: Option<LengthValue>,\r\n  state: &mut WmState,\r\n) -> anyhow::Result<()> {\r\n  match window {\r\n    WindowContainer::TilingWindow(window) => {\r\n      set_tiling_window_size(&window, target_width, target_height, state)?;\r\n    }\r\n    WindowContainer::NonTilingWindow(window) => {\r\n      if matches!(window.state(), WindowState::Floating(_)) {\r\n        set_floating_window_size(\r\n          &window,\r\n          target_width,\r\n          target_height,\r\n          state,\r\n        )?;\r\n      }\r\n    }\r\n  }\r\n\r\n  Ok(())\r\n}\r\n\r\nfn set_tiling_window_size(\r\n  window: &TilingWindow,\r\n  target_width: Option<LengthValue>,\r\n  target_height: Option<LengthValue>,\r\n  state: &mut WmState,\r\n) -> anyhow::Result<()> {\r\n  if let Some(target_width) = target_width {\r\n    set_tiling_window_length(window, &target_width, true, state)?;\r\n  }\r\n\r\n  if let Some(target_height) = target_height {\r\n    set_tiling_window_length(window, &target_height, false, state)?;\r\n  }\r\n\r\n  Ok(())\r\n}\r\n\r\n/// Updates either the width or height of a tiling window.\r\nfn set_tiling_window_length(\r\n  window: &TilingWindow,\r\n  target_length: &LengthValue,\r\n  is_width_resize: bool,\r\n  state: &mut WmState,\r\n) -> anyhow::Result<()> {\r\n  // When resizing a tiling window, the container to resize can actually be\r\n  // an ancestor split container.\r\n  let container_to_resize = window.container_to_resize(is_width_resize)?;\r\n\r\n  if let Some(container_to_resize) = container_to_resize {\r\n    let parent = container_to_resize.parent().context(\"No parent.\")?;\r\n    let (horizontal_gap, vertical_gap) =\r\n      container_to_resize.inner_gaps()?;\r\n\r\n    #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]\r\n    let parent_length = if is_width_resize {\r\n      parent.to_rect()?.width()\r\n        - horizontal_gap * window.tiling_siblings().count() as i32\r\n    } else {\r\n      parent.to_rect()?.height()\r\n        - vertical_gap * window.tiling_siblings().count() as i32\r\n    };\r\n\r\n    // Convert the target length to a tiling size.\r\n    let tiling_size = target_length.to_percentage(parent_length);\r\n\r\n    // Skip the resize if the window is already at the target size.\r\n    if container_to_resize.tiling_size() - tiling_size != 0. {\r\n      resize_tiling_container(&container_to_resize, tiling_size);\r\n\r\n      state\r\n        .pending_sync\r\n        .queue_containers_to_redraw(parent.tiling_children());\r\n    }\r\n  }\r\n\r\n  Ok(())\r\n}\r\n\r\nfn set_floating_window_size(\r\n  window: &NonTilingWindow,\r\n  target_width: Option<LengthValue>,\r\n  target_height: Option<LengthValue>,\r\n  state: &mut WmState,\r\n) -> anyhow::Result<()> {\r\n  let monitor = window.monitor().context(\"No monitor\")?;\r\n  let monitor_rect = monitor.to_rect()?;\r\n  let window_rect = window.to_rect()?;\r\n\r\n  // Prevent resize from making the window smaller than minimum dimensions.\r\n  // Always allow the size to be increased, even if the window would still\r\n  // be within minimum dimension values.\r\n  let length_with_clamp =\r\n    |target_length: Option<i32>, current_length, min_length| {\r\n      target_length.map_or(current_length, |target_length| {\r\n        if target_length >= current_length {\r\n          target_length\r\n        } else {\r\n          target_length.max(min_length)\r\n        }\r\n      })\r\n    };\r\n\r\n  let target_width_px = target_width\r\n    .map(|target_width| target_width.to_px(monitor_rect.width(), None));\r\n\r\n  let new_width = length_with_clamp(\r\n    target_width_px,\r\n    window_rect.width(),\r\n    MIN_FLOATING_WIDTH,\r\n  );\r\n\r\n  let target_height_px = target_height\r\n    .map(|target_height| target_height.to_px(monitor_rect.height(), None));\r\n\r\n  let new_height = length_with_clamp(\r\n    target_height_px,\r\n    window_rect.height(),\r\n    MIN_FLOATING_HEIGHT,\r\n  );\r\n\r\n  window.set_floating_placement(Rect::from_xy(\r\n    window.floating_placement().x(),\r\n    window.floating_placement().y(),\r\n    new_width,\r\n    new_height,\r\n  ));\r\n\r\n  state.pending_sync.queue_container_to_redraw(window.clone());\r\n\r\n  Ok(())\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/commands/window/unmanage_window.rs",
    "content": "use anyhow::Context;\r\nuse wm_common::{WindowState, WmEvent};\r\n\r\nuse crate::{\r\n  commands::container::{\r\n    detach_container, flatten_child_split_containers,\r\n    set_focused_descendant,\r\n  },\r\n  models::WindowContainer,\r\n  traits::{CommonGetters, WindowGetters},\r\n  wm_state::WmState,\r\n};\r\n\r\n#[allow(clippy::needless_pass_by_value)]\r\npub fn unmanage_window(\r\n  window: WindowContainer,\r\n  state: &mut WmState,\r\n) -> anyhow::Result<()> {\r\n  // Create iterator of parent, grandparent, and great-grandparent.\r\n  let ancestors = window.ancestors().take(3).collect::<Vec<_>>();\r\n\r\n  // Get container to switch focus to after the window has been removed.\r\n  let focus_target = state.focus_target_after_removal(&window.clone());\r\n\r\n  detach_container(window.clone().into())?;\r\n\r\n  // After detaching the container, flatten any redundant split containers.\r\n  // For example, in the layout V[1 H[2]] where container 1 is detached to\r\n  // become V[H[2]], this will then need to be flattened to V[2].\r\n  for ancestor in ancestors.iter().rev() {\r\n    flatten_child_split_containers(ancestor)?;\r\n  }\r\n\r\n  state.emit_event(WmEvent::WindowUnmanaged {\r\n    unmanaged_id: window.id(),\r\n    #[allow(clippy::cast_possible_wrap, clippy::unnecessary_cast)]\r\n    unmanaged_handle: window.native().id().0 as isize,\r\n  });\r\n\r\n  // Reassign focus to suitable target.\r\n  if let Some(focus_target) = focus_target {\r\n    set_focused_descendant(&focus_target, None);\r\n    state.pending_sync.queue_focus_change();\r\n    state.unmanaged_or_minimized_timestamp =\r\n      Some(std::time::Instant::now());\r\n  }\r\n\r\n  // Sibling containers need to be redrawn if the window was tiling.\r\n  if window.state() == WindowState::Tiling {\r\n    let ancestor_to_redraw = ancestors\r\n      .into_iter()\r\n      .find(|ancestor| !ancestor.is_detached())\r\n      .context(\"No ancestor to redraw.\")?;\r\n\r\n    state\r\n      .pending_sync\r\n      .queue_containers_to_redraw(ancestor_to_redraw.tiling_children());\r\n  }\r\n\r\n  Ok(())\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/commands/window/update_window_state.rs",
    "content": "use anyhow::Context;\r\nuse tracing::{info, warn};\r\nuse wm_common::WindowState;\r\n\r\nuse crate::{\r\n  commands::container::{\r\n    move_container_within_tree, replace_container, resize_tiling_container,\r\n  },\r\n  models::{Container, InsertionTarget, WindowContainer},\r\n  traits::{CommonGetters, TilingSizeGetters, WindowGetters},\r\n  user_config::UserConfig,\r\n  wm_state::WmState,\r\n};\r\n\r\n/// Updates the state of a window.\r\n///\r\n/// Adds the window for redraw if there is a state change.\r\n///\r\n/// Returns the window after the state change.\r\npub fn update_window_state(\r\n  window: WindowContainer,\r\n  target_state: WindowState,\r\n  state: &mut WmState,\r\n  config: &UserConfig,\r\n) -> anyhow::Result<WindowContainer> {\r\n  if window.state() == target_state {\r\n    return Ok(window);\r\n  }\r\n\r\n  info!(\"Updating window state: {:?}.\", target_state);\r\n\r\n  match target_state {\r\n    WindowState::Tiling => set_tiling(&window, state, config),\r\n    _ => set_non_tiling(window, target_state, state),\r\n  }\r\n}\r\n\r\n/// Updates the state of a window to be `WindowState::Tiling`.\r\nfn set_tiling(\r\n  window: &WindowContainer,\r\n  state: &mut WmState,\r\n  config: &UserConfig,\r\n) -> anyhow::Result<WindowContainer> {\r\n  let window = window\r\n    .as_non_tiling_window()\r\n    .context(\"Invalid window state.\")?\r\n    .clone();\r\n\r\n  let workspace =\r\n    window.workspace().context(\"Window has no workspace.\")?;\r\n\r\n  // Check whether insertion target is still valid.\r\n  let insertion_target =\r\n    window.insertion_target().filter(|insertion_target| {\r\n      insertion_target\r\n        .target_parent\r\n        .workspace()\r\n        .is_some_and(|workspace| workspace.is_displayed())\r\n    });\r\n\r\n  // Get the position in the tree to insert the new tiling window. This\r\n  // will be the window's previous tiling position if it has one, or\r\n  // instead beside the last focused tiling window in the workspace.\r\n  let (target_parent, target_index) = insertion_target\r\n    .as_ref()\r\n    .map(|insertion_target| {\r\n      (\r\n        insertion_target.target_parent.clone(),\r\n        insertion_target.target_index,\r\n      )\r\n    })\r\n    // Fallback to the last focused tiling window within the workspace.\r\n    .or_else(|| {\r\n      let focused_window = workspace\r\n        .descendant_focus_order()\r\n        .find(Container::is_tiling_window)?;\r\n\r\n      Some((focused_window.parent()?, focused_window.index() + 1))\r\n    })\r\n    // Default to inserting at the end of the workspace.\r\n    .unwrap_or((workspace.clone().into(), workspace.child_count()));\r\n\r\n  let tiling_window = window.to_tiling(config.value.gaps.clone());\r\n\r\n  // Replace the original window with the created tiling window.\r\n  replace_container(\r\n    &tiling_window.clone().into(),\r\n    &window.parent().context(\"No parent.\")?,\r\n    window.index(),\r\n  )?;\r\n\r\n  move_container_within_tree(\r\n    &tiling_window.clone().into(),\r\n    &target_parent,\r\n    target_index,\r\n    state,\r\n  )?;\r\n\r\n  #[allow(clippy::cast_precision_loss)]\r\n  if let Some(insertion_target) = &insertion_target {\r\n    let size_scale = (insertion_target.prev_sibling_count + 1) as f32\r\n      / (tiling_window.tiling_siblings().count() + 1) as f32;\r\n\r\n    // Scale the window's previous size based on the current number of\r\n    // siblings. E.g. if the window was 0.5 with 1 sibling, and now has 2\r\n    // siblings, scale to 0.5 * (2/3) to maintain proportional sizing.\r\n    let target_size = insertion_target.prev_tiling_size * size_scale;\r\n    resize_tiling_container(&tiling_window.clone().into(), target_size);\r\n  }\r\n\r\n  state\r\n    .pending_sync\r\n    .queue_containers_to_redraw(target_parent.tiling_children())\r\n    .queue_workspace_to_reorder(workspace);\r\n\r\n  Ok(tiling_window.into())\r\n}\r\n\r\n/// Updates the state of a window to be either `WindowState::Floating`,\r\n/// `WindowState::Fullscreen`, or `WindowState::Minimized`.\r\nfn set_non_tiling(\r\n  window: WindowContainer,\r\n  target_state: WindowState,\r\n  state: &mut WmState,\r\n) -> anyhow::Result<WindowContainer> {\r\n  // A window can only be updated to a minimized state if it is\r\n  // natively minimized.\r\n  // TODO: Consider doing the same for maximized and fullscreen states.\r\n  if target_state == WindowState::Minimized\r\n    && !window.native_properties().is_minimized\r\n  {\r\n    info!(\"No window state update. Minimizing window.\");\r\n\r\n    // TODO: Instead of doing the platform call directly here, instead add\r\n    // a `queue_state_change` method to `PendingSync`.\r\n    if let Err(err) = window.native().minimize() {\r\n      warn!(\"Failed to minimize window: {}\", err);\r\n    }\r\n\r\n    return Ok(window);\r\n  }\r\n\r\n  let workspace = window.workspace().context(\"No workspace.\")?;\r\n\r\n  match window {\r\n    WindowContainer::NonTilingWindow(window) => {\r\n      let current_state = window.state();\r\n\r\n      // Update the window's previous state if the discriminant changes.\r\n      // TODO: Move out handling of active drag. Can then simplify calls to\r\n      // `set_active_drag` in `handle_window_moved_or_resized_end`.\r\n      if !current_state.is_same_state(&target_state)\r\n        && window.active_drag().is_none()\r\n      {\r\n        window.set_prev_state(current_state);\r\n        state.pending_sync.queue_workspace_to_reorder(workspace);\r\n      }\r\n\r\n      window.set_state(target_state);\r\n      state.pending_sync.queue_container_to_redraw(window.clone());\r\n\r\n      Ok(window.into())\r\n    }\r\n    WindowContainer::TilingWindow(window) => {\r\n      let parent = window.parent().context(\"No parent\")?;\r\n\r\n      let non_tiling_window = window.to_non_tiling(\r\n        target_state.clone(),\r\n        Some(InsertionTarget {\r\n          target_parent: parent.clone(),\r\n          target_index: window.index(),\r\n          prev_tiling_size: window.tiling_size(),\r\n          prev_sibling_count: window.tiling_siblings().count(),\r\n        }),\r\n      );\r\n\r\n      // Non-tiling windows should always be direct children of the\r\n      // workspace.\r\n      if parent != workspace.clone().into() {\r\n        move_container_within_tree(\r\n          &window.clone().into(),\r\n          &workspace.clone().into(),\r\n          workspace.child_count(),\r\n          state,\r\n        )?;\r\n      }\r\n\r\n      replace_container(\r\n        &non_tiling_window.clone().into(),\r\n        &workspace.clone().into(),\r\n        window.index(),\r\n      )?;\r\n\r\n      state\r\n        .pending_sync\r\n        .queue_container_to_redraw(non_tiling_window.clone())\r\n        .queue_containers_to_redraw(workspace.tiling_children())\r\n        .queue_workspace_to_reorder(workspace);\r\n\r\n      Ok(non_tiling_window.into())\r\n    }\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/commands/workspace/activate_workspace.rs",
    "content": "use anyhow::Context;\r\nuse tracing::info;\r\nuse wm_common::{TilingDirection, WmEvent, WorkspaceConfig};\r\n\r\nuse super::sort_workspaces;\r\nuse crate::{\r\n  commands::container::attach_container,\r\n  models::{Monitor, Workspace},\r\n  traits::{CommonGetters, PositionGetters},\r\n  user_config::UserConfig,\r\n  wm_state::WmState,\r\n};\r\n\r\n/// Activates a workspace on the target monitor.\r\n///\r\n/// If no workspace name is provided, the first suitable workspace defined\r\n/// in the user's config will be used.\r\n///\r\n/// If no target monitor is provided, the workspace is activated on\r\n/// whichever monitor it is bound to, or the currently focused monitor.\r\npub fn activate_workspace(\r\n  workspace_name: Option<&str>,\r\n  target_monitor: Option<Monitor>,\r\n  state: &mut WmState,\r\n  config: &UserConfig,\r\n) -> anyhow::Result<()> {\r\n  let workspace_config = workspace_config(\r\n    workspace_name,\r\n    target_monitor.clone(),\r\n    state,\r\n    config,\r\n  )?;\r\n\r\n  let target_monitor = target_monitor\r\n    .or_else(|| {\r\n      workspace_config\r\n        .bind_to_monitor\r\n        .and_then(|index| {\r\n          state\r\n            .monitors()\r\n            .into_iter()\r\n            .find(|monitor| monitor.index() == index as usize)\r\n        })\r\n        .or_else(|| {\r\n          state\r\n            .focused_container()\r\n            .and_then(|focused| focused.monitor())\r\n        })\r\n    })\r\n    .context(\"Failed to get a target monitor for the workspace.\")?;\r\n\r\n  let monitor_rect = target_monitor.to_rect()?;\r\n\r\n  let tiling_direction = if monitor_rect.height() > monitor_rect.width() {\r\n    TilingDirection::Vertical\r\n  } else {\r\n    TilingDirection::Horizontal\r\n  };\r\n\r\n  let workspace = Workspace::new(\r\n    workspace_config.clone(),\r\n    config.value.gaps.clone(),\r\n    tiling_direction,\r\n  );\r\n\r\n  // Attach the created workspace to the specified monitor.\r\n  attach_container(\r\n    &workspace.clone().into(),\r\n    &target_monitor.clone().into(),\r\n    None,\r\n  )?;\r\n\r\n  sort_workspaces(&target_monitor, config)?;\r\n\r\n  info!(\"Activating workspace: {workspace}\");\r\n\r\n  state.emit_event(WmEvent::WorkspaceActivated {\r\n    activated_workspace: workspace.to_dto()?,\r\n  });\r\n\r\n  Ok(())\r\n}\r\n\r\n/// Gets config for the workspace to activate.\r\nfn workspace_config(\r\n  workspace_name: Option<&str>,\r\n  target_monitor: Option<Monitor>,\r\n  state: &mut WmState,\r\n  config: &UserConfig,\r\n) -> anyhow::Result<WorkspaceConfig> {\r\n  let found_config = match workspace_name {\r\n    Some(workspace_name) => config\r\n      .inactive_workspace_configs(&state.workspaces())\r\n      .into_iter()\r\n      .find(|config| config.name == workspace_name)\r\n      .with_context(|| {\r\n        format!(\r\n          \"Workspace with name '{workspace_name}' doesn't exist or is already active.\"\r\n        )\r\n      }),\r\n    None => target_monitor\r\n      .and_then(|target_monitor| {\r\n        config.workspace_config_for_monitor(\r\n          &target_monitor,\r\n          &state.workspaces(),\r\n        )\r\n      })\r\n      .or_else(|| {\r\n        config.next_inactive_workspace_config(&state.workspaces())\r\n      })\r\n      .context(\"No workspace config available to activate workspace.\"),\r\n  };\r\n\r\n  found_config.cloned()\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/commands/workspace/deactivate_workspace.rs",
    "content": "use tracing::info;\r\nuse wm_common::WmEvent;\r\n\r\nuse crate::{\r\n  commands::container::detach_container, models::Workspace,\r\n  traits::CommonGetters, wm_state::WmState,\r\n};\r\n\r\n/// Deactivates a given workspace. This removes the container from its\r\n/// parent monitor and emits a `WorkspaceDeactivated` event.\r\n#[allow(clippy::needless_pass_by_value)]\r\npub fn deactivate_workspace(\r\n  workspace: Workspace,\r\n  state: &WmState,\r\n) -> anyhow::Result<()> {\r\n  info!(\"Deactivating workspace: {workspace}\");\r\n\r\n  detach_container(workspace.clone().into())?;\r\n\r\n  state.emit_event(WmEvent::WorkspaceDeactivated {\r\n    deactivated_id: workspace.id(),\r\n    deactivated_name: workspace.config().name,\r\n  });\r\n\r\n  Ok(())\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/commands/workspace/focus_workspace.rs",
    "content": "use anyhow::Context;\r\nuse tracing::info;\r\n\r\nuse super::activate_workspace;\r\nuse crate::{\r\n  commands::{\r\n    container::set_focused_descendant, workspace::deactivate_workspace,\r\n  },\r\n  models::WorkspaceTarget,\r\n  traits::CommonGetters,\r\n  user_config::UserConfig,\r\n  wm_state::WmState,\r\n};\r\n\r\n/// Focuses a workspace by a given target.\r\n///\r\n/// This target can be a workspace name, the most recently focused\r\n/// workspace, the next workspace, the previous workspace, or the workspace\r\n/// in a given direction from the currently focused workspace.\r\n///\r\n/// The workspace will be activated if it isn't already active.\r\npub fn focus_workspace(\r\n  target: WorkspaceTarget,\r\n  state: &mut WmState,\r\n  config: &UserConfig,\r\n) -> anyhow::Result<()> {\r\n  let focused_workspace = state\r\n    .focused_container()\r\n    .and_then(|focused| focused.workspace())\r\n    .context(\"No workspace is currently focused.\")?;\r\n\r\n  let (target_workspace_name, target_workspace) =\r\n    state.workspace_by_target(&focused_workspace, target, config)?;\r\n\r\n  // Retrieve or activate the target workspace by its name.\r\n  let target_workspace = match target_workspace {\r\n    Some(_) => anyhow::Ok(target_workspace),\r\n    _ => match target_workspace_name {\r\n      Some(name) => {\r\n        activate_workspace(Some(&name), None, state, config)?;\r\n\r\n        Ok(state.workspace_by_name(&name))\r\n      }\r\n      _ => Ok(None),\r\n    },\r\n  }?;\r\n\r\n  if let Some(target_workspace) = target_workspace {\r\n    info!(\"Focusing workspace: {target_workspace}\");\r\n\r\n    // Get the currently displayed workspace on the same monitor that the\r\n    // workspace to focus is on.\r\n    let displayed_workspace = target_workspace\r\n      .monitor()\r\n      .and_then(|monitor| monitor.displayed_workspace())\r\n      .context(\"No workspace is currently displayed.\")?;\r\n\r\n    // Set focus to whichever window last had focus in workspace. If the\r\n    // workspace has no windows, then set focus to the workspace itself.\r\n    let container_to_focus = target_workspace\r\n      .descendant_focus_order()\r\n      .next()\r\n      .unwrap_or_else(|| target_workspace.clone().into());\r\n\r\n    set_focused_descendant(&container_to_focus, None);\r\n    state.pending_sync.queue_focus_change();\r\n\r\n    // Display the workspace to switch focus to.\r\n    state\r\n      .pending_sync\r\n      .queue_container_to_redraw(displayed_workspace)\r\n      .queue_container_to_redraw(target_workspace);\r\n\r\n    // Get empty workspace to destroy (if one is found). Cannot destroy\r\n    // empty workspaces if they're the only workspace on the monitor.\r\n    let workspace_to_destroy =\r\n      state.workspaces().into_iter().find(|workspace| {\r\n        !workspace.config().keep_alive\r\n          && !workspace.has_children()\r\n          && !workspace.is_displayed()\r\n      });\r\n\r\n    if let Some(workspace) = workspace_to_destroy {\r\n      deactivate_workspace(workspace, state)?;\r\n    }\r\n\r\n    // Save the currently focused workspace as recent.\r\n    state.recent_workspace_name = Some(focused_workspace.config().name);\r\n    state.pending_sync.queue_cursor_jump();\r\n  }\r\n\r\n  Ok(())\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/commands/workspace/mod.rs",
    "content": "mod activate_workspace;\r\nmod deactivate_workspace;\r\nmod focus_workspace;\r\nmod move_workspace_in_direction;\r\nmod sort_workspaces;\r\nmod update_workspace_config;\r\n\r\npub use activate_workspace::*;\r\npub use deactivate_workspace::*;\r\npub use focus_workspace::*;\r\npub use move_workspace_in_direction::*;\r\npub use sort_workspaces::*;\r\npub use update_workspace_config::*;\r\n"
  },
  {
    "path": "packages/wm/src/commands/workspace/move_workspace_in_direction.rs",
    "content": "use anyhow::Context;\r\nuse wm_common::WmEvent;\r\nuse wm_platform::Direction;\r\n\r\nuse super::{activate_workspace, deactivate_workspace, sort_workspaces};\r\nuse crate::{\r\n  commands::container::move_container_within_tree,\r\n  models::Workspace,\r\n  traits::{CommonGetters, PositionGetters, WindowGetters},\r\n  user_config::UserConfig,\r\n  wm_state::WmState,\r\n};\r\n\r\npub fn move_workspace_in_direction(\r\n  workspace: &Workspace,\r\n  direction: &Direction,\r\n  state: &mut WmState,\r\n  config: &UserConfig,\r\n) -> anyhow::Result<()> {\r\n  let origin_monitor = workspace.monitor().context(\"No monitor.\")?;\r\n  let target_monitor =\r\n    state.monitor_in_direction(&origin_monitor, direction)?;\r\n\r\n  if let Some(target_monitor) = target_monitor {\r\n    // Get currently displayed workspace on the target monitor.\r\n    let displayed_workspace = target_monitor\r\n      .displayed_workspace()\r\n      .context(\"No displayed workspace.\")?;\r\n\r\n    move_container_within_tree(\r\n      &workspace.clone().into(),\r\n      &target_monitor.clone().into(),\r\n      target_monitor.child_count(),\r\n      state,\r\n    )?;\r\n\r\n    let windows = workspace\r\n      .descendants()\r\n      .filter_map(|descendant| descendant.as_window_container().ok());\r\n\r\n    for window in windows {\r\n      window.set_has_pending_dpi_adjustment(true);\r\n\r\n      window.set_floating_placement(\r\n        window\r\n          .floating_placement()\r\n          .translate_to_center(&workspace.to_rect()?),\r\n      );\r\n    }\r\n\r\n    state\r\n      .pending_sync\r\n      .queue_cursor_jump()\r\n      .queue_container_to_redraw(workspace.clone())\r\n      .queue_container_to_redraw(displayed_workspace);\r\n\r\n    match origin_monitor.child_count() {\r\n      0 => {\r\n        // Prevent origin monitor from having no workspaces.\r\n        activate_workspace(None, Some(origin_monitor), state, config)?;\r\n      }\r\n      _ => {\r\n        // Redraw the workspace on the origin monitor.\r\n        state.pending_sync.queue_container_to_redraw(\r\n          origin_monitor\r\n            .displayed_workspace()\r\n            .context(\"No displayed workspace.\")?,\r\n        );\r\n      }\r\n    }\r\n\r\n    // Get empty workspace to destroy (if one is found). Cannot destroy\r\n    // empty workspaces if they're the only workspace on the monitor.\r\n    let workspace_to_destroy =\r\n      target_monitor.workspaces().into_iter().find(|workspace| {\r\n        !workspace.config().keep_alive\r\n          && !workspace.has_children()\r\n          && !workspace.is_displayed()\r\n      });\r\n\r\n    if let Some(workspace) = workspace_to_destroy {\r\n      deactivate_workspace(workspace, state)?;\r\n    }\r\n\r\n    sort_workspaces(&target_monitor, config)?;\r\n\r\n    state.emit_event(WmEvent::WorkspaceUpdated {\r\n      updated_workspace: workspace.to_dto()?,\r\n    });\r\n  }\r\n\r\n  Ok(())\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/commands/workspace/sort_workspaces.rs",
    "content": "use anyhow::Context;\r\nuse wm_common::VecDequeExt;\r\n\r\nuse crate::{\r\n  models::Monitor, traits::CommonGetters, user_config::UserConfig,\r\n};\r\n\r\n/// Sorts a monitor's workspaces by config order.\r\npub fn sort_workspaces(\r\n  monitor: &Monitor,\r\n  config: &UserConfig,\r\n) -> anyhow::Result<()> {\r\n  let mut workspaces = monitor.workspaces();\r\n  config.sort_workspaces(&mut workspaces);\r\n\r\n  for workspace in &workspaces {\r\n    let target_index = &workspaces\r\n      .iter()\r\n      .position(|sorted_workspace| sorted_workspace.id() == workspace.id())\r\n      .context(\"Failed to get workspace target index.\")?;\r\n\r\n    monitor\r\n      .borrow_children_mut()\r\n      .shift_to_index(*target_index, workspace.clone().into());\r\n  }\r\n\r\n  Ok(())\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/commands/workspace/update_workspace_config.rs",
    "content": "use anyhow::Context;\r\nuse wm_common::{InvokeUpdateWorkspaceConfig, WmEvent, WorkspaceConfig};\r\n\r\nuse super::sort_workspaces;\r\nuse crate::{\r\n  models::Workspace, traits::CommonGetters, user_config::UserConfig,\r\n  wm_state::WmState,\r\n};\r\n\r\npub fn update_workspace_config(\r\n  workspace: &Workspace,\r\n  state: &WmState,\r\n  config: &UserConfig,\r\n  new_config: &InvokeUpdateWorkspaceConfig,\r\n) -> anyhow::Result<()> {\r\n  let current_config = workspace.config();\r\n\r\n  // Validate the workspace name change.\r\n  if let Some(new_name) = &new_config.name {\r\n    if new_name != &current_config.name {\r\n      if let Some(_other_workspace) = state.workspace_by_name(new_name) {\r\n        anyhow::bail!(\"The workspace \\\"{}\\\" already exists\", new_name);\r\n      }\r\n    }\r\n  }\r\n\r\n  // Update the config with the incoming values.\r\n  let updated_config = WorkspaceConfig {\r\n    name: new_config\r\n      .name\r\n      .clone()\r\n      .unwrap_or(current_config.name.clone()),\r\n    display_name: new_config\r\n      .display_name\r\n      .clone()\r\n      .or(current_config.display_name.clone()),\r\n    bind_to_monitor: new_config\r\n      .bind_to_monitor\r\n      .or(current_config.bind_to_monitor),\r\n    keep_alive: new_config.keep_alive.unwrap_or(current_config.keep_alive),\r\n  };\r\n\r\n  workspace.set_config(updated_config);\r\n\r\n  sort_workspaces(\r\n    &workspace.monitor().context(\"No displayed workspace.\")?,\r\n    config,\r\n  )?;\r\n\r\n  // TODO: Re-assign bound workspaces to their respective monitors.\r\n\r\n  state.emit_event(WmEvent::WorkspaceUpdated {\r\n    updated_workspace: workspace.to_dto()?,\r\n  });\r\n\r\n  Ok(())\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/events/handle_display_settings_changed.rs",
    "content": "use anyhow::Context;\r\nuse wm_common::try_warn;\r\n\r\nuse crate::{\r\n  commands::monitor::{\r\n    add_monitor, move_bounded_workspaces_to_new_monitor, remove_monitor,\r\n    sort_monitors, update_monitor,\r\n  },\r\n  models::{Monitor, NativeMonitorProperties},\r\n  traits::{CommonGetters, PositionGetters, WindowGetters},\r\n  user_config::UserConfig,\r\n  wm_state::WmState,\r\n};\r\n\r\npub fn handle_display_settings_changed(\r\n  state: &mut WmState,\r\n  config: &UserConfig,\r\n) -> anyhow::Result<()> {\r\n  tracing::info!(\"Display settings changed.\");\r\n\r\n  // Ignore the event if retrieval of the displays or their properties\r\n  // fails (can happen transiently during sleep/wake).\r\n  let displays = try_warn!(state\r\n    .dispatcher\r\n    .sorted_displays()\r\n    .map_err(anyhow::Error::from)\r\n    .and_then(|displays| {\r\n      displays\r\n        .into_iter()\r\n        .map(|display| {\r\n          let properties = NativeMonitorProperties::try_from(&display)?;\r\n          Ok((display, properties))\r\n        })\r\n        .try_collect::<Vec<_>>()\r\n    }));\r\n\r\n  let mut pending_monitors = state.monitors();\r\n  let mut unmatched_displays = Vec::new();\r\n\r\n  // Match each display to an existing monitor and update it.\r\n  for (display, properties) in displays {\r\n    match find_matching_monitor(&pending_monitors, &properties) {\r\n      Some((monitor, index)) => {\r\n        update_monitor(monitor, &display, properties, state)?;\r\n        pending_monitors.remove(index);\r\n      }\r\n      None => unmatched_displays.push((display, properties)),\r\n    }\r\n  }\r\n\r\n  let mut new_monitors: Vec<Monitor> = Vec::new();\r\n\r\n  // Pair unmatched displays with unmatched monitors, or add new ones.\r\n  for (display, properties) in unmatched_displays {\r\n    if pending_monitors.is_empty() {\r\n      let monitor = add_monitor(display, properties, state)?;\r\n      new_monitors.push(monitor);\r\n    } else {\r\n      let monitor = pending_monitors.remove(0);\r\n      update_monitor(&monitor, &display, properties, state)?;\r\n    }\r\n  }\r\n\r\n  // Remove monitors that no longer have a corresponding display and move\r\n  // their workspaces to other monitors.\r\n  //\r\n  // Prevent removal of the last monitor (i.e. for when all monitors are\r\n  // disconnected). This will cause the WM's monitors to temporarily\r\n  // mismatch the OS monitor state, however, it'll be updated correctly\r\n  // when a new monitor is connected again.\r\n  for monitor in pending_monitors {\r\n    if state.monitors().len() > 1 {\r\n      remove_monitor(monitor, state, config)?;\r\n    }\r\n  }\r\n\r\n  // Sort monitors by position.\r\n  sort_monitors(&state.root_container)?;\r\n\r\n  for new_monitor in new_monitors {\r\n    move_bounded_workspaces_to_new_monitor(&new_monitor, state, config)?;\r\n  }\r\n\r\n  for window in state.windows() {\r\n    // Display setting changes can spread windows out sporadically, so mark\r\n    // all windows as needing a DPI adjustment (just in case).\r\n    window.set_has_pending_dpi_adjustment(true);\r\n\r\n    // Need to update floating position of moved windows when a monitor is\r\n    // disconnected or if the primary display is changed. The primary\r\n    // display dictates the position of 0,0.\r\n    let workspace = window.workspace().context(\"No workspace.\")?;\r\n\r\n    let should_recenter = if window.has_custom_floating_placement() {\r\n      let workspace_rect = workspace.to_rect()?;\r\n\r\n      // Keep the placement if it still intersects the workspace, since\r\n      // `PlatformEvent::DisplaySettingsChanged` can be triggered by\r\n      // non-monitor changes (e.g. unplugging a USB device).\r\n      window\r\n        .floating_placement()\r\n        .intersection_area(&workspace_rect)\r\n        == 0\r\n    } else {\r\n      true\r\n    };\r\n\r\n    if should_recenter {\r\n      window.set_floating_placement(\r\n        window\r\n          .floating_placement()\r\n          .translate_to_center(&workspace.to_rect()?),\r\n      );\r\n    }\r\n  }\r\n\r\n  // Redraw full container tree.\r\n  state\r\n    .pending_sync\r\n    .queue_container_to_redraw(state.root_container.clone());\r\n\r\n  Ok(())\r\n}\r\n\r\n/// Finds the monitor matching the given display properties.\r\n///\r\n/// Returns the monitor and its index within the list of monitors.\r\nfn find_matching_monitor<'a>(\r\n  monitors: &'a [Monitor],\r\n  properties: &NativeMonitorProperties,\r\n) -> Option<(&'a Monitor, usize)> {\r\n  monitors.iter().enumerate().find_map(|(index, monitor)| {\r\n    let existing = monitor.native_properties();\r\n\r\n    let is_match = {\r\n      #[cfg(target_os = \"macos\")]\r\n      {\r\n        existing.device_uuid == properties.device_uuid\r\n      }\r\n\r\n      // On Windows, match the monitor by:\r\n      // 1. Its handle\r\n      // 2. Its device path\r\n      // 3. Its hardware ID (if unique)\r\n      //\r\n      // Monitor handles and device paths are unique, but can change over\r\n      // time. The hardware ID is not guaranteed to be unique, so we\r\n      // match against that last.\r\n      #[cfg(target_os = \"windows\")]\r\n      {\r\n        existing.handle == properties.handle\r\n          || existing.device_path.as_deref().is_some_and(|device_path| {\r\n            properties.device_path.as_deref() == Some(device_path)\r\n          })\r\n          || existing.hardware_id.as_deref().is_some_and(|hardware_id| {\r\n            let is_unique = monitors\r\n              .iter()\r\n              .filter(|other_monitor| {\r\n                other_monitor.native_properties().hardware_id.as_deref()\r\n                  == Some(hardware_id)\r\n              })\r\n              .count()\r\n              == 1;\r\n\r\n            is_unique\r\n              && properties.hardware_id.as_deref() == Some(hardware_id)\r\n          })\r\n      }\r\n    };\r\n\r\n    is_match.then_some((monitor, index))\r\n  })\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/events/handle_mouse_move.rs",
    "content": "use anyhow::Context;\r\n#[cfg(target_os = \"macos\")]\r\nuse wm_common::try_warn;\r\nuse wm_platform::{MouseButton, MouseEvent};\r\n\r\nuse crate::{\r\n  commands::container::set_focused_descendant, traits::CommonGetters,\r\n  user_config::UserConfig, wm_state::WmState,\r\n};\r\n#[cfg(target_os = \"macos\")]\r\nuse crate::{\r\n  events::handle_window_moved_or_resized_end, traits::WindowGetters,\r\n};\r\n\r\npub fn handle_mouse_move(\r\n  event: &MouseEvent,\r\n  state: &mut WmState,\r\n  config: &UserConfig,\r\n) -> anyhow::Result<()> {\r\n  // Ignore mouse move events if the WM is paused. The mouse listener\r\n  // should anyways be disabled when the WM is paused, but this is just in\r\n  // case any events slipped through while disabling.\r\n  if state.is_paused {\r\n    return Ok(());\r\n  }\r\n\r\n  // On macOS, detect when a window drag operation has ended by listening\r\n  // to the release of left click.\r\n  //\r\n  // This cannot be used for Windows, since it leads to race conditions\r\n  // where the mouse event comes in before the `MovedOrResized` event with\r\n  // `is_interactive_end`. For example, if the user drags to maximize a\r\n  // window, the WS_MAXIMIZED state is sometimes set after the mouse event.\r\n  #[cfg(target_os = \"macos\")]\r\n  if let MouseEvent::ButtonUp { button, .. } = event {\r\n    if *button == MouseButton::Left {\r\n      let active_drag_windows = state\r\n        .windows()\r\n        .into_iter()\r\n        .filter(|window| window.active_drag().is_some());\r\n\r\n      // Only one window should ever be actively dragged at a time, but\r\n      // just in case, iterate over all active drag windows.\r\n      for window in active_drag_windows {\r\n        let new_rect = try_warn!(window.native().frame());\r\n\r\n        window.update_native_properties(|properties| {\r\n          properties.frame = new_rect;\r\n        });\r\n\r\n        handle_window_moved_or_resized_end(&window, state, config)?;\r\n      }\r\n    }\r\n\r\n    return Ok(());\r\n  }\r\n\r\n  if let MouseEvent::Move {\r\n    pressed_buttons,\r\n    // LINT: `window_below_cursor` is only used on macOS.\r\n    #[cfg_attr(not(target_os = \"macos\"), allow(unused_variables))]\r\n    window_below_cursor,\r\n    position,\r\n    ..\r\n  } = event\r\n  {\r\n    // Ignore event if left/right-click is down. Otherwise, this causes\r\n    // focus to jitter when a window is being resized by its drag\r\n    // handles. Also ignore if the OS focused window isn't the same as\r\n    // the WM's focused window.\r\n    if pressed_buttons.contains(&MouseButton::Left)\r\n      || pressed_buttons.contains(&MouseButton::Right)\r\n      || !state.is_focus_synced\r\n      || !config.value.general.focus_follows_cursor\r\n    {\r\n      return Ok(());\r\n    }\r\n\r\n    let window_under_cursor = {\r\n      #[cfg(target_os = \"macos\")]\r\n      {\r\n        window_below_cursor.and_then(|window_id| {\r\n          use crate::traits::WindowGetters;\r\n\r\n          state\r\n            .windows()\r\n            .into_iter()\r\n            .find(|w| w.native().id() == window_id)\r\n        })\r\n      }\r\n      #[cfg(target_os = \"windows\")]\r\n      {\r\n        state\r\n          .dispatcher\r\n          .window_from_point(position)?\r\n          .and_then(|native| state.window_from_native(&native))\r\n      }\r\n    };\r\n\r\n    // Set focus to whichever window is currently under the cursor.\r\n    if let Some(window) = window_under_cursor {\r\n      let focused_container =\r\n        state.focused_container().context(\"No focused container.\")?;\r\n\r\n      if focused_container.id() != window.id() {\r\n        set_focused_descendant(&window.as_container(), None);\r\n        state.pending_sync.queue_focus_change();\r\n      }\r\n    } else {\r\n      // Focus the monitor if no window is under the cursor.\r\n      let cursor_monitor = state\r\n        .monitor_at_point(position)\r\n        .context(\"No monitor under cursor.\")?;\r\n\r\n      let focused_monitor = state\r\n        .focused_container()\r\n        .context(\"No focused container.\")?\r\n        .monitor()\r\n        .context(\"Focused container has no monitor.\")?;\r\n\r\n      // Avoid setting focus to the same monitor.\r\n      if cursor_monitor.id() != focused_monitor.id() {\r\n        set_focused_descendant(&cursor_monitor.as_container(), None);\r\n        state.pending_sync.queue_focus_change();\r\n      }\r\n    }\r\n  }\r\n\r\n  Ok(())\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/events/handle_window_destroyed.rs",
    "content": "use anyhow::Context;\r\nuse tracing::info;\r\nuse wm_platform::WindowId;\r\n\r\nuse crate::{\r\n  commands::{window::unmanage_window, workspace::deactivate_workspace},\r\n  traits::{CommonGetters, WindowGetters},\r\n  wm_state::WmState,\r\n};\r\n\r\npub fn handle_window_destroyed(\r\n  native_window_id: WindowId,\r\n  state: &mut WmState,\r\n) -> anyhow::Result<()> {\r\n  let found_window = state\r\n    .windows()\r\n    .into_iter()\r\n    .find(|window| window.native().id() == native_window_id);\r\n\r\n  // Unmanage the window if it's currently managed.\r\n  if let Some(window) = found_window {\r\n    let workspace = window.workspace().context(\"No workspace.\")?;\r\n\r\n    info!(\"Window closed: {window}\");\r\n    unmanage_window(window, state)?;\r\n\r\n    // Destroy parent workspace if window was killed while its workspace\r\n    // was not displayed (e.g. via task manager).\r\n    if !workspace.config().keep_alive\r\n      && !workspace.has_children()\r\n      && !workspace.is_displayed()\r\n    {\r\n      deactivate_workspace(workspace, state)?;\r\n    }\r\n  }\r\n\r\n  Ok(())\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/events/handle_window_focused.rs",
    "content": "use anyhow::Context;\r\nuse tracing::info;\r\nuse wm_common::{DisplayState, WindowRuleEvent, WmEvent};\r\nuse wm_platform::NativeWindow;\r\n\r\nuse crate::{\r\n  commands::{\r\n    container::set_focused_descendant, window::run_window_rules,\r\n    workspace::focus_workspace,\r\n  },\r\n  models::WorkspaceTarget,\r\n  traits::{CommonGetters, WindowGetters},\r\n  user_config::UserConfig,\r\n  wm_state::WmState,\r\n};\r\n\r\npub fn handle_window_focused(\r\n  native_window: &NativeWindow,\r\n  state: &mut WmState,\r\n  config: &mut UserConfig,\r\n) -> anyhow::Result<()> {\r\n  let found_window = state.window_from_native(native_window);\r\n  let focused_container =\r\n    state.focused_container().context(\"No focused container.\")?;\r\n\r\n  // Update the focus sync state. If the OS focused window is not same as\r\n  // the WM's focused container, then the focus is not synced.\r\n  state.is_focus_synced = match focused_container.as_window_container() {\r\n    Ok(window) => *window.native() == *native_window,\r\n    _ => native_window.is_desktop_window().unwrap_or(false),\r\n  };\r\n\r\n  // Handle overriding focus on close/minimize. After a window is closed\r\n  // or minimized, the OS or the closed application might automatically\r\n  // switch focus to a different window. To force focus to go to the WM's\r\n  // target focus container, we reassign any focus events 100ms after\r\n  // close/minimize. This will cause focus to briefly flicker to the OS\r\n  // focus target and then to the WM's focus target.\r\n  if should_override_focus(state) {\r\n    state.pending_sync.queue_focus_change();\r\n    return Ok(());\r\n  }\r\n\r\n  // Ignore the focus event if window is being hidden by the WM.\r\n  if let Some(window) = &found_window {\r\n    if window.display_state() == DisplayState::Hiding {\r\n      return Ok(());\r\n    }\r\n  }\r\n\r\n  // Focus effect should be updated for any change in focus that shouldn't\r\n  // be overwritten. The incoming focus event at this point is either:\r\n  //  1. WM's focus container (window or workspace). This is the desktop\r\n  //     window in the case of a workspace.\r\n  //  2. An ignored window.\r\n  //  3. A window that received manual focus.\r\n  state.pending_sync.queue_focused_effect_update();\r\n\r\n  if let Some(window) = found_window {\r\n    let workspace = window.workspace().context(\"No workspace\")?;\r\n\r\n    // Native focus has been synced to the WM's focused container.\r\n    if focused_container == window.clone().into() {\r\n      state.is_focus_synced = true;\r\n      state.pending_sync.queue_workspace_to_reorder(workspace);\r\n      return Ok(());\r\n    }\r\n\r\n    info!(\"Window manually focused: {window}\");\r\n\r\n    // Handle focus events from windows on hidden workspaces. For example,\r\n    // if Discord is forcefully shown by the OS when it's on a hidden\r\n    // workspace, switch focus to Discord's workspace.\r\n    if window.display_state() == DisplayState::Hidden {\r\n      info!(\"Focusing off-screen window: {window}\");\r\n\r\n      focus_workspace(\r\n        WorkspaceTarget::Name(workspace.config().name),\r\n        state,\r\n        config,\r\n      )?;\r\n    }\r\n\r\n    // Update the WM's focus state.\r\n    set_focused_descendant(&window.clone().into(), None);\r\n\r\n    // Run window rules for focus events.\r\n    run_window_rules(\r\n      window.clone(),\r\n      &WindowRuleEvent::Focus,\r\n      state,\r\n      config,\r\n    )?;\r\n\r\n    state.is_focus_synced = true;\r\n    state.pending_sync.queue_workspace_to_reorder(workspace);\r\n\r\n    // Broadcast the focus change event.\r\n    state.emit_event(WmEvent::FocusChanged {\r\n      focused_container: window.to_dto()?,\r\n    });\r\n  }\r\n\r\n  Ok(())\r\n}\r\n\r\n/// Returns true if focus should be reassigned to the WM's focus container.\r\nfn should_override_focus(state: &WmState) -> bool {\r\n  let has_recent_unmanage = state\r\n    .unmanaged_or_minimized_timestamp\r\n    .is_some_and(|time| time.elapsed().as_millis() < 100);\r\n\r\n  has_recent_unmanage && !state.is_focus_synced\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/events/handle_window_hidden.rs",
    "content": "use tracing::info;\r\nuse wm_common::{DisplayState, HideMethod};\r\nuse wm_platform::NativeWindow;\r\n\r\nuse crate::{\r\n  commands::window::unmanage_window, traits::WindowGetters,\r\n  user_config::UserConfig, wm_state::WmState,\r\n};\r\n\r\npub fn handle_window_hidden(\r\n  native_window: &NativeWindow,\r\n  state: &mut WmState,\r\n  config: &UserConfig,\r\n) -> anyhow::Result<()> {\r\n  let found_window = state.window_from_native(native_window);\r\n\r\n  if let Some(window) = found_window {\r\n    info!(\"Window hidden: {window}\");\r\n\r\n    // Update the display state.\r\n    if config.value.general.hide_method != HideMethod::PlaceInCorner\r\n      && window.display_state() == DisplayState::Hiding\r\n    {\r\n      window.set_display_state(DisplayState::Hidden);\r\n      return Ok(());\r\n    }\r\n\r\n    // Unmanage the window if it's not in a display state transition. Also,\r\n    // since window events are not 100% guaranteed to be in correct order,\r\n    // we need to ignore events where the window is not actually hidden.\r\n    if window.display_state() == DisplayState::Shown\r\n      && !window.native().is_visible().unwrap_or(false)\r\n    {\r\n      unmanage_window(window, state)?;\r\n    }\r\n  }\r\n\r\n  Ok(())\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/events/handle_window_minimize_ended.rs",
    "content": "use tracing::info;\r\nuse wm_common::{try_warn, WindowState};\r\nuse wm_platform::NativeWindow;\r\n\r\nuse crate::{\r\n  commands::window::update_window_state, traits::WindowGetters,\r\n  user_config::UserConfig, wm_state::WmState,\r\n};\r\n\r\npub fn handle_window_minimize_ended(\r\n  native_window: &NativeWindow,\r\n  state: &mut WmState,\r\n  config: &UserConfig,\r\n) -> anyhow::Result<()> {\r\n  let found_window = state.window_from_native(native_window);\r\n\r\n  // Update the window's state to not be minimized.\r\n  if let Some(window) = found_window {\r\n    let is_minimized = try_warn!(window.native().is_minimized());\r\n\r\n    window.update_native_properties(|properties| {\r\n      properties.is_minimized = is_minimized;\r\n    });\r\n\r\n    if !is_minimized && window.state() == WindowState::Minimized {\r\n      info!(\"Window minimize ended: {window}\");\r\n\r\n      let target_state = window\r\n        .prev_state()\r\n        .unwrap_or(WindowState::default_from_config(&config.value));\r\n\r\n      update_window_state(window.clone(), target_state, state, config)?;\r\n    }\r\n  }\r\n\r\n  Ok(())\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/events/handle_window_minimized.rs",
    "content": "use tracing::info;\r\nuse wm_common::{try_warn, WindowState};\r\nuse wm_platform::NativeWindow;\r\n\r\nuse crate::{\r\n  commands::{\r\n    container::set_focused_descendant, window::update_window_state,\r\n  },\r\n  traits::WindowGetters,\r\n  user_config::UserConfig,\r\n  wm_state::WmState,\r\n};\r\n\r\npub fn handle_window_minimized(\r\n  native_window: &NativeWindow,\r\n  state: &mut WmState,\r\n  config: &UserConfig,\r\n) -> anyhow::Result<()> {\r\n  let found_window = state.window_from_native(native_window);\r\n\r\n  // Update the window's state to be minimized.\r\n  if let Some(window) = found_window {\r\n    let is_minimized = try_warn!(window.native().is_minimized());\r\n\r\n    window.update_native_properties(|properties| {\r\n      properties.is_minimized = is_minimized;\r\n    });\r\n\r\n    if is_minimized && window.state() != WindowState::Minimized {\r\n      info!(\"Window minimized: {window}\");\r\n\r\n      let window = update_window_state(\r\n        window.clone(),\r\n        WindowState::Minimized,\r\n        state,\r\n        config,\r\n      )?;\r\n\r\n      // Clear the drag state, as a window can be minimized while\r\n      // being dragged (e.g. via `toggle-minimized`).\r\n      // TODO: Investigate other code paths where the drag state should be\r\n      // cleared (e.g. most commands that call `update_window_state`).\r\n      window.set_active_drag(None);\r\n\r\n      // Focus should be reassigned after a window has been minimized.\r\n      if let Some(focus_target) = state.focus_target_after_removal(&window)\r\n      {\r\n        set_focused_descendant(&focus_target, None);\r\n        state.pending_sync.queue_focus_change().queue_cursor_jump();\r\n        state.unmanaged_or_minimized_timestamp =\r\n          Some(std::time::Instant::now());\r\n      }\r\n    }\r\n  }\r\n\r\n  Ok(())\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/events/handle_window_moved_or_resized.rs",
    "content": "use anyhow::Context;\r\nuse wm_common::{\r\n  try_warn, ActiveDrag, ActiveDragOperation, DisplayState,\r\n  FloatingStateConfig, FullscreenStateConfig, HideMethod, WindowState,\r\n};\r\n#[cfg(target_os = \"windows\")]\r\nuse wm_platform::NativeWindowWindowsExt;\r\n#[cfg(target_os = \"macos\")]\r\nuse wm_platform::{LengthValue, MouseButton, RectDelta};\r\nuse wm_platform::{NativeWindow, Rect};\r\n\r\nuse crate::{\r\n  commands::{\r\n    container::{flatten_split_container, move_container_within_tree},\r\n    window::update_window_state,\r\n  },\r\n  events::handle_window_moved_or_resized_end,\r\n  models::{Monitor, NonTilingWindow, WindowContainer},\r\n  traits::{CommonGetters, WindowGetters},\r\n  user_config::UserConfig,\r\n  wm_state::WmState,\r\n};\r\n\r\n#[allow(clippy::too_many_lines)]\r\npub fn handle_window_moved_or_resized(\r\n  native_window: &NativeWindow,\r\n  // LINT: `is_interactive_start` is only used on Windows.\r\n  #[cfg_attr(not(target_os = \"windows\"), allow(unused_variables))]\r\n  is_interactive_start: bool,\r\n  // LINT: `is_interactive_end` is only used on Windows.\r\n  #[cfg_attr(not(target_os = \"windows\"), allow(unused_variables))]\r\n  is_interactive_end: bool,\r\n  state: &mut WmState,\r\n  config: &UserConfig,\r\n) -> anyhow::Result<()> {\r\n  let found_window = state.window_from_native(native_window);\r\n\r\n  if let Some(window) = found_window {\r\n    let old_frame_position = window.native_properties().frame;\r\n    let frame_position = try_warn!(window.native().frame());\r\n\r\n    window.update_native_properties(|properties| {\r\n      properties.frame = frame_position.clone();\r\n    });\r\n\r\n    // Handle windows that are actively being dragged.\r\n    if !state.is_paused && window.active_drag().is_some() {\r\n      let is_drag_end = {\r\n        // On Windows, the drag operation has ended when\r\n        // `is_interactive_end` is `true`. This corresponds to a\r\n        // `EVENT_SYSTEM_MOVESIZEEND` event, which is unavailable on macOS.\r\n        #[cfg(target_os = \"windows\")]\r\n        {\r\n          is_interactive_end\r\n        }\r\n        // On macOS, the drag operation has ended when the mouse button is\r\n        // no longer down. This is a fallback mechanism since for macOS,\r\n        // `is_interactive_end` is always `false`. The `MouseEvent` handler\r\n        // also catches `MouseButtonUp` events, but this provides\r\n        // additional safety.\r\n        // TODO: Can probably remove this check and rely 100% on the mouse\r\n        // event handler.\r\n        #[cfg(target_os = \"macos\")]\r\n        {\r\n          !state.dispatcher.is_mouse_down(&MouseButton::Left)\r\n        }\r\n      };\r\n\r\n      if is_drag_end {\r\n        return handle_window_moved_or_resized_end(&window, state, config);\r\n      }\r\n\r\n      return update_drag_state(&window, &frame_position, state, config);\r\n    }\r\n\r\n    let old_is_maximized = window.native_properties().is_maximized;\r\n    let is_maximized = try_warn!(window.native().is_maximized());\r\n\r\n    // Ignore duplicate move/resize events. Window position changes can\r\n    // trigger multiple events. For example, restoring from maximized can\r\n    // trigger as many as 4 identical events on Windows.\r\n    if old_frame_position == frame_position\r\n      && old_is_maximized == is_maximized\r\n      && !is_interactive_start\r\n    {\r\n      return Ok(());\r\n    }\r\n\r\n    window.update_native_properties(|properties| {\r\n      properties.is_maximized = is_maximized;\r\n    });\r\n\r\n    // If the window is not maximized, update its cached shadow borders.\r\n    // Maximized windows temporarily have 0 shadow borders, in which case\r\n    // we should use its previous value for redraws.\r\n    #[cfg(target_os = \"windows\")]\r\n    {\r\n      let shadow_borders = try_warn!(window.native().shadow_borders());\r\n      if !is_maximized {\r\n        window.update_native_properties(|properties| {\r\n          properties.shadow_borders = shadow_borders;\r\n        });\r\n      }\r\n    }\r\n\r\n    let is_minimized = try_warn!(window.native().is_minimized());\r\n\r\n    // Ignore events for minimized windows. Let them be handled by the\r\n    // `PlatformEvent::WindowMinimized` event handler instead.\r\n    if is_minimized {\r\n      return Ok(());\r\n    }\r\n\r\n    // Detect whether the window is starting to be interactively moved or\r\n    // resized by the user (e.g. via the window's drag handles).\r\n    let is_drag_start = !state.is_paused && {\r\n      #[cfg(target_os = \"windows\")]\r\n      {\r\n        // Drag events can be valid for all window states apart from\r\n        // minimized.\r\n        is_interactive_start\r\n          && !matches!(window.state(), WindowState::Minimized)\r\n      }\r\n      #[cfg(target_os = \"macos\")]\r\n      {\r\n        // Drag events are never valid for minimized or maximized windows.\r\n        let is_valid_state = !matches!(\r\n          window.state(),\r\n          WindowState::Fullscreen(FullscreenStateConfig {\r\n            maximized: true,\r\n            ..\r\n          }) | WindowState::Minimized\r\n        );\r\n\r\n        let is_dragging_other_window =\r\n          state.windows().iter().any(|w| w.active_drag().is_some());\r\n\r\n        let is_left_click =\r\n          state.dispatcher.is_mouse_down(&MouseButton::Left);\r\n\r\n        // Only consider the window to be dragging if:\r\n        //  1. The window is not minimized or maximized.\r\n        //  2. No other window is being dragged.\r\n        //  3. Left-click is down.\r\n        //  4. The cursor is within 40px margin around the window's frame.\r\n        if is_valid_state && !is_dragging_other_window && is_left_click {\r\n          // The window frame can lag behind the cursor when moving or\r\n          // resizing quickly, so allow for a bit of leeway.\r\n          let frame_to_check = frame_position.apply_delta(\r\n            &RectDelta::new(\r\n              LengthValue::from_px(40),\r\n              LengthValue::from_px(40),\r\n              LengthValue::from_px(40),\r\n              LengthValue::from_px(40),\r\n            ),\r\n            None,\r\n          );\r\n\r\n          // TODO: Might be more robust to also check if the window under\r\n          // the cursor (i.e. via `dispatcher.window_from_point`) is not a\r\n          // different window.\r\n          let cursor_position = state.dispatcher.cursor_position()?;\r\n          frame_to_check.contains_point(&cursor_position)\r\n        } else {\r\n          false\r\n        }\r\n      }\r\n    };\r\n\r\n    if is_drag_start {\r\n      tracing::info!(\"Window started dragging: {window}\");\r\n\r\n      window.set_active_drag(Some(ActiveDrag {\r\n        operation: None,\r\n        is_from_floating: matches!(\r\n          window.state(),\r\n          WindowState::Floating(_)\r\n        ),\r\n        #[cfg(target_os = \"windows\")]\r\n        initial_position: old_frame_position.clone(),\r\n        // The updated frame position is used here instead of the initial\r\n        // frame position due to a quirk on macOS. When we resize an\r\n        // AXUIElement to a value outside the allowed min/max width &\r\n        // height, macOS doesn't actually apply that size. However, it\r\n        // still reports the value we attempted to set until a subsequent\r\n        // `WindowEvent::MovedOrResized` event.\r\n        #[cfg(target_os = \"macos\")]\r\n        initial_position: frame_position.clone(),\r\n      }));\r\n\r\n      #[cfg(target_os = \"windows\")]\r\n      update_drag_state(&window, &frame_position, state, config)?;\r\n\r\n      return Ok(());\r\n    }\r\n\r\n    let nearest_monitor = state\r\n      .nearest_monitor(&window.native())\r\n      .context(\"No nearest monitor.\")?;\r\n\r\n    // For `HideMethod::PlaceInCorner`, hiding/showing is implemented by\r\n    // repositioning the window. Since the OS won't emit real\r\n    // shown/hidden events in this mode, update `DisplayState` based on\r\n    // whether the window has been moved to the monitor's bottom corner.\r\n    if config.value.general.hide_method == HideMethod::PlaceInCorner {\r\n      let is_in_corner = is_in_corner(\r\n        &frame_position,\r\n        &nearest_monitor.native_properties().working_area,\r\n      );\r\n\r\n      // TODO: Consider redrawing if hidden and should be shown, or if\r\n      // shown and should be hidden.\r\n      // TODO: It can be valid for a floating window to be in the corner,\r\n      // in which case, it currently doesn't get updated to\r\n      // `DisplayState::Shown`.\r\n      let display_state = match (window.display_state(), is_in_corner) {\r\n        (DisplayState::Hiding, true) => DisplayState::Hidden,\r\n        (DisplayState::Showing, false) => DisplayState::Shown,\r\n        _ => window.display_state(),\r\n      };\r\n\r\n      if display_state != window.display_state() {\r\n        window.set_display_state(display_state);\r\n        return Ok(());\r\n      }\r\n    }\r\n\r\n    let should_fullscreen = {\r\n      let workspace = nearest_monitor\r\n        .displayed_workspace()\r\n        .context(\"No workspace.\")?;\r\n\r\n      let should_fullscreen = window.should_fullscreen(&workspace)?;\r\n\r\n      match window.state() {\r\n        // Override the fullscreen check for when an app self-exits\r\n        // fullscreen (e.g. Chrome via F11) and restores its window to\r\n        // a position that exactly covers the workspace rect.\r\n        WindowState::Fullscreen(fullscreen)\r\n          if !fullscreen.maximized && should_fullscreen =>\r\n        {\r\n          let workspace_rect = workspace.max_workspace_rect()?;\r\n\r\n          let old_frame = old_frame_position\r\n            .apply_delta(&window.border_delta().inverse(), None);\r\n          let new_frame = frame_position\r\n            .apply_delta(&window.border_delta().inverse(), None);\r\n\r\n          let old_exceeded =\r\n            old_frame.inset(1).contains_rect(&workspace_rect);\r\n          let new_exceeds =\r\n            new_frame.inset(1).contains_rect(&workspace_rect);\r\n\r\n          // The window should no longer be fullscreen if the old frame\r\n          // exceeded the workspace bounds (app was in OS fullscreen), but\r\n          // the new frame no longer does. Configs with 0px outer gaps\r\n          // always use the `should_fullscreen` check, since the old frame\r\n          // will never exceed the workspace bounds.\r\n          if old_exceeded && !new_exceeds {\r\n            false\r\n          } else {\r\n            should_fullscreen\r\n          }\r\n        }\r\n        _ => should_fullscreen,\r\n      }\r\n    };\r\n\r\n    // Handle a window being maximized or entering fullscreen.\r\n    if is_maximized || should_fullscreen {\r\n      let is_same_state = is_maximized\r\n        && matches!(\r\n          window.state(),\r\n          WindowState::Fullscreen(FullscreenStateConfig {\r\n            maximized: true,\r\n            ..\r\n          })\r\n        )\r\n        || should_fullscreen\r\n          && matches!(\r\n            window.state(),\r\n            WindowState::Fullscreen(FullscreenStateConfig {\r\n              maximized: false,\r\n              ..\r\n            })\r\n          );\r\n\r\n      // Ignore if there's no state change.\r\n      if is_same_state {\r\n        return Ok(());\r\n      }\r\n\r\n      let fullscreen_state = if let WindowState::Fullscreen(\r\n        fullscreen_state,\r\n      ) = window.state()\r\n      {\r\n        fullscreen_state\r\n      } else {\r\n        config\r\n          .value\r\n          .window_behavior\r\n          .state_defaults\r\n          .fullscreen\r\n          .clone()\r\n      };\r\n\r\n      let window = update_window_state(\r\n        window.clone(),\r\n        WindowState::Fullscreen(FullscreenStateConfig {\r\n          maximized: is_maximized,\r\n          ..fullscreen_state\r\n        }),\r\n        state,\r\n        config,\r\n      )?;\r\n\r\n      if is_maximized {\r\n        // Dequeue the window from redraw if it's maximized, since the\r\n        // window is already in the correct state.\r\n        state\r\n          .pending_sync\r\n          .dequeue_container_from_redraw(window.clone());\r\n      }\r\n\r\n      // TODO: Handle a fullscreen window being moved from one monitor to\r\n      // another.\r\n\r\n      return Ok(());\r\n    }\r\n\r\n    match window.state() {\r\n      WindowState::Fullscreen(_) => {\r\n        // Window is no longer maximized/fullscreen and should be restored.\r\n        tracing::info!(\"Restoring window from fullscreen: {window}\");\r\n\r\n        update_window_state(\r\n          window.clone(),\r\n          window.toggled_state(window.state(), config),\r\n          state,\r\n          config,\r\n        )?;\r\n      }\r\n      WindowState::Floating(_) => {\r\n        if let WindowContainer::NonTilingWindow(window) = window {\r\n          update_floating_window_position(\r\n            &window,\r\n            frame_position,\r\n            &nearest_monitor,\r\n            state,\r\n          )?;\r\n        }\r\n      }\r\n      _ => {}\r\n    }\r\n  }\r\n\r\n  Ok(())\r\n}\r\n\r\n// TODO: Move to shared location. `handle_window_moved_or_resized_end.rs`\r\n// also uses this.\r\npub fn update_floating_window_position(\r\n  window: &NonTilingWindow,\r\n  frame_position: Rect,\r\n  nearest_monitor: &Monitor,\r\n  state: &mut WmState,\r\n) -> anyhow::Result<()> {\r\n  tracing::info!(\r\n    \"Updating floating window position: {}\",\r\n    window.as_window_container()?\r\n  );\r\n\r\n  // Update state with the new location of the floating window.\r\n  window.set_floating_placement(frame_position);\r\n  window.set_has_custom_floating_placement(true);\r\n\r\n  let monitor = window.monitor().context(\"No monitor.\")?;\r\n\r\n  // Update the window's workspace if it goes out of bounds of its\r\n  // current workspace.\r\n  if monitor.id() != nearest_monitor.id() {\r\n    let updated_workspace = nearest_monitor\r\n      .displayed_workspace()\r\n      .context(\"Failed to get workspace of nearest monitor.\")?;\r\n\r\n    tracing::info!(\r\n      \"Floating window moved to new workspace: {updated_workspace}\",\r\n    );\r\n\r\n    window.set_insertion_target(None);\r\n\r\n    move_container_within_tree(\r\n      &window.clone().into(),\r\n      &updated_workspace.clone().into(),\r\n      updated_workspace.child_count(),\r\n      state,\r\n    )?;\r\n  }\r\n\r\n  Ok(())\r\n}\r\n\r\n/// Updates the window operation based on changes in frame position.\r\n///\r\n/// This function determines whether a window is being moved or resized and\r\n/// updates its operation state accordingly. If the window is being moved,\r\n/// it's set to floating mode.\r\nfn update_drag_state(\r\n  window: &WindowContainer,\r\n  frame_position: &Rect,\r\n  state: &mut WmState,\r\n  config: &UserConfig,\r\n) -> anyhow::Result<()> {\r\n  let Some(active_drag) = window.active_drag() else {\r\n    return Ok(());\r\n  };\r\n\r\n  // Ignore if the window position has not changed yet.\r\n  if *frame_position == active_drag.initial_position {\r\n    return Ok(());\r\n  }\r\n\r\n  // Determine the drag operation if not already set.\r\n  let is_move = if let Some(operation) = active_drag.operation {\r\n    matches!(operation, ActiveDragOperation::Move)\r\n  } else {\r\n    let is_move = *frame_position != active_drag.initial_position\r\n      && frame_position.height() == active_drag.initial_position.height()\r\n      && frame_position.width() == active_drag.initial_position.width();\r\n\r\n    let operation = if is_move {\r\n      ActiveDragOperation::Move\r\n    } else {\r\n      ActiveDragOperation::Resize\r\n    };\r\n\r\n    window.set_active_drag(Some(ActiveDrag {\r\n      operation: Some(operation),\r\n      ..active_drag.clone()\r\n    }));\r\n\r\n    is_move\r\n  };\r\n\r\n  // Transition window to be floating while it's being dragged, but only\r\n  // after it has been moved at least 10px from its initial position. The\r\n  // 10px threshold is to account for small movements that may be\r\n  // accidental.\r\n  if is_move && !matches!(window.state(), WindowState::Floating(_)) {\r\n    let move_distance = frame_position\r\n      .center_point()\r\n      .distance_between(&active_drag.initial_position.center_point());\r\n\r\n    // Dragging operations on a maximized window can only occur on Windows.\r\n    // The OS immediately restores it while it's being dragged, so we need\r\n    // to update state accordingly without a redraw.\r\n    let is_maximized = matches!(\r\n      window.state(),\r\n      WindowState::Fullscreen(FullscreenStateConfig {\r\n        maximized: true,\r\n        ..\r\n      })\r\n    );\r\n\r\n    if move_distance >= 10.0 || is_maximized {\r\n      let parent = window.parent().context(\"No parent\")?;\r\n\r\n      let is_fullscreen =\r\n        matches!(window.state(), WindowState::Fullscreen(_))\r\n          && !is_maximized;\r\n\r\n      let window = update_window_state(\r\n        window.clone(),\r\n        WindowState::Floating(FloatingStateConfig {\r\n          centered: false,\r\n          ..config.value.window_behavior.state_defaults.floating\r\n        }),\r\n        state,\r\n        config,\r\n      )?;\r\n\r\n      // `update_window_state` automatically adds the window for redraw,\r\n      // which we don't want in this case. However, for fullscreen windows,\r\n      // we do actually want it to be resized initially so that it's\r\n      // easier to move around while dragging.\r\n      if !is_fullscreen {\r\n        state\r\n          .pending_sync\r\n          .dequeue_container_from_redraw(window.clone());\r\n      }\r\n\r\n      // Flatten the parent split container if it only contains the window.\r\n      // TODO: Consider doing this to `move_container_within_tree`, so that\r\n      // the behavior is consistent.\r\n      if let Some(split_parent) = parent.as_split() {\r\n        if split_parent.child_count() == 1 {\r\n          flatten_split_container(split_parent.clone())?;\r\n\r\n          // Hacky fix to redraw siblings after flattening. The parent is\r\n          // queued for redraw from the state change, which gets detached\r\n          // on flatten.\r\n          // TODO: Change `queue_containers_to_redraw` to iterate over its\r\n          // descendant windows and store those instead.\r\n          state\r\n            .pending_sync\r\n            .queue_containers_to_redraw(window.tiling_siblings());\r\n        }\r\n      }\r\n    }\r\n  }\r\n\r\n  Ok(())\r\n}\r\n\r\n/// Gets whether the window is in the corner of the monitor.\r\nfn is_in_corner(window_frame: &Rect, monitor_rect: &Rect) -> bool {\r\n  // Visible portion of the window used when positioning windows in the\r\n  // monitor's corner. See `platform_sync` for how hidden windows are\r\n  // positioned.\r\n  const VISIBLE_SLIVER_PX: i32 = 1;\r\n\r\n  // Allow 1px of leeway.\r\n  let is_left_corner =\r\n    (window_frame.right - VISIBLE_SLIVER_PX - monitor_rect.left).abs()\r\n      <= 1;\r\n\r\n  // Allow 1px of leeway.\r\n  let is_right_corner =\r\n    (window_frame.x() + VISIBLE_SLIVER_PX - monitor_rect.right).abs() <= 1;\r\n\r\n  // On macOS, the window's title bar is prevented from being positioned\r\n  // outside of monitor's working area, so we need to allow ~55px of\r\n  // vertical leeway. Title bar height varies, but can be up to 52px.\r\n  // TODO: See if possible to make this dynamic based on the window's title\r\n  // bar height.\r\n  let is_bottom_of_monitor =\r\n    (window_frame.y() - monitor_rect.bottom).abs() <= 55;\r\n\r\n  (is_left_corner || is_right_corner) && is_bottom_of_monitor\r\n}\r\n\r\n#[cfg(test)]\r\nmod tests {\r\n  use wm_platform::Rect;\r\n\r\n  use super::is_in_corner;\r\n\r\n  #[test]\r\n  fn matches_corner_positions() {\r\n    let monitor = Rect::from_xy(0, 0, 1920, 1080);\r\n\r\n    let frame_in_right_corner = Rect::from_xy(1919, 1050, 600, 600);\r\n    assert!(is_in_corner(&frame_in_right_corner, &monitor));\r\n\r\n    let frame_in_left_corner = Rect::from_xy(1, 1050, 600, 600);\r\n    assert!(is_in_corner(&frame_in_left_corner, &monitor));\r\n  }\r\n\r\n  #[test]\r\n  fn does_not_match_non_corner_positions() {\r\n    let monitor = Rect::from_xy(0, 0, 1920, 1080);\r\n    let frame = Rect::from_xy(100, 100, 800, 600);\r\n\r\n    assert!(!is_in_corner(&frame, &monitor));\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/events/handle_window_moved_or_resized_end.rs",
    "content": "use anyhow::Context;\r\nuse wm_common::{\r\n  try_warn, FullscreenStateConfig, TilingDirection, WindowState,\r\n};\r\nuse wm_platform::{LengthValue, Point, Rect};\r\n\r\nuse crate::{\r\n  commands::{\r\n    container::{move_container_within_tree, wrap_in_split_container},\r\n    window::{set_window_size, update_window_state},\r\n  },\r\n  events::update_floating_window_position,\r\n  models::{\r\n    DirectionContainer, NonTilingWindow, SplitContainer, TilingContainer,\r\n    WindowContainer,\r\n  },\r\n  traits::{\r\n    CommonGetters, PositionGetters, TilingDirectionGetters, WindowGetters,\r\n  },\r\n  user_config::UserConfig,\r\n  wm_state::WmState,\r\n};\r\n\r\n/// Handles the event for when a window is finished being moved or resized\r\n/// by the user (e.g. via the window's drag handles).\r\n///\r\n/// This resizes the window if it's a tiling window and attach a dragged\r\n/// floating window.\r\n///\r\n/// TODO: Move this to a better location - maybe a new `active_drag_ext`\r\n/// mod.\r\npub fn handle_window_moved_or_resized_end(\r\n  window: &WindowContainer,\r\n  state: &mut WmState,\r\n  config: &UserConfig,\r\n) -> anyhow::Result<()> {\r\n  let Some(active_drag) = window.active_drag() else {\r\n    return Ok(());\r\n  };\r\n\r\n  match &window {\r\n    WindowContainer::NonTilingWindow(window) => {\r\n      let is_maximized = try_warn!(window.native().is_maximized());\r\n\r\n      window.update_native_properties(|properties| {\r\n        properties.is_maximized = is_maximized;\r\n      });\r\n\r\n      let nearest_monitor = state\r\n        .nearest_monitor(&window.native())\r\n        .context(\"Failed to get workspace of nearest monitor.\")?;\r\n\r\n      let should_fullscreen = window.should_fullscreen(\r\n        &nearest_monitor\r\n          .displayed_workspace()\r\n          .context(\"No workspace.\")?,\r\n      )?;\r\n\r\n      if is_maximized || should_fullscreen {\r\n        let fullscreen_state = if let WindowState::Fullscreen(\r\n          fullscreen_state,\r\n        ) = window.state()\r\n        {\r\n          fullscreen_state\r\n        } else {\r\n          config\r\n            .value\r\n            .window_behavior\r\n            .state_defaults\r\n            .fullscreen\r\n            .clone()\r\n        };\r\n\r\n        let window = update_window_state(\r\n          window.clone().into(),\r\n          WindowState::Fullscreen(FullscreenStateConfig {\r\n            maximized: is_maximized,\r\n            ..fullscreen_state\r\n          }),\r\n          state,\r\n          config,\r\n        )?;\r\n\r\n        window.set_active_drag(None);\r\n\r\n        if is_maximized {\r\n          // Dequeue the window from redraw if it's maximized, since the\r\n          // window is already in the correct state.\r\n          state\r\n            .pending_sync\r\n            .dequeue_container_from_redraw(window.clone());\r\n        } else {\r\n          // Force a redraw to snap the window to the monitor edges.\r\n          // TODO: Skip redraw if it's already matches fullscreen frame.\r\n          state.pending_sync.queue_container_to_redraw(window.clone());\r\n        }\r\n\r\n        return Ok(());\r\n      }\r\n\r\n      if active_drag.is_from_floating {\r\n        update_floating_window_position(\r\n          window,\r\n          window.native_properties().frame,\r\n          &nearest_monitor,\r\n          state,\r\n        )?;\r\n        window.set_active_drag(None);\r\n      } else {\r\n        // Window is a temporary floating window that should be\r\n        // reverted back to tiling.\r\n        let window = drop_as_tiling_window(window, state, config)?;\r\n        window.set_active_drag(None);\r\n      }\r\n    }\r\n    WindowContainer::TilingWindow(window) => {\r\n      tracing::info!(\r\n        \"Tiling window move/resize ended: {}\",\r\n        window.as_window_container()?\r\n      );\r\n\r\n      let frame = window.native_properties().frame;\r\n\r\n      // Update the window's size based on the new frame position. This\r\n      // means we use the actual window dimensions as the source of truth.\r\n      set_window_size(\r\n        window.clone().into(),\r\n        Some(LengthValue::from_px(frame.width())),\r\n        Some(LengthValue::from_px(frame.height())),\r\n        state,\r\n      )?;\r\n\r\n      window.set_active_drag(None);\r\n\r\n      // Force a redraw of the window to snap it back to its original\r\n      // position. This is necessary when:\r\n      // - The window is the only tiling window in the workspace.\r\n      // - The window is not past the movement threshold for transitioning\r\n      //   to floating while being dragged.\r\n      // - Resizing in a direction that doesn't change the window's tiling\r\n      //   size.\r\n      state.pending_sync.queue_container_to_redraw(window.clone());\r\n    }\r\n  }\r\n\r\n  Ok(())\r\n}\r\n\r\n/// Handles transition from temporary floating window to tiling window on\r\n/// drag end.\r\n#[allow(clippy::too_many_lines)]\r\nfn drop_as_tiling_window(\r\n  moved_window: &NonTilingWindow,\r\n  state: &mut WmState,\r\n  config: &UserConfig,\r\n) -> anyhow::Result<WindowContainer> {\r\n  tracing::info!(\r\n    \"Tiling window drag ended: {}\",\r\n    moved_window.as_window_container()?\r\n  );\r\n\r\n  let mouse_pos = state.dispatcher.cursor_position()?;\r\n  let mouse_workspace = state\r\n    .monitor_at_point(&mouse_pos)\r\n    .and_then(|monitor| monitor.displayed_workspace())\r\n    .or_else(|| moved_window.workspace())\r\n    .context(\"Couldn't find workspace for window drop.\")?;\r\n\r\n  // Get the workspace, split containers, and other windows under the\r\n  // dragged window.\r\n  let containers_at_pos = state\r\n    .containers_at_point(&mouse_workspace.clone().into(), &mouse_pos)\r\n    .into_iter()\r\n    .filter(|container| container.id() != moved_window.id());\r\n\r\n  // Get the deepest direction container under the dragged window.\r\n  let target_parent: DirectionContainer = containers_at_pos\r\n    .filter_map(|container| container.as_direction_container().ok())\r\n    .fold(mouse_workspace.into(), |acc, container| {\r\n      if container.ancestors().count() > acc.ancestors().count() {\r\n        container\r\n      } else {\r\n        acc\r\n      }\r\n    });\r\n\r\n  // If the target parent has no children (i.e. an empty workspace), then\r\n  // add the window directly.\r\n  if target_parent.tiling_children().count() == 0 {\r\n    move_container_within_tree(\r\n      &moved_window.clone().into(),\r\n      &target_parent.clone().into(),\r\n      0,\r\n      state,\r\n    )?;\r\n\r\n    moved_window.set_insertion_target(None);\r\n\r\n    return update_window_state(\r\n      moved_window.as_window_container()?,\r\n      WindowState::Tiling,\r\n      state,\r\n      config,\r\n    );\r\n  }\r\n\r\n  let nearest_container = target_parent\r\n    .children()\r\n    .into_iter()\r\n    .filter_map(|container| container.as_tiling_container().ok())\r\n    .try_fold(None, |acc: Option<TilingContainer>, container| match acc {\r\n      Some(acc) => {\r\n        let is_nearer = acc.to_rect()?.distance_to_point(&mouse_pos)\r\n          < container.to_rect()?.distance_to_point(&mouse_pos);\r\n\r\n        anyhow::Ok(Some(if is_nearer { acc } else { container }))\r\n      }\r\n      None => Ok(Some(container)),\r\n    })?\r\n    .context(\"No nearest container.\")?;\r\n\r\n  let tiling_direction = target_parent.tiling_direction();\r\n  let drop_position =\r\n    drop_position(&mouse_pos, &nearest_container.to_rect()?);\r\n\r\n  let moved_window = update_window_state(\r\n    moved_window.clone().into(),\r\n    WindowState::Tiling,\r\n    state,\r\n    config,\r\n  )?;\r\n\r\n  let should_split = nearest_container.is_tiling_window()\r\n    && match tiling_direction {\r\n      TilingDirection::Horizontal => {\r\n        drop_position == DropPosition::Top\r\n          || drop_position == DropPosition::Bottom\r\n      }\r\n      TilingDirection::Vertical => {\r\n        drop_position == DropPosition::Left\r\n          || drop_position == DropPosition::Right\r\n      }\r\n    };\r\n\r\n  if should_split {\r\n    let split_container = SplitContainer::new(\r\n      tiling_direction.inverse(),\r\n      config.value.gaps.clone(),\r\n    );\r\n\r\n    wrap_in_split_container(\r\n      &split_container,\r\n      &target_parent.clone().into(),\r\n      &[nearest_container],\r\n    )?;\r\n\r\n    let target_index = match drop_position {\r\n      DropPosition::Top | DropPosition::Left => 0,\r\n      _ => 1,\r\n    };\r\n\r\n    move_container_within_tree(\r\n      &moved_window.clone().into(),\r\n      &split_container.into(),\r\n      target_index,\r\n      state,\r\n    )?;\r\n  } else {\r\n    let target_index = match drop_position {\r\n      DropPosition::Top | DropPosition::Left => nearest_container.index(),\r\n      _ => nearest_container.index() + 1,\r\n    };\r\n\r\n    move_container_within_tree(\r\n      &moved_window.clone().into(),\r\n      &target_parent.clone().into(),\r\n      target_index,\r\n      state,\r\n    )?;\r\n  }\r\n\r\n  state.pending_sync.queue_container_to_redraw(target_parent);\r\n\r\n  Ok(moved_window)\r\n}\r\n\r\n/// Represents where the window was dropped over another.\r\n#[derive(Debug, Clone, PartialEq)]\r\nenum DropPosition {\r\n  Top,\r\n  Bottom,\r\n  Left,\r\n  Right,\r\n}\r\n\r\n/// Gets the drop position for a window based on the mouse position.\r\n///\r\n/// This approach divides the window rect into an \"X\", creating four\r\n/// triangular quadrants, to determine which side the cursor is closest to.\r\nfn drop_position(mouse_pos: &Point, rect: &Rect) -> DropPosition {\r\n  let delta_x = mouse_pos.x - rect.center_point().x;\r\n  let delta_y = mouse_pos.y - rect.center_point().y;\r\n\r\n  if delta_x.abs() > delta_y.abs() {\r\n    // Window is in the left or right triangle.\r\n    if delta_x > 0 {\r\n      DropPosition::Right\r\n    } else {\r\n      DropPosition::Left\r\n    }\r\n  } else {\r\n    // Window is in the top or bottom triangle.\r\n    if delta_y > 0 {\r\n      DropPosition::Bottom\r\n    } else {\r\n      DropPosition::Top\r\n    }\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/events/handle_window_shown.rs",
    "content": "use tracing::info;\r\nuse wm_common::{DisplayState, HideMethod};\r\nuse wm_platform::NativeWindow;\r\n\r\nuse crate::{\r\n  commands::window::manage_window, traits::WindowGetters,\r\n  user_config::UserConfig, wm_state::WmState,\r\n};\r\n\r\npub fn handle_window_shown(\r\n  native_window: NativeWindow,\r\n  state: &mut WmState,\r\n  config: &mut UserConfig,\r\n) -> anyhow::Result<()> {\r\n  let found_window = state.window_from_native(&native_window);\r\n\r\n  if let Some(window) = found_window {\r\n    info!(\"Window shown: {window}\");\r\n\r\n    // Update display state if window is already managed.\r\n    if config.value.general.hide_method != HideMethod::PlaceInCorner\r\n      && window.display_state() == DisplayState::Showing\r\n    {\r\n      window.set_display_state(DisplayState::Shown);\r\n    } else {\r\n      state.pending_sync.queue_container_to_redraw(window);\r\n    }\r\n  } else {\r\n    // If the window is not managed, manage it.\r\n    manage_window(native_window, None, state, config)?;\r\n  }\r\n\r\n  Ok(())\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/events/handle_window_title_changed.rs",
    "content": "use tracing::info;\r\nuse wm_common::{try_warn, WindowRuleEvent};\r\nuse wm_platform::NativeWindow;\r\n\r\nuse crate::{\r\n  commands::window::run_window_rules, traits::WindowGetters,\r\n  user_config::UserConfig, wm_state::WmState,\r\n};\r\n\r\npub fn handle_window_title_changed(\r\n  native_window: &NativeWindow,\r\n  state: &mut WmState,\r\n  config: &mut UserConfig,\r\n) -> anyhow::Result<()> {\r\n  let found_window = state.window_from_native(native_window);\r\n\r\n  if let Some(window) = found_window {\r\n    info!(\"Window title changed: {window}\");\r\n\r\n    let title = try_warn!(window.native().title());\r\n\r\n    window.update_native_properties(|properties| {\r\n      properties.title = title;\r\n    });\r\n\r\n    // Run window rules for title change events.\r\n    run_window_rules(\r\n      window,\r\n      &WindowRuleEvent::TitleChange,\r\n      state,\r\n      config,\r\n    )?;\r\n  }\r\n\r\n  Ok(())\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/events/mod.rs",
    "content": "mod handle_display_settings_changed;\r\nmod handle_mouse_move;\r\nmod handle_window_destroyed;\r\nmod handle_window_focused;\r\nmod handle_window_hidden;\r\nmod handle_window_minimize_ended;\r\nmod handle_window_minimized;\r\nmod handle_window_moved_or_resized;\r\nmod handle_window_moved_or_resized_end;\r\nmod handle_window_shown;\r\nmod handle_window_title_changed;\r\n\r\npub use handle_display_settings_changed::*;\r\npub use handle_mouse_move::*;\r\npub use handle_window_destroyed::*;\r\npub use handle_window_focused::*;\r\npub use handle_window_hidden::*;\r\npub use handle_window_minimize_ended::*;\r\npub use handle_window_minimized::*;\r\npub use handle_window_moved_or_resized::*;\r\npub use handle_window_moved_or_resized_end::*;\r\npub use handle_window_shown::*;\r\npub use handle_window_title_changed::*;\r\n"
  },
  {
    "path": "packages/wm/src/ipc_server.rs",
    "content": "use std::{iter, net::SocketAddr};\r\n\r\nuse anyhow::{bail, Context};\r\nuse clap::Parser;\r\nuse futures_util::{SinkExt, StreamExt};\r\nuse tokio::{\r\n  net::{TcpListener, TcpStream},\r\n  sync::{broadcast, mpsc},\r\n  task,\r\n};\r\nuse tokio_tungstenite::{accept_async, tungstenite::Message};\r\nuse tracing::{info, warn};\r\nuse uuid::Uuid;\r\nuse wm_common::{\r\n  AppCommand, AppMetadataData, BindingModesData, ClientResponseData,\r\n  ClientResponseMessage, CommandData, EventSubscribeData,\r\n  EventSubscriptionMessage, FocusedData, MonitorsData, QueryCommand,\r\n  ServerMessage, SubscribableEvent, TilingDirectionData, WindowsData,\r\n  WmEvent, WorkspacesData, DEFAULT_IPC_PORT,\r\n};\r\n\r\nuse crate::{\r\n  traits::{CommonGetters, TilingDirectionGetters},\r\n  user_config::UserConfig,\r\n  wm::WindowManager,\r\n};\r\n\r\npub struct IpcServer {\r\n  abort_handle: task::AbortHandle,\r\n  pub message_rx: mpsc::UnboundedReceiver<(\r\n    String,\r\n    mpsc::UnboundedSender<Message>,\r\n    broadcast::Sender<()>,\r\n  )>,\r\n  _event_rx: broadcast::Receiver<(SubscribableEvent, WmEvent)>,\r\n  event_tx: broadcast::Sender<(SubscribableEvent, WmEvent)>,\r\n  _unsubscribe_rx: broadcast::Receiver<Uuid>,\r\n  unsubscribe_tx: broadcast::Sender<Uuid>,\r\n}\r\n\r\nimpl IpcServer {\r\n  pub async fn start() -> anyhow::Result<Self> {\r\n    let (message_tx, message_rx) = mpsc::unbounded_channel();\r\n    let (event_tx, _event_rx) = broadcast::channel(16);\r\n    let (unsubscribe_tx, _unsubscribe_rx) = broadcast::channel(16);\r\n\r\n    let server_addr = format!(\"127.0.0.1:{DEFAULT_IPC_PORT}\");\r\n    let server = TcpListener::bind(server_addr.clone()).await?;\r\n    info!(\"IPC server started on: '{}'.\", server_addr);\r\n\r\n    let task = task::spawn(async move {\r\n      while let Ok((stream, addr)) = server.accept().await {\r\n        let message_tx = message_tx.clone();\r\n\r\n        task::spawn(async move {\r\n          if let Err(err) =\r\n            Self::handle_connection(stream, addr, message_tx).await\r\n          {\r\n            warn!(\"Error handling connection: {}\", err);\r\n          }\r\n        });\r\n      }\r\n    });\r\n\r\n    Ok(Self {\r\n      abort_handle: task.abort_handle(),\r\n      #[allow(clippy::used_underscore_binding)]\r\n      _event_rx,\r\n      event_tx,\r\n      message_rx,\r\n      unsubscribe_tx,\r\n      #[allow(clippy::used_underscore_binding)]\r\n      _unsubscribe_rx,\r\n    })\r\n  }\r\n\r\n  async fn handle_connection(\r\n    stream: TcpStream,\r\n    addr: SocketAddr,\r\n    message_tx: mpsc::UnboundedSender<(\r\n      String,\r\n      mpsc::UnboundedSender<Message>,\r\n      broadcast::Sender<()>,\r\n    )>,\r\n  ) -> anyhow::Result<()> {\r\n    info!(\"Incoming IPC connection from: {}.\", addr);\r\n\r\n    let ws_stream = accept_async(stream)\r\n      .await\r\n      .context(\"Error during websocket handshake.\")?;\r\n\r\n    let (mut outgoing, mut incoming) = ws_stream.split();\r\n    let (response_tx, mut response_rx) = mpsc::unbounded_channel();\r\n    let (disconnection_tx, _) = broadcast::channel(16);\r\n\r\n    let res = async {\r\n      loop {\r\n        tokio::select! {\r\n          Some(response) = response_rx.recv() => {\r\n            outgoing.send(response).await?;\r\n          }\r\n          message = incoming.next() => {\r\n            match message {\r\n              Some(Ok(message)) => {\r\n                if message.is_text() || message.is_binary() {\r\n                  message_tx.send((\r\n                    message.to_text()?.to_string(),\r\n                    response_tx.clone(),\r\n                    disconnection_tx.clone(),\r\n                  ))?;\r\n                }\r\n              }\r\n              Some(Err(err)) => bail!(\"WebSocket error: {}\", err),\r\n              None => {\r\n                // WebSocket connection closed.\r\n                break Ok(());\r\n              },\r\n            }\r\n          }\r\n        }\r\n      }\r\n    }\r\n    .await;\r\n\r\n    info!(\"IPC disconnection from: {}.\", addr);\r\n\r\n    if let Err(err) = disconnection_tx.send(()) {\r\n      warn!(\"Failed to broadcast disconnection: {}\", err);\r\n    }\r\n\r\n    res\r\n  }\r\n\r\n  pub fn process_message(\r\n    &self,\r\n    message: String,\r\n    response_tx: &mpsc::UnboundedSender<Message>,\r\n    disconnection_tx: &broadcast::Sender<()>,\r\n    wm: &mut WindowManager,\r\n    config: &mut UserConfig,\r\n  ) -> anyhow::Result<()> {\r\n    let app_command = AppCommand::try_parse_from(\r\n      iter::once(\"\").chain(message.split_whitespace()),\r\n    );\r\n\r\n    let response_data =\r\n      app_command\r\n        .map_err(anyhow::Error::msg)\r\n        .and_then(|app_command| {\r\n          self.handle_app_command(\r\n            app_command,\r\n            response_tx,\r\n            disconnection_tx,\r\n            wm,\r\n            config,\r\n          )\r\n        });\r\n\r\n    // Respond to the client with the result of the command.\r\n    response_tx\r\n      .send(Self::to_client_response_msg(message, response_data)?)\r\n      .map_err(|err| {\r\n        anyhow::anyhow!(\"Failed to send response: {}\", err)\r\n      })?;\r\n\r\n    Ok(())\r\n  }\r\n\r\n  #[allow(clippy::too_many_lines)]\r\n  fn handle_app_command(\r\n    &self,\r\n    app_command: AppCommand,\r\n    response_tx: &mpsc::UnboundedSender<Message>,\r\n    disconnection_tx: &broadcast::Sender<()>,\r\n    wm: &mut WindowManager,\r\n    config: &mut UserConfig,\r\n  ) -> anyhow::Result<ClientResponseData> {\r\n    let response_data = match app_command {\r\n      AppCommand::Query { command } => match command {\r\n        QueryCommand::Windows => {\r\n          ClientResponseData::Windows(WindowsData {\r\n            windows: wm\r\n              .state\r\n              .windows()\r\n              .into_iter()\r\n              .map(|window| window.to_dto())\r\n              .try_collect()?,\r\n          })\r\n        }\r\n        QueryCommand::Workspaces => {\r\n          ClientResponseData::Workspaces(WorkspacesData {\r\n            workspaces: wm\r\n              .state\r\n              .workspaces()\r\n              .into_iter()\r\n              .map(|workspace| workspace.to_dto())\r\n              .try_collect()?,\r\n          })\r\n        }\r\n        QueryCommand::Monitors => {\r\n          ClientResponseData::Monitors(MonitorsData {\r\n            monitors: wm\r\n              .state\r\n              .monitors()\r\n              .into_iter()\r\n              .map(|monitor| monitor.to_dto())\r\n              .try_collect()?,\r\n          })\r\n        }\r\n        QueryCommand::BindingModes => {\r\n          ClientResponseData::BindingModes(BindingModesData {\r\n            binding_modes: wm.state.binding_modes.clone(),\r\n          })\r\n        }\r\n        QueryCommand::Focused => {\r\n          let focused_container = wm\r\n            .state\r\n            .focused_container()\r\n            .context(\"No focused container.\")?;\r\n\r\n          ClientResponseData::Focused(FocusedData {\r\n            focused: focused_container.to_dto()?,\r\n          })\r\n        }\r\n        QueryCommand::AppMetadata => {\r\n          ClientResponseData::AppMetadata(AppMetadataData {\r\n            version: env!(\"VERSION_NUMBER\").to_string(),\r\n          })\r\n        }\r\n        QueryCommand::TilingDirection => {\r\n          let direction_container = wm\r\n            .state\r\n            .focused_container()\r\n            .and_then(|focused| focused.direction_container())\r\n            .context(\"No direction container.\")?;\r\n\r\n          ClientResponseData::TilingDirection(TilingDirectionData {\r\n            direction_container: direction_container.to_dto()?,\r\n            tiling_direction: direction_container.tiling_direction(),\r\n          })\r\n        }\r\n        QueryCommand::Paused => {\r\n          ClientResponseData::Paused(wm.state.is_paused)\r\n        }\r\n      },\r\n      AppCommand::Command {\r\n        subject_container_id,\r\n        command,\r\n      } => {\r\n        let subject_container_id = wm.process_commands(\r\n          &vec![command],\r\n          subject_container_id,\r\n          config,\r\n        )?;\r\n\r\n        ClientResponseData::Command(CommandData {\r\n          subject_container_id,\r\n        })\r\n      }\r\n      AppCommand::Sub { events } => {\r\n        let subscription_id = Uuid::new_v4();\r\n        info!(\"New event subscription {}: {:?}\", subscription_id, events);\r\n\r\n        let response_tx = response_tx.clone();\r\n        let mut event_rx = self.event_tx.subscribe();\r\n        let mut unsubscribe_rx = self.unsubscribe_tx.subscribe();\r\n        let mut disconnection_rx = disconnection_tx.subscribe();\r\n\r\n        task::spawn(async move {\r\n          loop {\r\n            tokio::select! {\r\n              Ok(()) = disconnection_rx.recv() => {\r\n                break;\r\n              }\r\n              Ok(id) = unsubscribe_rx.recv() => {\r\n                if id == subscription_id {\r\n                  break;\r\n                }\r\n              }\r\n              Ok((event_type, event)) = event_rx.recv() => {\r\n                // Check whether the event is one of the subscribed events.\r\n                if events.contains(&event_type)\r\n                  || events.contains(&SubscribableEvent::All)\r\n                {\r\n                  let send_result = Self::to_event_subscription_msg(\r\n                    subscription_id,\r\n                    event,\r\n                  )\r\n                  .and_then(|event_msg| {\r\n                    response_tx\r\n                      .send(event_msg)\r\n                      .map_err(anyhow::Error::from)\r\n                  });\r\n\r\n                  if let Err(err) = send_result {\r\n                    warn!(\"Error emitting WM event: {}\", err);\r\n                    break;\r\n                  }\r\n                }\r\n              }\r\n            }\r\n          }\r\n        });\r\n\r\n        ClientResponseData::EventSubscribe(EventSubscribeData {\r\n          subscription_id,\r\n        })\r\n      }\r\n      AppCommand::Unsub { subscription_id } => {\r\n        self\r\n          .unsubscribe_tx\r\n          .send(subscription_id)\r\n          .context(\"Failed to unsubscribe from event.\")?;\r\n\r\n        ClientResponseData::EventUnsubscribe\r\n      }\r\n      AppCommand::Start { .. } => bail!(\"Unsupported IPC command.\"),\r\n    };\r\n\r\n    Ok(response_data)\r\n  }\r\n\r\n  fn to_client_response_msg(\r\n    client_message: String,\r\n    response_data: anyhow::Result<ClientResponseData>,\r\n  ) -> anyhow::Result<Message> {\r\n    let error = response_data.as_ref().err().map(ToString::to_string);\r\n    let success = response_data.as_ref().is_ok();\r\n\r\n    let message = ServerMessage::ClientResponse(ClientResponseMessage {\r\n      client_message,\r\n      data: response_data.ok(),\r\n      error,\r\n      success,\r\n    });\r\n\r\n    let message_json = serde_json::to_string(&message)?;\r\n    Ok(Message::Text(message_json.into()))\r\n  }\r\n\r\n  fn to_event_subscription_msg(\r\n    subscription_id: Uuid,\r\n    event: WmEvent,\r\n  ) -> anyhow::Result<Message> {\r\n    let message =\r\n      ServerMessage::EventSubscription(EventSubscriptionMessage {\r\n        data: Some(event),\r\n        error: None,\r\n        subscription_id,\r\n        success: true,\r\n      });\r\n\r\n    let message_json = serde_json::to_string(&message)?;\r\n    Ok(Message::Text(message_json.into()))\r\n  }\r\n\r\n  pub fn process_event(&mut self, event: WmEvent) -> anyhow::Result<()> {\r\n    let event_type = match event {\r\n      WmEvent::ApplicationExiting => SubscribableEvent::ApplicationExiting,\r\n      WmEvent::BindingModesChanged { .. } => {\r\n        SubscribableEvent::BindingModesChanged\r\n      }\r\n      WmEvent::FocusChanged { .. } => SubscribableEvent::FocusChanged,\r\n      WmEvent::FocusedContainerMoved { .. } => {\r\n        SubscribableEvent::FocusedContainerMoved\r\n      }\r\n      WmEvent::MonitorAdded { .. } => SubscribableEvent::MonitorAdded,\r\n      WmEvent::MonitorUpdated { .. } => SubscribableEvent::MonitorUpdated,\r\n      WmEvent::MonitorRemoved { .. } => SubscribableEvent::MonitorRemoved,\r\n      WmEvent::TilingDirectionChanged { .. } => {\r\n        SubscribableEvent::TilingDirectionChanged\r\n      }\r\n      WmEvent::UserConfigChanged { .. } => {\r\n        SubscribableEvent::UserConfigChanged\r\n      }\r\n      WmEvent::WindowManaged { .. } => SubscribableEvent::WindowManaged,\r\n      WmEvent::WindowUnmanaged { .. } => {\r\n        SubscribableEvent::WindowUnmanaged\r\n      }\r\n      WmEvent::WorkspaceActivated { .. } => {\r\n        SubscribableEvent::WorkspaceActivated\r\n      }\r\n      WmEvent::WorkspaceDeactivated { .. } => {\r\n        SubscribableEvent::WorkspaceDeactivated\r\n      }\r\n      WmEvent::WorkspaceUpdated { .. } => {\r\n        SubscribableEvent::WorkspaceUpdated\r\n      }\r\n      WmEvent::PauseChanged { .. } => SubscribableEvent::PauseChanged,\r\n    };\r\n\r\n    self\r\n      .event_tx\r\n      .send((event_type, event))\r\n      .map_err(|err| anyhow::anyhow!(\"Failed to send event: {}\", err))?;\r\n\r\n    Ok(())\r\n  }\r\n\r\n  pub fn stop(&self) {\r\n    info!(\"Shutting down IPC server.\");\r\n    self.abort_handle.abort();\r\n  }\r\n}\r\n\r\nimpl Drop for IpcServer {\r\n  fn drop(&mut self) {\r\n    self.stop();\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/main.rs",
    "content": "// The `windows` or `console` subsystem (default is `console`) determines\r\n// whether a console window is spawned on launch, if not already ran\r\n// through a console. The following prevents this additional console window\r\n// in release mode.\r\n#![cfg_attr(\r\n  all(not(debug_assertions), target_os = \"windows\"),\r\n  windows_subsystem = \"windows\"\r\n)]\r\n#![warn(clippy::all, clippy::pedantic)]\r\n#![feature(iterator_try_collect)]\r\n\r\n#[cfg(target_os = \"macos\")]\r\nuse std::io::IsTerminal;\r\nuse std::{env, path::PathBuf, process, time::Duration};\r\n\r\nuse anyhow::{Context, Error};\r\nuse tokio::{process::Command, signal};\r\nuse tracing::Level;\r\nuse tracing_subscriber::{\r\n  fmt::{self, writer::MakeWriterExt},\r\n  layer::SubscriberExt,\r\n};\r\nuse wm_common::{AppCommand, InvokeCommand, Verbosity, WmEvent};\r\n#[cfg(target_os = \"macos\")]\r\nuse wm_platform::DispatcherExtMacOs;\r\nuse wm_platform::{\r\n  Dispatcher, DisplayListener, EventLoop, KeybindingListener,\r\n  MouseEventKind, MouseListener, PlatformEvent, SingleInstance,\r\n  WindowListener,\r\n};\r\n\r\nuse crate::{\r\n  ipc_server::IpcServer, sys_tray::SystemTray, user_config::UserConfig,\r\n  wm::WindowManager,\r\n};\r\n\r\nmod commands;\r\nmod events;\r\nmod ipc_server;\r\nmod models;\r\nmod pending_sync;\r\nmod sys_tray;\r\nmod traits;\r\nmod user_config;\r\nmod wm;\r\nmod wm_state;\r\n\r\n/// Main entry point for the application.\r\n///\r\n/// Conditionally starts the WM or runs a CLI command based on the given\r\n/// subcommand.\r\nfn main() -> anyhow::Result<()> {\r\n  let args = std::env::args().collect::<Vec<_>>();\r\n  let app_command = AppCommand::parse_with_default(&args);\r\n\r\n  if let AppCommand::Start {\r\n    config_path,\r\n    verbosity,\r\n  } = app_command\r\n  {\r\n    let rt = tokio::runtime::Runtime::new()?;\r\n    let (event_loop, dispatcher) = EventLoop::new()?;\r\n\r\n    let task_handle = std::thread::spawn(move || {\r\n      rt.block_on(async {\r\n        let start_res =\r\n          start_wm(config_path, verbosity, &dispatcher).await;\r\n\r\n        if let Err(err) = &start_res {\r\n          // If unable to start the WM, the error is fatal and a message\r\n          // dialog is shown.\r\n          tracing::error!(\"{:?}\", err);\r\n          dispatcher.show_error_dialog(\"Fatal error\", &err.to_string());\r\n        }\r\n\r\n        if let Err(err) = dispatcher.stop_event_loop() {\r\n          // Forcefully exit the process to ensure the event loop is\r\n          // stopped.\r\n          tracing::error!(\"Failed to stop event loop gracefully: {}\", err);\r\n          process::exit(1);\r\n        }\r\n\r\n        start_res\r\n      })\r\n    });\r\n\r\n    // Run event loop (blocks until shutdown). This must be on the main\r\n    // thread for macOS compatibility.\r\n    event_loop.run()?;\r\n\r\n    // Wait for clean exit of the WM.\r\n    task_handle.join().unwrap()\r\n  } else {\r\n    let rt = tokio::runtime::Runtime::new()?;\r\n    rt.block_on(wm_cli::start(args))\r\n  }\r\n}\r\n\r\n#[allow(clippy::too_many_lines)]\r\nasync fn start_wm(\r\n  config_path: Option<PathBuf>,\r\n  verbosity: Verbosity,\r\n  dispatcher: &Dispatcher,\r\n) -> anyhow::Result<()> {\r\n  setup_logging(&verbosity)?;\r\n\r\n  // Ensure that only one instance of the WM is running.\r\n  let _single_instance = SingleInstance::new()?;\r\n\r\n  #[cfg(target_os = \"macos\")]\r\n  {\r\n    if !dispatcher.has_ax_permission(true) {\r\n      anyhow::bail!(\r\n        \"Accessibility permissions are not granted. In System Preferences, \\\r\n         go to Privacy & Security > Accessibility and enable GlazeWM.\"\r\n      );\r\n    }\r\n  }\r\n\r\n  // Parse and validate user config.\r\n  let mut config = UserConfig::new(config_path)?;\r\n\r\n  // Add application icon to system tray.\r\n  let mut tray = SystemTray::new(&config.path, dispatcher.clone())?;\r\n\r\n  let mut wm = WindowManager::new(&mut config, dispatcher.clone())?;\r\n\r\n  let mut ipc_server = IpcServer::start().await?;\r\n\r\n  // On Windows, start watcher process for restoring hidden windows on\r\n  // crash. macOS' hidden windows are always accessible.\r\n  #[cfg(target_os = \"windows\")]\r\n  if let Err(err) = start_watcher_process() {\r\n    tracing::warn!(\r\n      \"Failed to start watcher process: {err}{}\",\r\n      cfg!(debug_assertions)\r\n        .then_some(\".\\n Run `cargo build -p wm-watcher` to build it.\")\r\n        .unwrap_or_default()\r\n    );\r\n  }\r\n\r\n  // On macOS, update the current process' PATH variable so that\r\n  // `shell-exec` can resolve programs defined in the shell's PATH. Skip if\r\n  // running via a terminal.\r\n  #[cfg(target_os = \"macos\")]\r\n  if !std::io::stdin().is_terminal() {\r\n    update_path_env();\r\n  }\r\n\r\n  // Start listening for platform events after populating initial state.\r\n  let mut window_listener = WindowListener::new(dispatcher)?;\r\n  let mut display_listener = DisplayListener::new(dispatcher)?;\r\n  let mut mouse_listener = MouseListener::new(\r\n    if config.value.general.focus_follows_cursor {\r\n      &[MouseEventKind::Move, MouseEventKind::LeftButtonUp]\r\n    } else {\r\n      &[MouseEventKind::LeftButtonUp]\r\n    },\r\n    dispatcher,\r\n  )?;\r\n  let mut keybinding_listener = KeybindingListener::new(\r\n    &config\r\n      .active_keybinding_configs(&[], false)\r\n      .flat_map(|kb| kb.bindings)\r\n      .collect::<Vec<_>>(),\r\n    dispatcher,\r\n  )?;\r\n\r\n  // Run user's startup commands.\r\n  if let Err(err) = wm.process_commands(\r\n    &config.value.general.startup_commands.clone(),\r\n    None,\r\n    &mut config,\r\n  ) {\r\n    tracing::error!(\"{:?}\", err);\r\n    dispatcher.show_error_dialog(\"Non-fatal error\", &err.to_string());\r\n  }\r\n\r\n  // Create an interval for periodically cleaning up invalid windows.\r\n  let mut cleanup_interval = tokio::time::interval(Duration::from_secs(5));\r\n\r\n  loop {\r\n    let res = tokio::select! {\r\n      _ = signal::ctrl_c() => {\r\n        tracing::info!(\"Received SIGINT signal.\");\r\n        break;\r\n      },\r\n      Some(()) = wm.exit_rx.recv() => {\r\n        tracing::info!(\"Exiting through WM command.\");\r\n        break;\r\n      },\r\n      Some(()) = tray.exit_rx.recv() => {\r\n        tracing::info!(\"Exiting through system tray.\");\r\n        break;\r\n      },\r\n      Some(event) = mouse_listener.next_event() => {\r\n        tracing::debug!(\"Received mouse event: {:?}\", event);\r\n        wm.process_event(PlatformEvent::Mouse(event), &mut config)\r\n      },\r\n      Some(event) = window_listener.next_event() => {\r\n        tracing::debug!(\"Received window event: {:?}\", event);\r\n        wm.process_event(PlatformEvent::Window(event), &mut config)\r\n      },\r\n      Some(()) = display_listener.next_event() => {\r\n        tracing::debug!(\"Received display settings changed event.\");\r\n        wm.process_event(PlatformEvent::DisplaySettingsChanged, &mut config)\r\n      },\r\n      Some(event) = keybinding_listener.next_event() => {\r\n        tracing::debug!(\"Received keyboard event: {:?}\", event);\r\n        wm.process_event(PlatformEvent::Keybinding(event), &mut config)\r\n      }\r\n      _ = cleanup_interval.tick() => {\r\n        if wm.state.is_paused {\r\n          Ok(())\r\n        } else {\r\n          wm.state.cleanup_invalid_windows()\r\n        }\r\n      },\r\n      Some((\r\n        message,\r\n        response_tx,\r\n        disconnection_tx\r\n      )) = ipc_server.message_rx.recv() => {\r\n        tracing::info!(\"Received IPC message: {:?}\", message);\r\n\r\n        if let Err(err) = ipc_server.process_message(\r\n          message,\r\n          &response_tx,\r\n          &disconnection_tx,\r\n          &mut wm,\r\n          &mut config,\r\n        ) {\r\n          tracing::error!(\"{:?}\", err);\r\n        }\r\n\r\n        Ok(())\r\n      },\r\n      Some(wm_event) = wm.event_rx.recv() => {\r\n        tracing::debug!(\"Received WM event: {:?}\", wm_event);\r\n\r\n        // Disable mouse listener when the WM is paused.\r\n        if let WmEvent::PauseChanged { is_paused } = wm_event {\r\n          let _ = mouse_listener.enable(!is_paused);\r\n        }\r\n\r\n        // Update keybinding and mouse listeners on config changes.\r\n        if matches!(\r\n          wm_event,\r\n          WmEvent::UserConfigChanged { .. }\r\n            | WmEvent::BindingModesChanged { .. }\r\n            | WmEvent::PauseChanged { .. }\r\n        ) {\r\n          keybinding_listener.update(\r\n            &config\r\n              .active_keybinding_configs(&wm.state.binding_modes, false)\r\n              .flat_map(|kb| kb.bindings)\r\n              .collect::<Vec<_>>(),\r\n          );\r\n\r\n          mouse_listener.set_enabled_events(\r\n            if config.value.general.focus_follows_cursor {\r\n              &[MouseEventKind::Move, MouseEventKind::LeftButtonUp]\r\n            } else {\r\n              &[MouseEventKind::LeftButtonUp]\r\n            },\r\n          )?;\r\n        }\r\n\r\n        if let Err(err) = ipc_server.process_event(wm_event) {\r\n          tracing::error!(\"{:?}\", err);\r\n        }\r\n\r\n        Ok(())\r\n      },\r\n      Some(()) = tray.config_reload_rx.recv() => {\r\n        wm.process_commands(\r\n          &vec![InvokeCommand::WmReloadConfig],\r\n          None,\r\n          &mut config,\r\n        ).map(|_| ())\r\n      },\r\n    };\r\n\r\n    if let Err(err) = res {\r\n      tracing::error!(\"{:?}\", err);\r\n      dispatcher.show_error_dialog(\"Non-fatal error\", &err.to_string());\r\n    }\r\n  }\r\n\r\n  tracing::info!(\"Window manager shutting down.\");\r\n  wm.cleanup(&mut config, &mut ipc_server);\r\n\r\n  Ok(())\r\n}\r\n\r\n/// Initialize logging with the specified verbosity level.\r\n///\r\n/// Error logs are saved to `~/.glzr/glazewm/errors.log`.\r\nfn setup_logging(verbosity: &Verbosity) -> anyhow::Result<()> {\r\n  let error_log_dir = home::home_dir()\r\n    .context(\"Unable to get home directory.\")?\r\n    .join(\".glzr/glazewm/\");\r\n\r\n  let error_writer =\r\n    tracing_appender::rolling::never(error_log_dir, \"errors.log\");\r\n\r\n  let subscriber = tracing_subscriber::registry()\r\n    .with(\r\n      // Output to stdout with specified verbosity level.\r\n      fmt::Layer::new()\r\n        .with_writer(std::io::stdout.with_max_level(verbosity.level())),\r\n    )\r\n    .with(\r\n      // Output to error log file.\r\n      fmt::Layer::new()\r\n        .with_writer(error_writer.with_max_level(Level::ERROR)),\r\n    );\r\n\r\n  tracing::subscriber::set_global_default(subscriber)?;\r\n\r\n  tracing::info!(\r\n    \"Starting WM with log level {:?}.\",\r\n    verbosity.level().to_string()\r\n  );\r\n\r\n  Ok(())\r\n}\r\n\r\n/// Launches watcher binary (Windows-only). This is a separate process that\r\n/// is responsible for restoring hidden windows in case the main WM process\r\n/// crashes.\r\n///\r\n/// This assumes the watcher binary exists in the same directory as the\r\n/// WM binary.\r\n#[allow(unused)]\r\nfn start_watcher_process() -> anyhow::Result<tokio::process::Child, Error>\r\n{\r\n  let watcher_path = env::current_exe()?\r\n    .parent()\r\n    .context(\"Failed to resolve path to the watcher process.\")?\r\n    .join(\"glazewm-watcher\");\r\n\r\n  Command::new(&watcher_path)\r\n    .spawn()\r\n    .context(\"Failed to start watcher process.\")\r\n}\r\n\r\n/// Updates the current process' PATH by querying the login shell.\r\n///\r\n/// Apps launched outside a terminal (Spotlight, Finder, login items)\r\n/// inherit a PATH that only contains `/usr/bin:/bin:/usr/sbin:/sbin`. This\r\n/// causes `shell-exec` to fail for binaries that aren't in the system\r\n/// PATH.\r\n#[cfg(target_os = \"macos\")]\r\nfn update_path_env() {\r\n  let shell =\r\n    std::env::var(\"SHELL\").unwrap_or_else(|_| \"/bin/zsh\".to_string());\r\n\r\n  // Use `-l` and `-i` (login + interactive) so that both profile and rc\r\n  // files are sourced.\r\n  let path_var = match std::process::Command::new(&shell)\r\n    .args([\"-lic\", \"printf '%s' \\\"$PATH\\\"\"])\r\n    .output()\r\n  {\r\n    Ok(output) if output.status.success() => {\r\n      String::from_utf8(output.stdout)\r\n        .ok()\r\n        .filter(|path| !path.is_empty())\r\n    }\r\n    _ => None,\r\n  };\r\n\r\n  if let Some(path) = path_var {\r\n    std::env::set_var(\"PATH\", path);\r\n  } else {\r\n    tracing::warn!(\r\n      \"Failed to query login shell for PATH. Keeping existing PATH.\"\r\n    );\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/models/container.rs",
    "content": "use std::{\r\n  cell::{Ref, RefMut},\r\n  collections::VecDeque,\r\n};\r\n\r\nuse ambassador::Delegate;\r\nuse enum_as_inner::EnumAsInner;\r\nuse uuid::Uuid;\r\nuse wm_common::{\r\n  ActiveDrag, ContainerDto, DisplayState, GapsConfig, TilingDirection,\r\n  WindowRuleConfig, WindowState,\r\n};\r\nuse wm_platform::{Direction, NativeWindow, Rect, RectDelta};\r\n\r\n#[allow(clippy::wildcard_imports)]\r\nuse crate::{\r\n  models::{\r\n    Monitor, NativeWindowProperties, NonTilingWindow, RootContainer,\r\n    SplitContainer, TilingWindow, Workspace,\r\n  },\r\n  traits::*,\r\n  user_config::UserConfig,\r\n};\r\n\r\n/// A container of any type.\r\n///\r\n/// Uses:\r\n///\r\n///  * [`wm_macros::SubEnum`] to define subtypes of containers.\r\n///  * [`wm_macros::EnumFromInner`] to define conversions between the enum\r\n///    and wrapped types.\r\n///  * [`ambassador::Delegate`] to delegate common getters to the contained\r\n///    types. E.g. implements [`CommonGetters`] for [Container] by\r\n///    forwarding the call to the item contained in the enum variant.\r\n///\r\n/// # Example\r\n/// Conversion between the different container types:\r\n/// ```\r\n/// fn example(split: SplitContainer) {\r\n///   // Convert a `SplitContainer` into a `Container`\r\n///   let container: Container = split.into(); // Will be a `Container::Split`\r\n///\r\n///   // Could also have gone straight to a [TilingContainer] from SplitContainer\r\n///   // let tiling: TilingContainer = split.into(); // Will be a `TilingContainer::Split`\r\n///\r\n///   // Try to convert a [Container] into a sub container type ([TilingContainer] in this case).\r\n///   let tiling: TilingContainer = container.try_into().unwrap(); // Will be a `TilingContainer::Split`\r\n///   tiling.tiling_size(); // Can use methods from the `TilingSizeGetters` trait.\r\n///\r\n///   // Try to convert a one sub container type into another. ([TilingContainer] to [DirectionContainer] in this case).\r\n///   let direction: DirectionContainer = tiling.try_into().unwrap(); // Will be a `DirectionContainer::Split`\r\n///   direction.tiling_direction(); // Can use methods from the `TilingDirectionGetters` trait.\r\n///\r\n///   // Convert a sub container back into a [Container]\r\n///   let container: Container = direction.into(); // Will be a `Container::Split`\r\n/// }\r\n/// ```\r\n#[derive(\r\n  Clone,\r\n  Debug,\r\n  EnumAsInner,\r\n  wm_macros::EnumFromInner,\r\n  Delegate,\r\n  wm_macros::SubEnum,\r\n)]\r\n#[delegate(CommonGetters)]\r\n#[delegate(PositionGetters)]\r\n#[subenum(defaults, {\r\n  /// Subenum of [Container]\r\n  #[derive(Clone, Debug, EnumAsInner, Delegate, wm_macros::EnumFromInner)]\r\n  #[delegate(CommonGetters)]\r\n  #[delegate(PositionGetters)]\r\n})]\r\n#[subenum(TilingContainer, {\r\n  /// Subset of containers that implement the following traits:\r\n  /// * `CommonGetters`\r\n  /// * `PositionGetters`\r\n  /// * `TilingSizeGetters`\r\n  #[delegate(TilingSizeGetters)]\r\n})]\r\n#[subenum(WindowContainer, {\r\n  /// Subset of containers that implement the following traits:\r\n  /// * `CommonGetters`\r\n  /// * `PositionGetters`\r\n  /// * `WindowGetters`\r\n  #[delegate(WindowGetters)]\r\n})]\r\n#[subenum(DirectionContainer, {\r\n  /// Subset of containers that implement the following traits:\r\n  /// * `CommonGetters`\r\n  /// * `PositionGetters`\r\n  /// * `DirectionGetters`\r\n  #[delegate(TilingDirectionGetters)]\r\n})]\r\npub enum Container {\r\n  Root(RootContainer),\r\n  Monitor(Monitor),\r\n  #[subenum(DirectionContainer)]\r\n  Workspace(Workspace),\r\n  #[subenum(TilingContainer, DirectionContainer)]\r\n  Split(SplitContainer),\r\n  #[subenum(TilingContainer, WindowContainer)]\r\n  TilingWindow(TilingWindow),\r\n  #[subenum(WindowContainer)]\r\n  NonTilingWindow(NonTilingWindow),\r\n}\r\n\r\nimpl PartialEq for Container {\r\n  fn eq(&self, other: &Self) -> bool {\r\n    self.id() == other.id()\r\n  }\r\n}\r\n\r\nimpl Eq for Container {}\r\n\r\nimpl PartialEq for TilingContainer {\r\n  fn eq(&self, other: &Self) -> bool {\r\n    self.id() == other.id()\r\n  }\r\n}\r\n\r\nimpl Eq for TilingContainer {}\r\n\r\nimpl PartialEq for WindowContainer {\r\n  fn eq(&self, other: &Self) -> bool {\r\n    self.id() == other.id()\r\n  }\r\n}\r\n\r\nimpl Eq for WindowContainer {}\r\n\r\nimpl std::fmt::Display for WindowContainer {\r\n  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\r\n    // Truncate title if longer than 20 chars. Need to use `chars()`\r\n    // instead of byte slices to handle invalid byte indices.\r\n    let title = {\r\n      let title = self.native_properties().title;\r\n      if title.len() > 20 {\r\n        format!(\"{}...\", title.chars().take(17).collect::<String>())\r\n      } else {\r\n        title\r\n      }\r\n    };\r\n\r\n    let class = {\r\n      #[cfg(target_os = \"windows\")]\r\n      {\r\n        self.native_properties().class_name\r\n      }\r\n      #[cfg(not(target_os = \"windows\"))]\r\n      {\r\n        String::new()\r\n      }\r\n    };\r\n\r\n    let process = self.native_properties().process_name;\r\n\r\n    write!(\r\n      f,\r\n      \"Window(id={:?}, process={}, class={}, title={})\",\r\n      self.native().id(),\r\n      process,\r\n      class,\r\n      title,\r\n    )?;\r\n\r\n    Ok(())\r\n  }\r\n}\r\n\r\nimpl PartialEq for DirectionContainer {\r\n  fn eq(&self, other: &Self) -> bool {\r\n    self.id() == other.id()\r\n  }\r\n}\r\n\r\nimpl Eq for DirectionContainer {}\r\n\r\n/// Implements the `Debug` trait for a given container struct.\r\n///\r\n/// Expects that the struct has a `to_dto()` method.\r\n#[macro_export]\r\nmacro_rules! impl_container_debug {\r\n  ($type:ty) => {\r\n    impl std::fmt::Debug for $type {\r\n      fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\r\n        std::fmt::Debug::fmt(\r\n          &self.to_dto().map_err(|_| std::fmt::Error),\r\n          f,\r\n        )\r\n      }\r\n    }\r\n  };\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/models/insertion_target.rs",
    "content": "use crate::models::Container;\r\n\r\n#[derive(Debug, Clone)]\r\npub struct InsertionTarget {\r\n  pub target_parent: Container,\r\n  pub target_index: usize,\r\n  pub prev_tiling_size: f32,\r\n  pub prev_sibling_count: usize,\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/models/mod.rs",
    "content": "mod container;\r\nmod insertion_target;\r\nmod monitor;\r\nmod native_monitor_properties;\r\nmod native_window_properties;\r\nmod non_tiling_window;\r\nmod root_container;\r\nmod split_container;\r\nmod tiling_window;\r\nmod workspace;\r\nmod workspace_target;\r\n\r\npub use container::*;\r\npub use insertion_target::*;\r\npub use monitor::*;\r\npub use native_monitor_properties::*;\r\npub use native_window_properties::*;\r\npub use non_tiling_window::*;\r\npub use root_container::*;\r\npub use split_container::*;\r\npub use tiling_window::*;\r\npub use workspace::*;\r\npub use workspace_target::*;\r\n"
  },
  {
    "path": "packages/wm/src/models/monitor.rs",
    "content": "use std::{\r\n  cell::{Ref, RefCell, RefMut},\r\n  collections::VecDeque,\r\n  rc::Rc,\r\n};\r\n\r\nuse anyhow::Context;\r\nuse uuid::Uuid;\r\nuse wm_common::{ContainerDto, MonitorDto};\r\nuse wm_platform::{Display, Rect};\r\n\r\nuse crate::{\r\n  impl_common_getters, impl_container_debug,\r\n  models::{\r\n    Container, DirectionContainer, NativeMonitorProperties,\r\n    TilingContainer, WindowContainer, Workspace,\r\n  },\r\n  traits::{CommonGetters, PositionGetters},\r\n};\r\n\r\n#[derive(Clone)]\r\npub struct Monitor(Rc<RefCell<MonitorInner>>);\r\n\r\nstruct MonitorInner {\r\n  id: Uuid,\r\n  parent: Option<Container>,\r\n  children: VecDeque<Container>,\r\n  child_focus_order: VecDeque<Uuid>,\r\n  native: Display,\r\n  native_properties: NativeMonitorProperties,\r\n}\r\n\r\nimpl Monitor {\r\n  pub fn new(\r\n    native_display: Display,\r\n    native_properties: NativeMonitorProperties,\r\n  ) -> Self {\r\n    let monitor = MonitorInner {\r\n      id: Uuid::new_v4(),\r\n      parent: None,\r\n      children: VecDeque::new(),\r\n      child_focus_order: VecDeque::new(),\r\n      native: native_display,\r\n      native_properties,\r\n    };\r\n\r\n    Self(Rc::new(RefCell::new(monitor)))\r\n  }\r\n\r\n  pub fn native(&self) -> Display {\r\n    self.0.borrow().native.clone()\r\n  }\r\n\r\n  pub fn set_native(&self, native: Display) {\r\n    self.0.borrow_mut().native = native;\r\n  }\r\n\r\n  pub fn native_properties(&self) -> NativeMonitorProperties {\r\n    self.0.borrow().native_properties.clone()\r\n  }\r\n\r\n  pub fn set_native_properties(\r\n    &self,\r\n    native_properties: NativeMonitorProperties,\r\n  ) {\r\n    self.0.borrow_mut().native_properties = native_properties;\r\n  }\r\n\r\n  pub fn displayed_workspace(&self) -> Option<Workspace> {\r\n    self\r\n      .child_focus_order()\r\n      .next()\r\n      .and_then(|child| child.as_workspace().cloned())\r\n  }\r\n\r\n  pub fn workspaces(&self) -> Vec<Workspace> {\r\n    self\r\n      .children()\r\n      .into_iter()\r\n      .filter_map(|container| container.as_workspace().cloned())\r\n      .collect()\r\n  }\r\n\r\n  /// Whether there is a difference in DPI between this monitor and the\r\n  /// parent monitor of another container.\r\n  pub fn has_dpi_difference(\r\n    &self,\r\n    other: &Container,\r\n  ) -> anyhow::Result<bool> {\r\n    let dpi = self.native_properties().dpi;\r\n\r\n    let other_dpi = other\r\n      .monitor()\r\n      .map(|monitor| monitor.native_properties().dpi)\r\n      .context(\"Failed to get DPI of other monitor.\")?;\r\n\r\n    Ok(dpi != other_dpi)\r\n  }\r\n\r\n  pub fn to_dto(&self) -> anyhow::Result<ContainerDto> {\r\n    let rect = self.to_rect()?;\r\n    let children = self\r\n      .children()\r\n      .iter()\r\n      .map(CommonGetters::to_dto)\r\n      .try_collect()?;\r\n\r\n    Ok(ContainerDto::Monitor(MonitorDto {\r\n      id: self.id(),\r\n      parent_id: self.parent().map(|parent| parent.id()),\r\n      children,\r\n      child_focus_order: self.0.borrow().child_focus_order.clone().into(),\r\n      has_focus: self.has_focus(None),\r\n      width: rect.width(),\r\n      height: rect.height(),\r\n      x: rect.x(),\r\n      y: rect.y(),\r\n      dpi: self.native_properties().dpi,\r\n      scale_factor: self.native_properties().scale_factor,\r\n      #[cfg(target_os = \"windows\")]\r\n      handle: Some(self.native_properties().handle),\r\n      #[cfg(not(target_os = \"windows\"))]\r\n      handle: None,\r\n      device_name: self.native_properties().device_name,\r\n      #[cfg(target_os = \"windows\")]\r\n      device_path: self.native_properties().device_path,\r\n      #[cfg(not(target_os = \"windows\"))]\r\n      device_path: None,\r\n      #[cfg(target_os = \"windows\")]\r\n      hardware_id: self.native_properties().hardware_id,\r\n      #[cfg(not(target_os = \"windows\"))]\r\n      hardware_id: None,\r\n      working_rect: self.native_properties().working_area,\r\n    }))\r\n  }\r\n}\r\n\r\nimpl_container_debug!(Monitor);\r\nimpl_common_getters!(Monitor);\r\n\r\nimpl PositionGetters for Monitor {\r\n  fn to_rect(&self) -> anyhow::Result<Rect> {\r\n    Ok(self.0.borrow().native_properties.bounds.clone())\r\n  }\r\n}\r\n\r\nimpl std::fmt::Display for Monitor {\r\n  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\r\n    write!(\r\n      f,\r\n      \"Monitor(device_name={})\",\r\n      self.native_properties().device_name,\r\n    )\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/models/native_monitor_properties.rs",
    "content": "use wm_platform::{Display, Rect};\r\n#[cfg(target_os = \"windows\")]\r\nuse wm_platform::{DisplayDeviceExtWindows, DisplayExtWindows};\r\n\r\n#[derive(Debug, Clone)]\r\npub struct NativeMonitorProperties {\r\n  #[cfg(target_os = \"macos\")]\r\n  pub device_uuid: String,\r\n  #[cfg(target_os = \"windows\")]\r\n  pub handle: isize,\r\n  #[cfg(target_os = \"windows\")]\r\n  pub hardware_id: Option<String>,\r\n  #[cfg(target_os = \"windows\")]\r\n  pub device_path: Option<String>,\r\n  pub device_name: String,\r\n  pub working_area: Rect,\r\n  pub bounds: Rect,\r\n  pub dpi: u32,\r\n  pub scale_factor: f32,\r\n}\r\n\r\nimpl NativeMonitorProperties {\r\n  pub fn try_from(native_display: &Display) -> anyhow::Result<Self> {\r\n    let display_device = native_display.main_device()?;\r\n\r\n    Ok(Self {\r\n      #[cfg(target_os = \"macos\")]\r\n      device_uuid: display_device.id().0,\r\n      #[cfg(target_os = \"windows\")]\r\n      handle: native_display.hmonitor().0,\r\n      #[cfg(target_os = \"windows\")]\r\n      hardware_id: display_device.hardware_id(),\r\n      #[cfg(target_os = \"windows\")]\r\n      device_path: display_device.device_path(),\r\n      device_name: native_display.name()?,\r\n      working_area: native_display.working_area()?,\r\n      bounds: native_display.bounds()?,\r\n      dpi: native_display.dpi()?,\r\n      scale_factor: native_display.scale_factor()?,\r\n    })\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/models/native_window_properties.rs",
    "content": "use wm_platform::{NativeWindow, Rect};\r\n#[cfg(target_os = \"windows\")]\r\nuse wm_platform::{NativeWindowWindowsExt, RectDelta};\r\n\r\n#[derive(Debug, Clone)]\r\npub struct NativeWindowProperties {\r\n  pub title: String,\r\n  #[cfg(target_os = \"windows\")]\r\n  pub class_name: String,\r\n  pub process_name: String,\r\n  pub frame: Rect,\r\n  pub is_minimized: bool,\r\n  pub is_maximized: bool,\r\n  pub is_resizable: bool,\r\n  #[cfg(target_os = \"windows\")]\r\n  pub shadow_borders: RectDelta,\r\n}\r\n\r\nimpl TryFrom<&NativeWindow> for NativeWindowProperties {\r\n  type Error = anyhow::Error;\r\n\r\n  fn try_from(native_window: &NativeWindow) -> Result<Self, Self::Error> {\r\n    Ok(Self {\r\n      title: native_window.title()?,\r\n      #[cfg(target_os = \"windows\")]\r\n      class_name: native_window.class_name()?,\r\n      process_name: native_window.process_name()?,\r\n      frame: native_window.frame()?,\r\n      is_minimized: native_window.is_minimized()?,\r\n      is_maximized: native_window.is_maximized()?,\r\n      is_resizable: native_window.is_resizable()?,\r\n      #[cfg(target_os = \"windows\")]\r\n      shadow_borders: native_window.shadow_borders()?,\r\n    })\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/models/non_tiling_window.rs",
    "content": "use std::{\r\n  cell::{Ref, RefCell, RefMut},\r\n  collections::VecDeque,\r\n  rc::Rc,\r\n};\r\n\r\nuse anyhow::Context;\r\nuse uuid::Uuid;\r\nuse wm_common::{\r\n  ActiveDrag, ContainerDto, DisplayState, GapsConfig, WindowDto,\r\n  WindowRuleConfig, WindowState,\r\n};\r\nuse wm_platform::{NativeWindow, Rect, RectDelta};\r\n\r\nuse crate::{\r\n  impl_common_getters, impl_container_debug, impl_window_getters,\r\n  models::{\r\n    Container, DirectionContainer, InsertionTarget,\r\n    NativeWindowProperties, TilingContainer, TilingWindow,\r\n    WindowContainer,\r\n  },\r\n  traits::{CommonGetters, PositionGetters, WindowGetters},\r\n};\r\n\r\n#[derive(Clone)]\r\npub struct NonTilingWindow(Rc<RefCell<NonTilingWindowInner>>);\r\n\r\nstruct NonTilingWindowInner {\r\n  id: Uuid,\r\n  parent: Option<Container>,\r\n  children: VecDeque<Container>,\r\n  child_focus_order: VecDeque<Uuid>,\r\n  native: NativeWindow,\r\n  native_properties: NativeWindowProperties,\r\n  state: WindowState,\r\n  prev_state: Option<WindowState>,\r\n  insertion_target: Option<InsertionTarget>,\r\n  display_state: DisplayState,\r\n  border_delta: RectDelta,\r\n  has_pending_dpi_adjustment: bool,\r\n  floating_placement: Rect,\r\n  has_custom_floating_placement: bool,\r\n  done_window_rules: Vec<WindowRuleConfig>,\r\n  active_drag: Option<ActiveDrag>,\r\n}\r\n\r\nimpl NonTilingWindow {\r\n  #[allow(clippy::too_many_arguments)]\r\n  pub fn new(\r\n    id: Option<Uuid>,\r\n    native: NativeWindow,\r\n    properties: NativeWindowProperties,\r\n    state: WindowState,\r\n    prev_state: Option<WindowState>,\r\n    border_delta: RectDelta,\r\n    insertion_target: Option<InsertionTarget>,\r\n    floating_placement: Rect,\r\n    has_custom_floating_placement: bool,\r\n    done_window_rules: Vec<WindowRuleConfig>,\r\n    active_drag: Option<ActiveDrag>,\r\n  ) -> Self {\r\n    let window = NonTilingWindowInner {\r\n      id: id.unwrap_or_else(Uuid::new_v4),\r\n      parent: None,\r\n      children: VecDeque::new(),\r\n      child_focus_order: VecDeque::new(),\r\n      native,\r\n      native_properties: properties,\r\n      state,\r\n      prev_state,\r\n      insertion_target,\r\n      display_state: DisplayState::Shown,\r\n      border_delta,\r\n      has_pending_dpi_adjustment: false,\r\n      floating_placement,\r\n      has_custom_floating_placement,\r\n      done_window_rules,\r\n      active_drag,\r\n    };\r\n\r\n    Self(Rc::new(RefCell::new(window)))\r\n  }\r\n\r\n  pub fn insertion_target(&self) -> Option<InsertionTarget> {\r\n    self.0.borrow().insertion_target.clone()\r\n  }\r\n\r\n  pub fn set_insertion_target(\r\n    &self,\r\n    insertion_target: Option<InsertionTarget>,\r\n  ) {\r\n    self.0.borrow_mut().insertion_target = insertion_target;\r\n  }\r\n\r\n  pub fn to_tiling(&self, gaps_config: GapsConfig) -> TilingWindow {\r\n    let prev_state = if self.active_drag().is_some() {\r\n      self.prev_state()\r\n    } else {\r\n      Some(self.state())\r\n    };\r\n\r\n    TilingWindow::new(\r\n      Some(self.id()),\r\n      self.native().clone(),\r\n      self.native_properties().clone(),\r\n      prev_state,\r\n      self.border_delta(),\r\n      self.floating_placement(),\r\n      self.has_custom_floating_placement(),\r\n      gaps_config,\r\n      self.done_window_rules(),\r\n      self.active_drag(),\r\n    )\r\n  }\r\n\r\n  pub fn to_dto(&self) -> anyhow::Result<ContainerDto> {\r\n    let rect = self.to_rect()?;\r\n\r\n    Ok(ContainerDto::Window(WindowDto {\r\n      id: self.id(),\r\n      parent_id: self.parent().map(|parent| parent.id()),\r\n      has_focus: self.has_focus(None),\r\n      tiling_size: None,\r\n      width: rect.width(),\r\n      height: rect.height(),\r\n      x: rect.x(),\r\n      y: rect.y(),\r\n      state: self.state(),\r\n      prev_state: self.prev_state(),\r\n      display_state: self.display_state(),\r\n      border_delta: self.border_delta(),\r\n      floating_placement: self.floating_placement(),\r\n      #[allow(clippy::cast_possible_wrap, clippy::unnecessary_cast)]\r\n      handle: self.native().id().0 as isize,\r\n      title: self.native_properties().title,\r\n      #[cfg(target_os = \"windows\")]\r\n      class_name: self.native_properties().class_name,\r\n      process_name: self.native_properties().process_name,\r\n      active_drag: self.active_drag(),\r\n    }))\r\n  }\r\n}\r\n\r\nimpl_container_debug!(NonTilingWindow);\r\nimpl_common_getters!(NonTilingWindow);\r\nimpl_window_getters!(NonTilingWindow);\r\n\r\nimpl PositionGetters for NonTilingWindow {\r\n  fn to_rect(&self) -> anyhow::Result<Rect> {\r\n    match self.state() {\r\n      WindowState::Fullscreen(_) => {\r\n        let monitor = self.monitor().context(\"No monitor.\")?;\r\n\r\n        #[cfg(target_os = \"windows\")]\r\n        {\r\n          monitor.to_rect()\r\n        }\r\n        #[cfg(target_os = \"macos\")]\r\n        {\r\n          // On macOS, the public APIs only allow window placement within\r\n          // the display's working area.\r\n          Ok(monitor.native_properties().working_area)\r\n        }\r\n      }\r\n      _ => Ok(self.floating_placement()),\r\n    }\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/models/root_container.rs",
    "content": "use std::{\r\n  cell::{Ref, RefCell, RefMut},\r\n  collections::VecDeque,\r\n  rc::Rc,\r\n};\r\n\r\nuse anyhow::bail;\r\nuse uuid::Uuid;\r\nuse wm_common::{ContainerDto, RootContainerDto};\r\nuse wm_platform::Rect;\r\n\r\nuse crate::{\r\n  impl_common_getters, impl_container_debug,\r\n  models::{\r\n    Container, DirectionContainer, Monitor, TilingContainer,\r\n    WindowContainer,\r\n  },\r\n  traits::{CommonGetters, PositionGetters},\r\n};\r\n\r\n/// Root node of the container tree.\r\n#[derive(Clone)]\r\npub struct RootContainer(Rc<RefCell<RootContainerInner>>);\r\n\r\nstruct RootContainerInner {\r\n  id: Uuid,\r\n  parent: Option<Container>,\r\n  children: VecDeque<Container>,\r\n  child_focus_order: VecDeque<Uuid>,\r\n}\r\n\r\nimpl Default for RootContainer {\r\n  fn default() -> Self {\r\n    let root = RootContainerInner {\r\n      id: Uuid::new_v4(),\r\n      parent: None,\r\n      children: VecDeque::new(),\r\n      child_focus_order: VecDeque::new(),\r\n    };\r\n\r\n    Self(Rc::new(RefCell::new(root)))\r\n  }\r\n}\r\n\r\nimpl RootContainer {\r\n  pub fn new() -> Self {\r\n    Self::default()\r\n  }\r\n\r\n  pub fn monitors(&self) -> Vec<Monitor> {\r\n    self\r\n      .children()\r\n      .into_iter()\r\n      .filter_map(|container| container.as_monitor().cloned())\r\n      .collect()\r\n  }\r\n\r\n  pub fn to_dto(&self) -> anyhow::Result<ContainerDto> {\r\n    let children = self\r\n      .children()\r\n      .iter()\r\n      .map(CommonGetters::to_dto)\r\n      .try_collect()?;\r\n\r\n    Ok(ContainerDto::Root(RootContainerDto {\r\n      id: self.id(),\r\n      parent_id: None,\r\n      children,\r\n      child_focus_order: self.0.borrow().child_focus_order.clone().into(),\r\n    }))\r\n  }\r\n}\r\n\r\nimpl_container_debug!(RootContainer);\r\nimpl_common_getters!(RootContainer);\r\n\r\nimpl PositionGetters for RootContainer {\r\n  fn to_rect(&self) -> anyhow::Result<Rect> {\r\n    bail!(\"Root container does not have a position.\")\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/models/split_container.rs",
    "content": "use std::{\r\n  cell::{Ref, RefCell, RefMut},\r\n  collections::VecDeque,\r\n  rc::Rc,\r\n};\r\n\r\nuse anyhow::Context;\r\nuse uuid::Uuid;\r\nuse wm_common::{\r\n  ContainerDto, GapsConfig, SplitContainerDto, TilingDirection,\r\n};\r\nuse wm_platform::Rect;\r\n\r\nuse crate::{\r\n  impl_common_getters, impl_container_debug,\r\n  impl_position_getters_as_resizable, impl_tiling_direction_getters,\r\n  impl_tiling_size_getters,\r\n  models::{\r\n    Container, DirectionContainer, TilingContainer, WindowContainer,\r\n  },\r\n  traits::{\r\n    CommonGetters, PositionGetters, TilingDirectionGetters,\r\n    TilingSizeGetters,\r\n  },\r\n};\r\n\r\n#[derive(Clone)]\r\npub struct SplitContainer(Rc<RefCell<SplitContainerInner>>);\r\n\r\nstruct SplitContainerInner {\r\n  id: Uuid,\r\n  parent: Option<Container>,\r\n  children: VecDeque<Container>,\r\n  child_focus_order: VecDeque<Uuid>,\r\n  tiling_size: f32,\r\n  tiling_direction: TilingDirection,\r\n  gaps_config: GapsConfig,\r\n}\r\n\r\nimpl SplitContainer {\r\n  pub fn new(\r\n    tiling_direction: TilingDirection,\r\n    gaps_config: GapsConfig,\r\n  ) -> Self {\r\n    let split = SplitContainerInner {\r\n      id: Uuid::new_v4(),\r\n      parent: None,\r\n      children: VecDeque::new(),\r\n      child_focus_order: VecDeque::new(),\r\n      tiling_size: 1.0,\r\n      tiling_direction,\r\n      gaps_config,\r\n    };\r\n\r\n    Self(Rc::new(RefCell::new(split)))\r\n  }\r\n\r\n  pub fn to_dto(&self) -> anyhow::Result<ContainerDto> {\r\n    let rect = self.to_rect()?;\r\n    let children = self\r\n      .children()\r\n      .iter()\r\n      .map(CommonGetters::to_dto)\r\n      .try_collect()?;\r\n\r\n    Ok(ContainerDto::Split(SplitContainerDto {\r\n      id: self.id(),\r\n      parent_id: self.parent().map(|parent| parent.id()),\r\n      children,\r\n      child_focus_order: self.0.borrow().child_focus_order.clone().into(),\r\n      has_focus: self.has_focus(None),\r\n      tiling_size: self.tiling_size(),\r\n      tiling_direction: self.tiling_direction(),\r\n      width: rect.width(),\r\n      height: rect.height(),\r\n      x: rect.x(),\r\n      y: rect.y(),\r\n    }))\r\n  }\r\n}\r\n\r\nimpl_container_debug!(SplitContainer);\r\nimpl_common_getters!(SplitContainer);\r\nimpl_tiling_size_getters!(SplitContainer);\r\nimpl_tiling_direction_getters!(SplitContainer);\r\nimpl_position_getters_as_resizable!(SplitContainer);\r\n"
  },
  {
    "path": "packages/wm/src/models/tiling_window.rs",
    "content": "use std::{\r\n  cell::{Ref, RefCell, RefMut},\r\n  collections::VecDeque,\r\n  rc::Rc,\r\n};\r\n\r\nuse anyhow::Context;\r\nuse uuid::Uuid;\r\nuse wm_common::{\r\n  ActiveDrag, ContainerDto, DisplayState, GapsConfig, TilingDirection,\r\n  WindowDto, WindowRuleConfig, WindowState,\r\n};\r\nuse wm_platform::{NativeWindow, Rect, RectDelta};\r\n\r\nuse crate::{\r\n  impl_common_getters, impl_container_debug,\r\n  impl_position_getters_as_resizable, impl_tiling_size_getters,\r\n  impl_window_getters,\r\n  models::{\r\n    Container, DirectionContainer, InsertionTarget,\r\n    NativeWindowProperties, NonTilingWindow, TilingContainer,\r\n    WindowContainer,\r\n  },\r\n  traits::{\r\n    CommonGetters, PositionGetters, TilingDirectionGetters,\r\n    TilingSizeGetters, WindowGetters,\r\n  },\r\n};\r\n\r\n#[derive(Clone)]\r\npub struct TilingWindow(Rc<RefCell<TilingWindowInner>>);\r\n\r\nstruct TilingWindowInner {\r\n  id: Uuid,\r\n  parent: Option<Container>,\r\n  children: VecDeque<Container>,\r\n  child_focus_order: VecDeque<Uuid>,\r\n  tiling_size: f32,\r\n  native: NativeWindow,\r\n  native_properties: NativeWindowProperties,\r\n  state: WindowState,\r\n  prev_state: Option<WindowState>,\r\n  display_state: DisplayState,\r\n  border_delta: RectDelta,\r\n  has_pending_dpi_adjustment: bool,\r\n  floating_placement: Rect,\r\n  has_custom_floating_placement: bool,\r\n  gaps_config: GapsConfig,\r\n  done_window_rules: Vec<WindowRuleConfig>,\r\n  active_drag: Option<ActiveDrag>,\r\n}\r\n\r\nimpl TilingWindow {\r\n  #[allow(clippy::too_many_arguments)]\r\n  pub fn new(\r\n    id: Option<Uuid>,\r\n    native: NativeWindow,\r\n    properties: NativeWindowProperties,\r\n    prev_state: Option<WindowState>,\r\n    border_delta: RectDelta,\r\n    floating_placement: Rect,\r\n    has_custom_floating_placement: bool,\r\n    gaps_config: GapsConfig,\r\n    done_window_rules: Vec<WindowRuleConfig>,\r\n    active_drag: Option<ActiveDrag>,\r\n  ) -> Self {\r\n    let window = TilingWindowInner {\r\n      id: id.unwrap_or_else(Uuid::new_v4),\r\n      parent: None,\r\n      children: VecDeque::new(),\r\n      child_focus_order: VecDeque::new(),\r\n      tiling_size: 1.0,\r\n      native,\r\n      native_properties: properties,\r\n      state: WindowState::Tiling,\r\n      prev_state,\r\n      display_state: DisplayState::Shown,\r\n      border_delta,\r\n      has_pending_dpi_adjustment: false,\r\n      floating_placement,\r\n      has_custom_floating_placement,\r\n      gaps_config,\r\n      done_window_rules,\r\n      active_drag,\r\n    };\r\n\r\n    Self(Rc::new(RefCell::new(window)))\r\n  }\r\n\r\n  pub fn to_non_tiling(\r\n    &self,\r\n    state: WindowState,\r\n    insertion_target: Option<InsertionTarget>,\r\n  ) -> NonTilingWindow {\r\n    NonTilingWindow::new(\r\n      Some(self.id()),\r\n      self.native().clone(),\r\n      self.native_properties().clone(),\r\n      state,\r\n      Some(WindowState::Tiling),\r\n      self.border_delta(),\r\n      insertion_target,\r\n      self.floating_placement(),\r\n      self.has_custom_floating_placement(),\r\n      self.done_window_rules(),\r\n      self.active_drag(),\r\n    )\r\n  }\r\n\r\n  pub fn to_dto(&self) -> anyhow::Result<ContainerDto> {\r\n    let rect = self.to_rect()?;\r\n\r\n    Ok(ContainerDto::Window(WindowDto {\r\n      id: self.id(),\r\n      parent_id: self.parent().map(|parent| parent.id()),\r\n      has_focus: self.has_focus(None),\r\n      tiling_size: Some(self.tiling_size()),\r\n      width: rect.width(),\r\n      height: rect.height(),\r\n      x: rect.x(),\r\n      y: rect.y(),\r\n      state: self.state(),\r\n      prev_state: self.prev_state(),\r\n      display_state: self.display_state(),\r\n      border_delta: self.border_delta(),\r\n      floating_placement: self.floating_placement(),\r\n      #[allow(clippy::cast_possible_wrap, clippy::unnecessary_cast)]\r\n      handle: self.native().id().0 as isize,\r\n      title: self.native_properties().title,\r\n      #[cfg(target_os = \"windows\")]\r\n      class_name: self.native_properties().class_name,\r\n      process_name: self.native_properties().process_name,\r\n      active_drag: self.active_drag(),\r\n    }))\r\n  }\r\n}\r\n\r\nimpl_container_debug!(TilingWindow);\r\nimpl_common_getters!(TilingWindow);\r\nimpl_tiling_size_getters!(TilingWindow);\r\nimpl_position_getters_as_resizable!(TilingWindow);\r\nimpl_window_getters!(TilingWindow);\r\n"
  },
  {
    "path": "packages/wm/src/models/workspace.rs",
    "content": "use std::{\r\n  cell::{Ref, RefCell, RefMut},\r\n  collections::VecDeque,\r\n  rc::Rc,\r\n};\r\n\r\nuse anyhow::Context;\r\nuse uuid::Uuid;\r\nuse wm_common::{\r\n  ContainerDto, GapsConfig, TilingDirection, WorkspaceConfig, WorkspaceDto,\r\n};\r\nuse wm_platform::{Rect, RectDelta};\r\n\r\nuse crate::{\r\n  impl_common_getters, impl_container_debug,\r\n  impl_tiling_direction_getters,\r\n  models::{\r\n    Container, DirectionContainer, TilingContainer, WindowContainer,\r\n  },\r\n  traits::{CommonGetters, PositionGetters, TilingDirectionGetters},\r\n};\r\n\r\n#[derive(Clone)]\r\npub struct Workspace(Rc<RefCell<WorkspaceInner>>);\r\n\r\n#[derive(Debug)]\r\nstruct WorkspaceInner {\r\n  id: Uuid,\r\n  parent: Option<Container>,\r\n  children: VecDeque<Container>,\r\n  child_focus_order: VecDeque<Uuid>,\r\n  config: WorkspaceConfig,\r\n  gaps_config: GapsConfig,\r\n  tiling_direction: TilingDirection,\r\n}\r\n\r\nimpl Workspace {\r\n  pub fn new(\r\n    config: WorkspaceConfig,\r\n    gaps_config: GapsConfig,\r\n    tiling_direction: TilingDirection,\r\n  ) -> Self {\r\n    let workspace = WorkspaceInner {\r\n      id: Uuid::new_v4(),\r\n      parent: None,\r\n      children: VecDeque::new(),\r\n      child_focus_order: VecDeque::new(),\r\n      config,\r\n      gaps_config,\r\n      tiling_direction,\r\n    };\r\n\r\n    Self(Rc::new(RefCell::new(workspace)))\r\n  }\r\n\r\n  /// Underlying config for the workspace.\r\n  pub fn config(&self) -> WorkspaceConfig {\r\n    self.0.borrow().config.clone()\r\n  }\r\n\r\n  /// Update the underlying config for the workspace.\r\n  pub fn set_config(&self, config: WorkspaceConfig) {\r\n    self.0.borrow_mut().config = config;\r\n  }\r\n\r\n  /// Whether the workspace is currently displayed by the parent monitor.\r\n  pub fn is_displayed(&self) -> bool {\r\n    self\r\n      .monitor()\r\n      .and_then(|monitor| monitor.displayed_workspace())\r\n      .is_some_and(|workspace| workspace.id() == self.id())\r\n  }\r\n\r\n  pub fn set_gaps_config(&self, gaps_config: GapsConfig) {\r\n    self.0.borrow_mut().gaps_config = gaps_config;\r\n  }\r\n\r\n  /// Effective outer gaps for this workspace.\r\n  ///\r\n  /// Uses `single_window_outer_gap` when the workspace has a single tiling\r\n  /// window, otherwise falls back to `outer_gap`.\r\n  pub fn outer_gaps(&self) -> RectDelta {\r\n    let is_single_window = self.tiling_children().nth(1).is_none();\r\n\r\n    let gaps_config = &self.0.borrow().gaps_config;\r\n    let gaps = if is_single_window {\r\n      gaps_config\r\n        .single_window_outer_gap\r\n        .as_ref()\r\n        .unwrap_or(&gaps_config.outer_gap)\r\n    } else {\r\n      &gaps_config.outer_gap\r\n    };\r\n\r\n    // TODO: Should this be scaled by the monitor's DPI?\r\n    gaps.clone()\r\n  }\r\n\r\n  /// Gets the bounds of a workspace with the given outer gap config.\r\n  fn workspace_rect_with_gap_config(\r\n    &self,\r\n    outer_gaps: &RectDelta,\r\n  ) -> anyhow::Result<Rect> {\r\n    let monitor =\r\n      self.monitor().context(\"Workspace has no parent monitor.\")?;\r\n\r\n    let gaps_config = &self.0.borrow().gaps_config;\r\n    let scale_factor = if gaps_config.scale_with_dpi {\r\n      monitor.native_properties().scale_factor\r\n    } else {\r\n      1.\r\n    };\r\n\r\n    // Get the delta between the monitor's bounds and its working area.\r\n    let monitor_bounds = monitor.native_properties().bounds;\r\n    let working_area_delta = monitor\r\n      .native_properties()\r\n      .working_area\r\n      .delta(&monitor_bounds);\r\n\r\n    Ok(\r\n      monitor_bounds\r\n        // Scale the gaps if `scale_with_dpi` is enabled. Outer gap config\r\n        // values can be a percentage (relative to the monitor bounds), so\r\n        // the outer gap delta needs to be applied prior to the working\r\n        // area delta.\r\n        .apply_delta(&outer_gaps.inverse(), Some(scale_factor))\r\n        .apply_delta(&working_area_delta, None),\r\n    )\r\n  }\r\n\r\n  /// Gets the maximum bounds of a workspace considering both `outer_gap`\r\n  /// and `single_window_outer_gap` config values.\r\n  pub fn max_workspace_rect(&self) -> anyhow::Result<Rect> {\r\n    let gaps_config = &self.0.borrow().gaps_config;\r\n\r\n    // Get the workspace rect using `outer_gap`.\r\n    let multi_window_rect =\r\n      self.workspace_rect_with_gap_config(&gaps_config.outer_gap)?;\r\n\r\n    let Some(single_gap) = &gaps_config.single_window_outer_gap else {\r\n      return Ok(multi_window_rect);\r\n    };\r\n\r\n    // Get the workspace rect using `single_window_outer_gap`.\r\n    let single_window_rect =\r\n      self.workspace_rect_with_gap_config(single_gap)?;\r\n\r\n    Ok(multi_window_rect.union(&single_window_rect))\r\n  }\r\n\r\n  pub fn to_dto(&self) -> anyhow::Result<ContainerDto> {\r\n    let rect = self.to_rect()?;\r\n    let config = self.config();\r\n\r\n    let children = self\r\n      .children()\r\n      .iter()\r\n      .map(CommonGetters::to_dto)\r\n      .try_collect()?;\r\n\r\n    Ok(ContainerDto::Workspace(WorkspaceDto {\r\n      id: self.id(),\r\n      name: config.name,\r\n      display_name: config.display_name,\r\n      parent_id: self.parent().map(|parent| parent.id()),\r\n      children,\r\n      child_focus_order: self.0.borrow().child_focus_order.clone().into(),\r\n      has_focus: self.has_focus(None),\r\n      is_displayed: self.is_displayed(),\r\n      width: rect.width(),\r\n      height: rect.height(),\r\n      x: rect.x(),\r\n      y: rect.y(),\r\n      tiling_direction: self.tiling_direction(),\r\n    }))\r\n  }\r\n}\r\n\r\nimpl_container_debug!(Workspace);\r\nimpl_common_getters!(Workspace);\r\nimpl_tiling_direction_getters!(Workspace);\r\n\r\nimpl PositionGetters for Workspace {\r\n  fn to_rect(&self) -> anyhow::Result<Rect> {\r\n    self.workspace_rect_with_gap_config(&self.outer_gaps())\r\n  }\r\n}\r\n\r\nimpl std::fmt::Display for Workspace {\r\n  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\r\n    write!(\r\n      f,\r\n      \"Workspace(name={}, tiling_direction={:?})\",\r\n      self.config().name,\r\n      self.tiling_direction(),\r\n    )\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/models/workspace_target.rs",
    "content": "use wm_platform::Direction;\r\n\r\npub enum WorkspaceTarget {\r\n  Name(String),\r\n  Recent,\r\n  NextActive,\r\n  PreviousActive,\r\n  NextActiveInMonitor,\r\n  PreviousActiveInMonitor,\r\n  Next,\r\n  Previous,\r\n  #[allow(dead_code)]\r\n  Direction(Direction),\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/pending_sync.rs",
    "content": "use std::collections::HashMap;\r\n\r\nuse uuid::Uuid;\r\n\r\nuse crate::{\r\n  models::{Container, Workspace},\r\n  traits::CommonGetters,\r\n};\r\n\r\n#[derive(Debug, Default)]\r\n#[allow(clippy::struct_excessive_bools)]\r\npub struct PendingSync {\r\n  /// Containers (and their descendants) that have a pending redraw.\r\n  containers_to_redraw: HashMap<Uuid, Container>,\r\n\r\n  /// Workspaces where z-order should be updated. Windows that match the\r\n  /// focused window's state should be brought to the front.\r\n  workspaces_to_reorder: Vec<Workspace>,\r\n\r\n  /// Whether native focus should be reassigned to the WM's focused\r\n  /// container.\r\n  needs_focus_update: bool,\r\n\r\n  /// Whether window effect for the focused window should be updated.\r\n  needs_focused_effect_update: bool,\r\n\r\n  /// Whether window effects for all windows should be updated.\r\n  needs_all_effects_update: bool,\r\n\r\n  /// Whether to jump the cursor to the focused container (if enabled in\r\n  /// user config).\r\n  needs_cursor_jump: bool,\r\n}\r\n\r\nimpl PendingSync {\r\n  pub fn has_changes(&self) -> bool {\r\n    !self.containers_to_redraw.is_empty()\r\n      || !self.workspaces_to_reorder.is_empty()\r\n      || self.needs_focus_update\r\n      || self.needs_focused_effect_update\r\n      || self.needs_all_effects_update\r\n      || self.needs_cursor_jump\r\n  }\r\n\r\n  pub fn clear(&mut self) -> &mut Self {\r\n    self.containers_to_redraw.clear();\r\n    self.workspaces_to_reorder.clear();\r\n    self.needs_focus_update = false;\r\n    self.needs_focused_effect_update = false;\r\n    self.needs_all_effects_update = false;\r\n    self.needs_cursor_jump = false;\r\n    self\r\n  }\r\n\r\n  pub fn queue_container_to_redraw<T>(&mut self, container: T) -> &mut Self\r\n  where\r\n    T: Into<Container>,\r\n  {\r\n    let container: Container = container.into();\r\n    self.containers_to_redraw.insert(container.id(), container);\r\n    self\r\n  }\r\n\r\n  pub fn queue_containers_to_redraw<I, T>(\r\n    &mut self,\r\n    containers: I,\r\n  ) -> &mut Self\r\n  where\r\n    I: IntoIterator<Item = T>,\r\n    T: Into<Container>,\r\n  {\r\n    for container in containers {\r\n      let container: Container = container.into();\r\n      self.containers_to_redraw.insert(container.id(), container);\r\n    }\r\n\r\n    self\r\n  }\r\n\r\n  pub fn dequeue_container_from_redraw<T>(\r\n    &mut self,\r\n    container: T,\r\n  ) -> &mut Self\r\n  where\r\n    T: Into<Container>,\r\n  {\r\n    self.containers_to_redraw.remove(&container.into().id());\r\n    self\r\n  }\r\n\r\n  pub fn queue_workspace_to_reorder(\r\n    &mut self,\r\n    workspace: Workspace,\r\n  ) -> &mut Self {\r\n    self.workspaces_to_reorder.push(workspace);\r\n    self\r\n  }\r\n\r\n  pub fn queue_focus_change(&mut self) -> &mut Self {\r\n    self.needs_focus_update = true;\r\n    self\r\n  }\r\n\r\n  pub fn queue_focused_effect_update(&mut self) -> &mut Self {\r\n    self.needs_focused_effect_update = true;\r\n    self\r\n  }\r\n\r\n  pub fn queue_all_effects_update(&mut self) -> &mut Self {\r\n    self.needs_all_effects_update = true;\r\n    self\r\n  }\r\n\r\n  pub fn queue_cursor_jump(&mut self) -> &mut Self {\r\n    self.needs_cursor_jump = true;\r\n    self\r\n  }\r\n\r\n  pub fn needs_focus_update(&self) -> bool {\r\n    self.needs_focus_update\r\n  }\r\n\r\n  pub fn needs_focused_effect_update(&self) -> bool {\r\n    self.needs_focused_effect_update\r\n  }\r\n\r\n  pub fn needs_all_effects_update(&self) -> bool {\r\n    self.needs_all_effects_update\r\n  }\r\n\r\n  pub fn needs_cursor_jump(&self) -> bool {\r\n    self.needs_cursor_jump\r\n  }\r\n\r\n  pub fn containers_to_redraw(&self) -> &HashMap<Uuid, Container> {\r\n    &self.containers_to_redraw\r\n  }\r\n\r\n  pub fn workspaces_to_reorder(&self) -> &Vec<Workspace> {\r\n    &self.workspaces_to_reorder\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/sys_tray.rs",
    "content": "use std::{\r\n  fmt::{self, Display},\r\n  path::Path,\r\n  str::FromStr,\r\n  sync::{Arc, Mutex},\r\n};\r\n\r\nuse anyhow::Context;\r\nuse auto_launch::AutoLaunch;\r\nuse tokio::sync::mpsc;\r\nuse tray_icon::{\r\n  menu::{CheckMenuItem, Menu, MenuEvent, MenuItem, PredefinedMenuItem},\r\n  Icon, TrayIcon, TrayIconBuilder,\r\n};\r\n#[cfg(target_os = \"windows\")]\r\nuse wm_platform::DispatcherExtWindows;\r\nuse wm_platform::{Dispatcher, ThreadBound};\r\n\r\n#[derive(Debug, Clone, Eq, PartialEq)]\r\nenum TrayMenuId {\r\n  ReloadConfig,\r\n  ShowConfigFolder,\r\n  #[cfg(target_os = \"windows\")]\r\n  ToggleWindowAnimations,\r\n  RunOnStartup,\r\n  Exit,\r\n}\r\n\r\nimpl Display for TrayMenuId {\r\n  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\r\n    match self {\r\n      TrayMenuId::ReloadConfig => write!(f, \"reload_config\"),\r\n      TrayMenuId::ShowConfigFolder => write!(f, \"show_config_folder\"),\r\n      #[cfg(target_os = \"windows\")]\r\n      TrayMenuId::ToggleWindowAnimations => {\r\n        write!(f, \"toggle_window_animations\")\r\n      }\r\n      TrayMenuId::RunOnStartup => write!(f, \"run_on_startup\"),\r\n      TrayMenuId::Exit => write!(f, \"exit\"),\r\n    }\r\n  }\r\n}\r\n\r\nimpl FromStr for TrayMenuId {\r\n  type Err = anyhow::Error;\r\n\r\n  fn from_str(event: &str) -> Result<Self, Self::Err> {\r\n    match event {\r\n      \"show_config_folder\" => Ok(Self::ShowConfigFolder),\r\n      \"reload_config\" => Ok(Self::ReloadConfig),\r\n      #[cfg(target_os = \"windows\")]\r\n      \"toggle_window_animations\" => Ok(Self::ToggleWindowAnimations),\r\n      \"exit\" => Ok(Self::Exit),\r\n      _ => anyhow::bail!(\"Invalid tray menu event: {}\", event),\r\n    }\r\n  }\r\n}\r\n\r\npub struct SystemTray {\r\n  pub config_reload_rx: mpsc::UnboundedReceiver<()>,\r\n  pub exit_rx: mpsc::UnboundedReceiver<()>,\r\n  _icon_thread: Option<std::thread::JoinHandle<()>>,\r\n  _tray_icon: ThreadBound<TrayIcon>,\r\n}\r\n\r\nimpl SystemTray {\r\n  /// Install the system tray on the main thread after the run loop starts.\r\n  pub fn new(\r\n    config_path: &Path,\r\n    dispatcher: Dispatcher,\r\n  ) -> anyhow::Result<Self> {\r\n    let (exit_tx, exit_rx) = mpsc::unbounded_channel();\r\n    let (config_reload_tx, config_reload_rx) = mpsc::unbounded_channel();\r\n\r\n    let animations_enabled = Arc::new(Mutex::new({\r\n      #[cfg(target_os = \"windows\")]\r\n      {\r\n        dispatcher.window_animations_enabled().unwrap_or(false)\r\n      }\r\n      #[cfg(not(target_os = \"windows\"))]\r\n      {\r\n        false\r\n      }\r\n    }));\r\n\r\n    let run_on_startup_enabled = Arc::new(Mutex::new(\r\n      auto_launch_instance()\r\n        .and_then(|auto_launch| {\r\n          auto_launch.is_enabled().map_err(Into::into)\r\n        })\r\n        .unwrap_or(false),\r\n    ));\r\n\r\n    let tray_icon = dispatcher.dispatch_sync(|| {\r\n      let tray_icon = Self::create_tray_icon(\r\n        *animations_enabled.lock().unwrap(),\r\n        *run_on_startup_enabled.lock().unwrap(),\r\n      )\r\n      .unwrap();\r\n      ThreadBound::new(tray_icon, dispatcher.clone())\r\n    })?;\r\n\r\n    // Spawn thread to handle tray menu events.\r\n    let config_path = config_path.to_owned();\r\n    let icon_thread = std::thread::spawn(move || {\r\n      let menu_event_rx = MenuEvent::receiver();\r\n\r\n      while let Ok(event) = menu_event_rx.recv() {\r\n        if let Ok(menu_event) = TrayMenuId::from_str(event.id.as_ref()) {\r\n          if let Err(err) = Self::handle_menu_event(\r\n            &menu_event,\r\n            &dispatcher,\r\n            &config_path,\r\n            &config_reload_tx,\r\n            &exit_tx,\r\n            &animations_enabled,\r\n            &run_on_startup_enabled,\r\n          ) {\r\n            tracing::warn!(\"Failed to handle tray menu event: {}\", err);\r\n          }\r\n        }\r\n      }\r\n    });\r\n\r\n    Ok(Self {\r\n      config_reload_rx,\r\n      exit_rx,\r\n      _icon_thread: Some(icon_thread),\r\n      _tray_icon: tray_icon,\r\n    })\r\n  }\r\n\r\n  fn create_tray_icon(\r\n    // LINT: `animations_enabled` is only used on Windows.\r\n    #[cfg_attr(not(target_os = \"windows\"), allow(unused_variables))]\r\n    animations_enabled: bool,\r\n    run_on_startup_enabled: bool,\r\n  ) -> anyhow::Result<TrayIcon> {\r\n    let reload_config_item = MenuItem::with_id(\r\n      TrayMenuId::ReloadConfig,\r\n      \"Reload config\",\r\n      true,\r\n      None,\r\n    );\r\n\r\n    let config_dir_item = MenuItem::with_id(\r\n      TrayMenuId::ShowConfigFolder,\r\n      \"Show config folder\",\r\n      true,\r\n      None,\r\n    );\r\n\r\n    #[cfg(target_os = \"windows\")]\r\n    let toggle_animations_item = CheckMenuItem::with_id(\r\n      TrayMenuId::ToggleWindowAnimations,\r\n      \"Window animations\",\r\n      true,\r\n      animations_enabled,\r\n      None,\r\n    );\r\n\r\n    let run_on_startup_item = CheckMenuItem::with_id(\r\n      TrayMenuId::RunOnStartup,\r\n      \"Run on system startup\",\r\n      true,\r\n      run_on_startup_enabled,\r\n      None,\r\n    );\r\n\r\n    let exit_item =\r\n      MenuItem::with_id(TrayMenuId::Exit, \"Exit\", true, None);\r\n\r\n    let tray_menu = Menu::new();\r\n    tray_menu.append_items(&[\r\n      &reload_config_item,\r\n      &config_dir_item,\r\n      #[cfg(target_os = \"windows\")]\r\n      &toggle_animations_item,\r\n      &run_on_startup_item,\r\n      &PredefinedMenuItem::separator(),\r\n      &exit_item,\r\n    ])?;\r\n\r\n    let icon = Self::load_icon(include_bytes!(\r\n      \"../../../resources/assets/icon.png\"\r\n    ))?;\r\n\r\n    let tray_icon = TrayIconBuilder::new()\r\n      .with_menu(Box::new(tray_menu))\r\n      .with_tooltip(format!(\"GlazeWM v{}\", env!(\"VERSION_NUMBER\")))\r\n      .with_icon(icon)\r\n      .build()?;\r\n\r\n    Ok(tray_icon)\r\n  }\r\n\r\n  fn load_icon(bytes: &[u8]) -> anyhow::Result<Icon> {\r\n    let (icon_rgba, icon_width, icon_height) = {\r\n      let image = image::load_from_memory(bytes)\r\n        .context(\"Failed to to create tray icon image from resource.\")?\r\n        .into_rgba8();\r\n\r\n      let (width, height) = image.dimensions();\r\n      let rgba = image.into_raw();\r\n      (rgba, width, height)\r\n    };\r\n\r\n    Ok(tray_icon::Icon::from_rgba(\r\n      icon_rgba,\r\n      icon_width,\r\n      icon_height,\r\n    )?)\r\n  }\r\n\r\n  fn handle_menu_event(\r\n    menu_id: &TrayMenuId,\r\n    dispatcher: &Dispatcher,\r\n    config_path: &Path,\r\n    config_reload_tx: &mpsc::UnboundedSender<()>,\r\n    exit_tx: &mpsc::UnboundedSender<()>,\r\n    // LINT: `animations_enabled` is only used on Windows.\r\n    #[cfg_attr(not(target_os = \"windows\"), allow(unused_variables))]\r\n    animations_enabled: &Arc<Mutex<bool>>,\r\n    run_on_startup_enabled: &Arc<Mutex<bool>>,\r\n  ) -> anyhow::Result<()> {\r\n    tracing::info!(\"Processing tray menu event: {:?}\", menu_id);\r\n\r\n    match menu_id {\r\n      TrayMenuId::ShowConfigFolder => {\r\n        dispatcher.open_file_explorer(config_path)?;\r\n        Ok(())\r\n      }\r\n      TrayMenuId::ReloadConfig => {\r\n        config_reload_tx.send(())?;\r\n        Ok(())\r\n      }\r\n      #[cfg(target_os = \"windows\")]\r\n      TrayMenuId::ToggleWindowAnimations => {\r\n        let mut animations_enabled = animations_enabled.lock().unwrap();\r\n        dispatcher.set_window_animations_enabled(!*animations_enabled)?;\r\n        *animations_enabled = !*animations_enabled;\r\n        Ok(())\r\n      }\r\n      TrayMenuId::RunOnStartup => {\r\n        let mut run_on_startup_enabled =\r\n          run_on_startup_enabled.lock().unwrap();\r\n\r\n        if *run_on_startup_enabled {\r\n          auto_launch_instance()?.disable()?;\r\n        } else {\r\n          auto_launch_instance()?.enable()?;\r\n        }\r\n\r\n        *run_on_startup_enabled = !*run_on_startup_enabled;\r\n        Ok(())\r\n      }\r\n      TrayMenuId::Exit => {\r\n        exit_tx.send(())?;\r\n        Ok(())\r\n      }\r\n    }\r\n  }\r\n}\r\n\r\n/// Creates a new [`AutoLaunch`] instance for managing auto-launch at\r\n/// system startup.\r\nfn auto_launch_instance() -> anyhow::Result<AutoLaunch> {\r\n  // TODO: Is wrapping the exe path in quotes necessary?\r\n  let formatted_exe_path =\r\n    format!(\"\\\"{}\\\"\", std::env::current_exe()?.to_string_lossy());\r\n  let args: [&str; 0] = [];\r\n\r\n  #[cfg(target_os = \"windows\")]\r\n  let instance = AutoLaunch::new(\"GlazeWM\", &formatted_exe_path, &args);\r\n\r\n  #[cfg(target_os = \"macos\")]\r\n  let instance =\r\n    AutoLaunch::new(\"GlazeWM\", &formatted_exe_path, false, &args);\r\n\r\n  Ok(instance)\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/traits/common_getters.rs",
    "content": "use std::{\r\n  cell::{Ref, RefMut},\r\n  collections::VecDeque,\r\n};\r\n\r\nuse ambassador::delegatable_trait;\r\nuse uuid::Uuid;\r\nuse wm_common::ContainerDto;\r\n\r\nuse crate::models::{\r\n  Container, DirectionContainer, Monitor, TilingContainer,\r\n  WindowContainer, Workspace,\r\n};\r\n\r\n#[delegatable_trait]\r\npub trait CommonGetters {\r\n  /// A unique identifier for the container.\r\n  fn id(&self) -> Uuid;\r\n\r\n  fn as_container(&self) -> Container;\r\n\r\n  fn as_tiling_container(&self) -> anyhow::Result<TilingContainer>;\r\n\r\n  fn as_window_container(&self) -> anyhow::Result<WindowContainer>;\r\n\r\n  fn as_direction_container(&self) -> anyhow::Result<DirectionContainer>;\r\n\r\n  fn to_dto(&self) -> anyhow::Result<ContainerDto>;\r\n\r\n  fn borrow_parent(&self) -> Ref<'_, Option<Container>>;\r\n\r\n  fn borrow_parent_mut(&self) -> RefMut<'_, Option<Container>>;\r\n\r\n  fn borrow_children(&self) -> Ref<'_, VecDeque<Container>>;\r\n\r\n  fn borrow_children_mut(&self) -> RefMut<'_, VecDeque<Container>>;\r\n\r\n  fn borrow_child_focus_order(&self) -> Ref<'_, VecDeque<Uuid>>;\r\n\r\n  fn borrow_child_focus_order_mut(&self) -> RefMut<'_, VecDeque<Uuid>>;\r\n\r\n  /// Gets the parent container, unless this container is the root.\r\n  fn parent(&self) -> Option<Container> {\r\n    self.borrow_parent().clone()\r\n  }\r\n\r\n  /// Direct children of this container.\r\n  fn children(&self) -> VecDeque<Container> {\r\n    self.borrow_children().clone()\r\n  }\r\n\r\n  /// Number of children that this container has.\r\n  fn child_count(&self) -> usize {\r\n    self.borrow_children().len()\r\n  }\r\n\r\n  /// Whether this container has any direct children.\r\n  fn has_children(&self) -> bool {\r\n    !self.borrow_children().is_empty()\r\n  }\r\n\r\n  /// Whether this container is detached from the tree (i.e. it does not\r\n  /// have a parent).\r\n  fn is_detached(&self) -> bool {\r\n    self.borrow_parent().as_ref().is_none()\r\n  }\r\n\r\n  /// Index of this container amongst its siblings.\r\n  ///\r\n  /// Returns 0 if the container has no parent.\r\n  fn index(&self) -> usize {\r\n    self\r\n      .borrow_parent()\r\n      .as_ref()\r\n      .and_then(|parent| {\r\n        parent\r\n          .borrow_children()\r\n          .iter()\r\n          .position(|child| child.id() == self.id())\r\n      })\r\n      .unwrap_or(0)\r\n  }\r\n\r\n  /// Gets child container with the given ID.\r\n  fn child_by_id(&self, child_id: &Uuid) -> Option<Container> {\r\n    self\r\n      .borrow_children()\r\n      .iter()\r\n      .find(|child| &child.id() == child_id)\r\n      .cloned()\r\n  }\r\n\r\n  fn tiling_children(\r\n    &self,\r\n  ) -> Box<dyn Iterator<Item = TilingContainer> + '_> {\r\n    Box::new(\r\n      self\r\n        .children()\r\n        .into_iter()\r\n        .filter_map(|container| container.try_into().ok()),\r\n    )\r\n  }\r\n\r\n  fn descendants(&self) -> Descendants {\r\n    Descendants {\r\n      stack: self.children(),\r\n    }\r\n  }\r\n\r\n  fn self_and_descendants(&self) -> Descendants {\r\n    let mut stack = self.children();\r\n    stack.push_front(self.as_container());\r\n    Descendants { stack }\r\n  }\r\n\r\n  /// Children in order of last focus.\r\n  fn child_focus_order(&self) -> Box<dyn Iterator<Item = Container> + '_> {\r\n    let child_focus_order = self.borrow_child_focus_order();\r\n\r\n    Box::new(std::iter::from_fn(move || {\r\n      for child_id in child_focus_order.iter() {\r\n        if let Some(child) = self.child_by_id(child_id) {\r\n          return Some(child);\r\n        }\r\n      }\r\n\r\n      None\r\n    }))\r\n  }\r\n\r\n  /// Leaf nodes (i.e. windows and workspaces) in order of last focus.\r\n  fn descendant_focus_order(\r\n    &self,\r\n  ) -> Box<dyn Iterator<Item = Container> + '_> {\r\n    let mut stack = Vec::new();\r\n    stack.push(self.as_container());\r\n\r\n    Box::new(std::iter::from_fn(move || {\r\n      while let Some(current) = stack.pop() {\r\n        // Get containers that have no children. Descendant also cannot be\r\n        // the container itself.\r\n        if current.id() != self.id() && !current.has_children() {\r\n          return Some(current);\r\n        }\r\n\r\n        // Reverse the child focus order so that the first element is\r\n        // pushed last and popped first.\r\n        for focus_child_id in\r\n          current.borrow_child_focus_order().iter().rev()\r\n        {\r\n          if let Some(focus_child) = current.child_by_id(focus_child_id) {\r\n            stack.push(focus_child);\r\n          }\r\n        }\r\n      }\r\n\r\n      None\r\n    }))\r\n  }\r\n\r\n  fn siblings(&self) -> Box<dyn Iterator<Item = Container> + '_> {\r\n    Box::new(\r\n      self\r\n        .parent()\r\n        .into_iter()\r\n        .flat_map(|parent| parent.children())\r\n        .filter(move |sibling| sibling.id() != self.id()),\r\n    )\r\n  }\r\n\r\n  fn self_and_siblings(&self) -> Box<dyn Iterator<Item = Container> + '_> {\r\n    Box::new(\r\n      self\r\n        .parent()\r\n        .into_iter()\r\n        .flat_map(|parent| parent.children()),\r\n    )\r\n  }\r\n\r\n  fn prev_siblings(&self) -> Box<dyn Iterator<Item = Container> + '_> {\r\n    Box::new(\r\n      self\r\n        .self_and_siblings()\r\n        .collect::<Vec<_>>()\r\n        .into_iter()\r\n        .take(self.index())\r\n        .rev(),\r\n    )\r\n  }\r\n\r\n  fn next_siblings(&self) -> Box<dyn Iterator<Item = Container> + '_> {\r\n    Box::new(\r\n      self\r\n        .self_and_siblings()\r\n        .collect::<Vec<_>>()\r\n        .into_iter()\r\n        .skip(self.index() + 1),\r\n    )\r\n  }\r\n\r\n  fn tiling_siblings(\r\n    &self,\r\n  ) -> Box<dyn Iterator<Item = TilingContainer> + '_> {\r\n    Box::new(\r\n      self\r\n        .siblings()\r\n        .filter_map(|container| container.try_into().ok()),\r\n    )\r\n  }\r\n\r\n  fn ancestors(&self) -> Ancestors {\r\n    Ancestors {\r\n      start: self.parent(),\r\n    }\r\n  }\r\n\r\n  fn self_and_ancestors(&self) -> Ancestors {\r\n    Ancestors {\r\n      start: Some(self.as_container()),\r\n    }\r\n  }\r\n\r\n  /// Workspace that this container belongs to.\r\n  ///\r\n  /// Note that this might return the container itself.\r\n  fn workspace(&self) -> Option<Workspace> {\r\n    self\r\n      .self_and_ancestors()\r\n      .find_map(|container| container.as_workspace().cloned())\r\n  }\r\n\r\n  /// Monitor that this container belongs to.\r\n  ///\r\n  /// Note that this might return the container itself.\r\n  fn monitor(&self) -> Option<Monitor> {\r\n    self\r\n      .self_and_ancestors()\r\n      .find_map(|container| container.as_monitor().cloned())\r\n  }\r\n\r\n  /// Nearest direction container (i.e. split container or workspace) that\r\n  /// this container belongs to.\r\n  ///\r\n  /// Note that this might return the container itself.\r\n  fn direction_container(&self) -> Option<DirectionContainer> {\r\n    self\r\n      .self_and_ancestors()\r\n      .find_map(|container| container.try_into().ok())\r\n  }\r\n\r\n  /// Index of this container in parent's child focus order.\r\n  ///\r\n  /// Returns 0 if the container has no parent.\r\n  fn focus_index(&self) -> usize {\r\n    self\r\n      .parent()\r\n      .and_then(|parent| {\r\n        parent\r\n          .borrow_child_focus_order()\r\n          .iter()\r\n          .position(|id| id == &self.id())\r\n      })\r\n      .unwrap_or(0)\r\n  }\r\n\r\n  /// Whether this container or a descendant has focus.\r\n  ///\r\n  /// If `end_ancestor` is provided, then the check for focus will be up to\r\n  /// and including the `end_ancestor`.\r\n  fn has_focus(&self, end_ancestor: Option<Container>) -> bool {\r\n    self\r\n      .self_and_ancestors()\r\n      .take_while(|ancestor| end_ancestor.as_ref() != Some(ancestor))\r\n      .chain(end_ancestor.clone())\r\n      .all(|ancestor| ancestor.focus_index() == 0)\r\n  }\r\n}\r\n\r\n/// An iterator over ancestors of a given container.\r\npub struct Ancestors {\r\n  start: Option<Container>,\r\n}\r\n\r\nimpl Iterator for Ancestors {\r\n  type Item = Container;\r\n\r\n  fn next(&mut self) -> Option<Container> {\r\n    self.start.take().inspect(|container| {\r\n      self.start = container.parent();\r\n    })\r\n  }\r\n}\r\n\r\n/// An iterator over descendants of a given container.\r\npub struct Descendants {\r\n  stack: VecDeque<Container>,\r\n}\r\n\r\nimpl Iterator for Descendants {\r\n  type Item = Container;\r\n\r\n  fn next(&mut self) -> Option<Container> {\r\n    if let Some(container) = self.stack.pop_front() {\r\n      self.stack.extend(container.children());\r\n      return Some(container);\r\n    }\r\n    None\r\n  }\r\n}\r\n\r\n/// Implements the `CommonGetters` trait for a given struct.\r\n///\r\n/// Expects that the struct has a wrapping `RefCell` containing a struct\r\n/// with an `id`, `parent`, `children`, and `child_focus_order` field.\r\n#[macro_export]\r\nmacro_rules! impl_common_getters {\r\n  ($struct_name:ident) => {\r\n    impl CommonGetters for $struct_name {\r\n      fn id(&self) -> Uuid {\r\n        self.0.borrow().id\r\n      }\r\n\r\n      fn as_container(&self) -> Container {\r\n        self.clone().into()\r\n      }\r\n\r\n      fn as_tiling_container(&self) -> anyhow::Result<TilingContainer> {\r\n        TryInto::<TilingContainer>::try_into(self.as_container())\r\n          .map_err(anyhow::Error::msg)\r\n      }\r\n\r\n      fn as_window_container(&self) -> anyhow::Result<WindowContainer> {\r\n        TryInto::<WindowContainer>::try_into(self.as_container())\r\n          .map_err(anyhow::Error::msg)\r\n      }\r\n\r\n      fn as_direction_container(\r\n        &self,\r\n      ) -> anyhow::Result<DirectionContainer> {\r\n        TryInto::<DirectionContainer>::try_into(self.as_container())\r\n          .map_err(anyhow::Error::msg)\r\n      }\r\n\r\n      fn to_dto(&self) -> anyhow::Result<ContainerDto> {\r\n        self.to_dto()\r\n      }\r\n\r\n      fn borrow_parent(&self) -> Ref<'_, Option<Container>> {\r\n        Ref::map(self.0.borrow(), |inner| &inner.parent)\r\n      }\r\n\r\n      fn borrow_parent_mut(&self) -> RefMut<'_, Option<Container>> {\r\n        RefMut::map(self.0.borrow_mut(), |inner| &mut inner.parent)\r\n      }\r\n\r\n      fn borrow_children(&self) -> Ref<'_, VecDeque<Container>> {\r\n        Ref::map(self.0.borrow(), |inner| &inner.children)\r\n      }\r\n\r\n      fn borrow_children_mut(&self) -> RefMut<'_, VecDeque<Container>> {\r\n        RefMut::map(self.0.borrow_mut(), |inner| &mut inner.children)\r\n      }\r\n\r\n      fn borrow_child_focus_order(&self) -> Ref<'_, VecDeque<Uuid>> {\r\n        Ref::map(self.0.borrow(), |inner| &inner.child_focus_order)\r\n      }\r\n\r\n      fn borrow_child_focus_order_mut(\r\n        &self,\r\n      ) -> RefMut<'_, VecDeque<Uuid>> {\r\n        RefMut::map(self.0.borrow_mut(), |inner| {\r\n          &mut inner.child_focus_order\r\n        })\r\n      }\r\n    }\r\n  };\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/traits/mod.rs",
    "content": "mod common_getters;\r\nmod position_getters;\r\nmod tiling_direction_getters;\r\nmod tiling_size_getters;\r\nmod window_getters;\r\n\r\npub use common_getters::*;\r\npub use position_getters::*;\r\npub use tiling_direction_getters::*;\r\npub use tiling_size_getters::*;\r\npub use window_getters::*;\r\n"
  },
  {
    "path": "packages/wm/src/traits/position_getters.rs",
    "content": "use ambassador::delegatable_trait;\r\nuse wm_platform::Rect;\r\n\r\n#[delegatable_trait]\r\npub trait PositionGetters {\r\n  fn to_rect(&self) -> anyhow::Result<Rect>;\r\n}\r\n\r\n/// Implements the `PositionGetters` trait for tiling containers that can\r\n/// be resized. This is used by `SplitContainer` and `TilingWindow`.\r\n///\r\n/// Expects that the struct has a wrapping `RefCell` containing a struct\r\n/// with an `id` and a `parent` field.\r\n#[macro_export]\r\nmacro_rules! impl_position_getters_as_resizable {\r\n  ($struct_name:ident) => {\r\n    impl PositionGetters for $struct_name {\r\n      fn to_rect(&self) -> anyhow::Result<Rect> {\r\n        let parent = self\r\n          .parent()\r\n          .and_then(|parent| parent.as_direction_container().ok())\r\n          .context(\"Parent does not have a tiling direction.\")?;\r\n\r\n        let parent_rect = parent.to_rect()?;\r\n\r\n        let (horizontal_gap, vertical_gap) = self.inner_gaps()?;\r\n        let inner_gap = match parent.tiling_direction() {\r\n          TilingDirection::Vertical => vertical_gap,\r\n          TilingDirection::Horizontal => horizontal_gap,\r\n        };\r\n\r\n        #[allow(\r\n          clippy::cast_precision_loss,\r\n          clippy::cast_possible_truncation,\r\n          clippy::cast_possible_wrap\r\n        )]\r\n        let (width, height) = match parent.tiling_direction() {\r\n          TilingDirection::Vertical => {\r\n            let available_height = parent_rect.height()\r\n              - inner_gap * self.tiling_siblings().count() as i32;\r\n\r\n            let height =\r\n              (self.tiling_size() * available_height as f32) as i32;\r\n\r\n            (parent_rect.width(), height)\r\n          }\r\n          TilingDirection::Horizontal => {\r\n            let available_width = parent_rect.width()\r\n              - inner_gap * self.tiling_siblings().count() as i32;\r\n\r\n            let width =\r\n              (available_width as f32 * self.tiling_size()).round() as i32;\r\n\r\n            (width, parent_rect.height())\r\n          }\r\n        };\r\n\r\n        let (x, y) = {\r\n          let mut prev_siblings = self\r\n            .prev_siblings()\r\n            .filter_map(|sibling| sibling.as_tiling_container().ok());\r\n\r\n          match prev_siblings.next() {\r\n            None => (parent_rect.x(), parent_rect.y()),\r\n            Some(sibling) => {\r\n              let sibling_rect = sibling.to_rect()?;\r\n\r\n              match parent.tiling_direction() {\r\n                TilingDirection::Vertical => (\r\n                  parent_rect.x(),\r\n                  sibling_rect.y() + sibling_rect.height() + inner_gap,\r\n                ),\r\n                TilingDirection::Horizontal => (\r\n                  sibling_rect.x() + sibling_rect.width() + inner_gap,\r\n                  parent_rect.y(),\r\n                ),\r\n              }\r\n            }\r\n          }\r\n        };\r\n\r\n        Ok(Rect::from_xy(x, y, width, height))\r\n      }\r\n    }\r\n  };\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/traits/tiling_direction_getters.rs",
    "content": "use ambassador::delegatable_trait;\r\nuse wm_common::TilingDirection;\r\nuse wm_platform::Direction;\r\n\r\nuse super::CommonGetters;\r\nuse crate::models::{TilingContainer, TilingWindow};\r\n\r\n#[delegatable_trait]\r\npub trait TilingDirectionGetters: CommonGetters {\r\n  fn tiling_direction(&self) -> TilingDirection;\r\n\r\n  fn set_tiling_direction(&self, tiling_direction: TilingDirection);\r\n\r\n  /// Traverses down a container in search of a descendant in the given\r\n  /// direction. For example, for `Direction::Right`, get the right-most\r\n  /// container.\r\n  ///\r\n  /// Any non-tiling containers are ignored.\r\n  fn descendant_in_direction(\r\n    &self,\r\n    direction: &Direction,\r\n  ) -> Option<TilingWindow> {\r\n    let child = self.child_in_direction(direction)?;\r\n\r\n    // Traverse further down if the child is a split container.\r\n    match child {\r\n      TilingContainer::Split(split_child) => {\r\n        split_child.descendant_in_direction(direction)\r\n      }\r\n      TilingContainer::TilingWindow(window) => Some(window),\r\n    }\r\n  }\r\n\r\n  fn child_in_direction(\r\n    &self,\r\n    direction: &Direction,\r\n  ) -> Option<TilingContainer> {\r\n    // When the tiling direction is the inverse of the direction, return\r\n    // the last focused tiling child.\r\n    if self.tiling_direction()\r\n      != TilingDirection::from_direction(direction)\r\n    {\r\n      return self\r\n        .child_focus_order()\r\n        .find_map(|c| c.as_tiling_container().ok());\r\n    }\r\n\r\n    match direction {\r\n      Direction::Up | Direction::Left => self.tiling_children().next(),\r\n      _ => self.tiling_children().last(),\r\n    }\r\n  }\r\n}\r\n\r\n/// Implements the `TilingDirectionGetters` trait for a given struct.\r\n///\r\n/// Expects that the struct has a wrapping `RefCell` containing a struct\r\n/// with a `tiling_direction` field.\r\n#[macro_export]\r\nmacro_rules! impl_tiling_direction_getters {\r\n  ($struct_name:ident) => {\r\n    impl TilingDirectionGetters for $struct_name {\r\n      fn tiling_direction(&self) -> TilingDirection {\r\n        self.0.borrow().tiling_direction.clone()\r\n      }\r\n\r\n      fn set_tiling_direction(&self, tiling_direction: TilingDirection) {\r\n        self.0.borrow_mut().tiling_direction = tiling_direction;\r\n      }\r\n    }\r\n  };\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/traits/tiling_size_getters.rs",
    "content": "use std::cell::Ref;\r\n\r\nuse ambassador::delegatable_trait;\r\nuse anyhow::Context;\r\nuse wm_common::{GapsConfig, TilingDirection};\r\n\r\nuse super::{CommonGetters, TilingDirectionGetters};\r\nuse crate::models::{Container, DirectionContainer, TilingContainer};\r\n\r\npub const MIN_TILING_SIZE: f32 = 0.01;\r\n\r\n#[delegatable_trait]\r\npub trait TilingSizeGetters: CommonGetters {\r\n  fn tiling_size(&self) -> f32;\r\n\r\n  fn set_tiling_size(&self, tiling_size: f32);\r\n\r\n  fn gaps_config(&self) -> Ref<'_, GapsConfig>;\r\n\r\n  fn set_gaps_config(&self, gaps_config: GapsConfig);\r\n\r\n  /// Gets the horizontal and vertical gaps between windows in pixels.\r\n  fn inner_gaps(&self) -> anyhow::Result<(i32, i32)> {\r\n    let monitor = self.monitor().context(\"No monitor.\")?;\r\n    let monitor_rect = monitor.native_properties().bounds;\r\n    let gaps_config = self.gaps_config();\r\n\r\n    let scale_factor = if gaps_config.scale_with_dpi {\r\n      monitor.native_properties().scale_factor\r\n    } else {\r\n      1.\r\n    };\r\n\r\n    Ok((\r\n      gaps_config\r\n        .inner_gap\r\n        .to_px(monitor_rect.height(), Some(scale_factor)),\r\n      gaps_config\r\n        .inner_gap\r\n        .to_px(monitor_rect.width(), Some(scale_factor)),\r\n    ))\r\n  }\r\n\r\n  /// Gets the container to resize when resizing a tiling window.\r\n  fn container_to_resize(\r\n    &self,\r\n    is_width_resize: bool,\r\n  ) -> anyhow::Result<Option<TilingContainer>> {\r\n    let parent = self.direction_container().context(\"No parent.\")?;\r\n\r\n    let tiling_direction = parent.tiling_direction();\r\n\r\n    // Whether the resize is in the inverse of its tiling direction.\r\n    let is_inverse_resize = match tiling_direction {\r\n      TilingDirection::Horizontal => !is_width_resize,\r\n      TilingDirection::Vertical => is_width_resize,\r\n    };\r\n\r\n    let container_to_resize = if is_inverse_resize {\r\n      match parent {\r\n        // Prevent workspaces from being resized.\r\n        DirectionContainer::Split(parent) => Some(parent.into()),\r\n        DirectionContainer::Workspace(_) => None,\r\n      }\r\n    } else {\r\n      let grandparent = parent.parent().context(\"No grandparent.\")?;\r\n\r\n      if self.tiling_siblings().count() > 0 {\r\n        // Window can only be resized if it has siblings.\r\n        Some(self.as_tiling_container()?)\r\n      } else {\r\n        // Resize grandparent in layouts like H[1 V[2 H[3]]], where\r\n        // container 3 is resized horizontally.\r\n        match grandparent {\r\n          Container::Split(grandparent) => Some(grandparent.into()),\r\n          _ => None,\r\n        }\r\n      }\r\n    };\r\n\r\n    Ok(container_to_resize)\r\n  }\r\n}\r\n\r\n/// Implements the `TilingSizeGetters` trait for a given struct.\r\n///\r\n/// Expects that the struct has a wrapping `RefCell` containing a struct\r\n/// with a `tiling_size` field.\r\n#[macro_export]\r\nmacro_rules! impl_tiling_size_getters {\r\n  ($struct_name:ident) => {\r\n    impl TilingSizeGetters for $struct_name {\r\n      fn tiling_size(&self) -> f32 {\r\n        self.0.borrow().tiling_size\r\n      }\r\n\r\n      fn set_tiling_size(&self, tiling_size: f32) {\r\n        self.0.borrow_mut().tiling_size = tiling_size;\r\n      }\r\n\r\n      fn gaps_config(&self) -> Ref<'_, GapsConfig> {\r\n        Ref::map(self.0.borrow(), |inner| &inner.gaps_config)\r\n      }\r\n\r\n      fn set_gaps_config(&self, gaps_config: GapsConfig) {\r\n        self.0.borrow_mut().gaps_config = gaps_config;\r\n      }\r\n    }\r\n  };\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/traits/window_getters.rs",
    "content": "use std::cell::Ref;\r\n\r\nuse ambassador::delegatable_trait;\r\nuse wm_common::{ActiveDrag, DisplayState, WindowRuleConfig, WindowState};\r\nuse wm_platform::{LengthValue, NativeWindow, Rect, RectDelta};\r\n\r\nuse crate::{\r\n  models::{NativeWindowProperties, Workspace},\r\n  traits::CommonGetters,\r\n  user_config::UserConfig,\r\n};\r\n\r\n#[delegatable_trait]\r\npub trait WindowGetters: CommonGetters {\r\n  fn state(&self) -> WindowState;\r\n\r\n  fn set_state(&self, state: WindowState);\r\n\r\n  fn prev_state(&self) -> Option<WindowState>;\r\n\r\n  fn set_prev_state(&self, state: WindowState);\r\n\r\n  /// Gets the \"toggled\" window state based on the current state and a\r\n  /// given target state.\r\n  ///\r\n  /// This will return the first valid state in the following order:\r\n  /// 1. If the window is not currently in the target state, return the\r\n  ///    target state.\r\n  /// 2. The previous state exists if one exists (excluding minimized).\r\n  /// 3. The state from `window_behavior.initial_state` in the user config.\r\n  /// 4. Default to either floating/tiling depending on the current state.\r\n  fn toggled_state(\r\n    &self,\r\n    target_state: WindowState,\r\n    config: &UserConfig,\r\n  ) -> WindowState {\r\n    let possible_states = [\r\n      Some(target_state),\r\n      self\r\n        .prev_state()\r\n        .filter(|state| *state != WindowState::Minimized),\r\n      Some(WindowState::default_from_config(&config.value)),\r\n    ];\r\n\r\n    // Return the first possible state with a different discriminant.\r\n    possible_states\r\n      .into_iter()\r\n      .find_map(|state| {\r\n        state.filter(|state| !self.state().is_same_state(state))\r\n      })\r\n      // Default to tiling from a non-tiling state, and floating from a\r\n      // tiling state.\r\n      .unwrap_or_else(|| match self.state() {\r\n        WindowState::Tiling => WindowState::Floating(\r\n          config.value.window_behavior.state_defaults.floating.clone(),\r\n        ),\r\n        _ => WindowState::Tiling,\r\n      })\r\n  }\r\n\r\n  fn native(&self) -> Ref<'_, NativeWindow>;\r\n\r\n  fn border_delta(&self) -> RectDelta;\r\n\r\n  fn set_border_delta(&self, border_delta: RectDelta);\r\n\r\n  fn total_border_delta(&self) -> anyhow::Result<RectDelta> {\r\n    let border_delta = self.border_delta();\r\n\r\n    #[cfg(target_os = \"windows\")]\r\n    let shadow_border_delta = self.native_properties().shadow_borders;\r\n    #[cfg(not(target_os = \"windows\"))]\r\n    let shadow_border_delta = RectDelta::zero();\r\n\r\n    // TODO: Allow percentage length values.\r\n    Ok(RectDelta {\r\n      left: LengthValue::from_px(\r\n        border_delta.left.to_px(0, None)\r\n          + shadow_border_delta.left.to_px(0, None),\r\n      ),\r\n      right: LengthValue::from_px(\r\n        border_delta.right.to_px(0, None)\r\n          + shadow_border_delta.right.to_px(0, None),\r\n      ),\r\n      top: LengthValue::from_px(\r\n        border_delta.top.to_px(0, None)\r\n          + shadow_border_delta.top.to_px(0, None),\r\n      ),\r\n      bottom: LengthValue::from_px(\r\n        border_delta.bottom.to_px(0, None)\r\n          + shadow_border_delta.bottom.to_px(0, None),\r\n      ),\r\n    })\r\n  }\r\n\r\n  /// Gets whether the window should be fullscreen for the given workspace.\r\n  ///\r\n  /// A window is considered fullscreen if its frame covers or exceeds the\r\n  /// workspace bounds, meaning all sides extend into the outer gaps.\r\n  ///\r\n  /// NOTE: The OS can be off by up to 1px when positioning windows.\r\n  fn should_fullscreen(\r\n    &self,\r\n    workspace: &Workspace,\r\n  ) -> anyhow::Result<bool> {\r\n    let workspace_rect = workspace.max_workspace_rect()?;\r\n    let frame = self\r\n      .native_properties()\r\n      .frame\r\n      .apply_delta(&self.border_delta().inverse(), None);\r\n\r\n    let should_fullscreen = match self.state() {\r\n      // Keep as fullscreen if the frame covers the workspace bounds.\r\n      WindowState::Fullscreen(fullscreen) if !fullscreen.maximized => {\r\n        frame.contains_rect(&workspace_rect.inset(1))\r\n      }\r\n\r\n      // Change to fullscreen if the frame *exceeds* the workspace bounds.\r\n      // NOTE: This is never possible with 0px outer gaps; the window has\r\n      // to be made fullscreen via the `set-fullscreen` command.\r\n      _ => frame.inset(1).contains_rect(&workspace_rect),\r\n    };\r\n\r\n    Ok(should_fullscreen)\r\n  }\r\n\r\n  fn display_state(&self) -> DisplayState;\r\n\r\n  fn set_display_state(&self, display_state: DisplayState);\r\n\r\n  // LINT: `has_pending_dpi_adjustment` is only used on Windows.\r\n  #[allow(unused)]\r\n  fn has_pending_dpi_adjustment(&self) -> bool;\r\n\r\n  fn set_has_pending_dpi_adjustment(\r\n    &self,\r\n    has_pending_dpi_adjustment: bool,\r\n  );\r\n\r\n  fn floating_placement(&self) -> Rect;\r\n\r\n  fn set_floating_placement(&self, floating_placement: Rect);\r\n\r\n  fn has_custom_floating_placement(&self) -> bool;\r\n\r\n  fn set_has_custom_floating_placement(\r\n    &self,\r\n    has_custom_floating_placement: bool,\r\n  );\r\n\r\n  fn done_window_rules(&self) -> Vec<WindowRuleConfig>;\r\n\r\n  fn set_done_window_rules(\r\n    &self,\r\n    done_window_rules: Vec<WindowRuleConfig>,\r\n  );\r\n\r\n  fn active_drag(&self) -> Option<ActiveDrag>;\r\n\r\n  fn set_active_drag(&self, active_drag: Option<ActiveDrag>);\r\n\r\n  /// Gets the cached native window properties.\r\n  fn native_properties(&self) -> NativeWindowProperties;\r\n\r\n  /// Updates the cached native window properties using a closure.\r\n  fn update_native_properties<F>(&self, updater: F)\r\n  where\r\n    F: FnOnce(&mut NativeWindowProperties);\r\n}\r\n\r\n/// Implements the `WindowGetters` trait for a given struct.\r\n///\r\n/// Expects that the struct has a wrapping `RefCell` containing a struct\r\n/// with a `state`, `prev_state`, `native`, `has_pending_dpi_adjustment`,\r\n/// `border_delta`, `display_state`, and a `done_window_rules` field.\r\n#[macro_export]\r\nmacro_rules! impl_window_getters {\r\n  ($struct_name:ident) => {\r\n    impl WindowGetters for $struct_name {\r\n      fn state(&self) -> WindowState {\r\n        self.0.borrow().state.clone()\r\n      }\r\n\r\n      fn set_state(&self, state: WindowState) {\r\n        self.0.borrow_mut().state = state;\r\n      }\r\n\r\n      fn prev_state(&self) -> Option<WindowState> {\r\n        self.0.borrow().prev_state.clone()\r\n      }\r\n\r\n      fn set_prev_state(&self, state: WindowState) {\r\n        self.0.borrow_mut().prev_state = Some(state);\r\n      }\r\n\r\n      fn native(&self) -> Ref<'_, NativeWindow> {\r\n        Ref::map(self.0.borrow(), |inner| &inner.native)\r\n      }\r\n\r\n      fn border_delta(&self) -> RectDelta {\r\n        self.0.borrow().border_delta.clone()\r\n      }\r\n\r\n      fn set_border_delta(&self, border_delta: RectDelta) {\r\n        self.0.borrow_mut().border_delta = border_delta;\r\n      }\r\n\r\n      fn display_state(&self) -> DisplayState {\r\n        self.0.borrow().display_state.clone()\r\n      }\r\n\r\n      fn set_display_state(&self, display_state: DisplayState) {\r\n        self.0.borrow_mut().display_state = display_state;\r\n      }\r\n\r\n      // LINT: `has_pending_dpi_adjustment` is only used on Windows.\r\n      #[allow(unused)]\r\n      fn has_pending_dpi_adjustment(&self) -> bool {\r\n        self.0.borrow().has_pending_dpi_adjustment\r\n      }\r\n\r\n      fn set_has_pending_dpi_adjustment(\r\n        &self,\r\n        has_pending_dpi_adjustment: bool,\r\n      ) {\r\n        self.0.borrow_mut().has_pending_dpi_adjustment =\r\n          has_pending_dpi_adjustment;\r\n      }\r\n\r\n      fn floating_placement(&self) -> Rect {\r\n        self.0.borrow().floating_placement.clone()\r\n      }\r\n\r\n      fn set_floating_placement(&self, floating_placement: Rect) {\r\n        self.0.borrow_mut().floating_placement = floating_placement;\r\n      }\r\n\r\n      fn has_custom_floating_placement(&self) -> bool {\r\n        self.0.borrow().has_custom_floating_placement.clone()\r\n      }\r\n\r\n      fn set_has_custom_floating_placement(\r\n        &self,\r\n        has_custom_floating_placement: bool,\r\n      ) {\r\n        self.0.borrow_mut().has_custom_floating_placement =\r\n          has_custom_floating_placement;\r\n      }\r\n\r\n      fn done_window_rules(&self) -> Vec<WindowRuleConfig> {\r\n        self.0.borrow().done_window_rules.clone()\r\n      }\r\n\r\n      fn set_done_window_rules(\r\n        &self,\r\n        done_window_rules: Vec<WindowRuleConfig>,\r\n      ) {\r\n        self.0.borrow_mut().done_window_rules = done_window_rules;\r\n      }\r\n\r\n      fn active_drag(&self) -> Option<ActiveDrag> {\r\n        self.0.borrow().active_drag.clone()\r\n      }\r\n\r\n      fn set_active_drag(&self, active_drag: Option<ActiveDrag>) {\r\n        self.0.borrow_mut().active_drag = active_drag;\r\n      }\r\n\r\n      fn native_properties(&self) -> NativeWindowProperties {\r\n        self.0.borrow().native_properties.clone()\r\n      }\r\n\r\n      fn update_native_properties<F>(&self, updater: F)\r\n      where\r\n        F: FnOnce(&mut NativeWindowProperties),\r\n      {\r\n        updater(&mut self.0.borrow_mut().native_properties);\r\n      }\r\n    }\r\n  };\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/user_config.rs",
    "content": "use std::{collections::HashMap, env, fs, path::PathBuf};\r\n\r\nuse anyhow::{Context, Result};\r\nuse wm_common::{\r\n  InvokeCommand, KeybindingConfig, MatchType, ParsedConfig,\r\n  WindowMatchConfig, WindowRuleConfig, WindowRuleEvent, WorkspaceConfig,\r\n};\r\n\r\nuse crate::{\r\n  models::{Monitor, WindowContainer, Workspace},\r\n  traits::{CommonGetters, WindowGetters},\r\n};\r\n\r\n/// Resource string for the sample config file.\r\nconst SAMPLE_CONFIG: &str =\r\n  include_str!(\"../../../resources/assets/sample-config.yaml\");\r\n\r\n#[derive(Debug)]\r\npub struct UserConfig {\r\n  /// Path to the user config file.\r\n  pub path: PathBuf,\r\n\r\n  /// Parsed user config value.\r\n  pub value: ParsedConfig,\r\n\r\n  /// Unparsed user config string.\r\n  pub value_str: String,\r\n\r\n  /// Hashmap of window rule event types (e.g. `WindowRuleEvent::Manage`)\r\n  /// and the corresponding window rules of that type.\r\n  window_rules_by_event: HashMap<WindowRuleEvent, Vec<WindowRuleConfig>>,\r\n}\r\n\r\nimpl UserConfig {\r\n  /// Creates an instance of `UserConfig`. Reads and validates the user\r\n  /// config from the given path.\r\n  ///\r\n  /// Creates a new config file from sample if it doesn't exist.\r\n  pub fn new(config_path: Option<PathBuf>) -> anyhow::Result<Self> {\r\n    let default_config_path = home::home_dir()\r\n      .context(\"Unable to get home directory.\")?\r\n      .join(\".glzr/glazewm/config.yaml\");\r\n\r\n    let config_path = config_path\r\n      .or_else(|| env::var(\"GLAZEWM_CONFIG_PATH\").ok().map(PathBuf::from))\r\n      .unwrap_or(default_config_path);\r\n\r\n    let (config_value, config_str) = Self::read(&config_path)?;\r\n\r\n    let window_rules_by_event = Self::window_rules_by_event(&config_value);\r\n\r\n    Ok(Self {\r\n      path: config_path,\r\n      value: config_value,\r\n      value_str: config_str,\r\n      window_rules_by_event,\r\n    })\r\n  }\r\n\r\n  /// Reads and validates the user config from the given path.\r\n  ///\r\n  /// Creates a new config file from sample if it doesn't exist.\r\n  fn read(\r\n    config_path: &PathBuf,\r\n  ) -> anyhow::Result<(ParsedConfig, String)> {\r\n    if !config_path.exists() {\r\n      Self::create_sample(config_path)?;\r\n    }\r\n\r\n    let config_str = fs::read_to_string(config_path)\r\n      .context(\"Unable to read config file.\")?;\r\n\r\n    // TODO: Improve error formatting of serde_yaml errors. Something\r\n    // similar to https://github.com/AlexanderThaller/format_serde_error\r\n    let config_value = serde_yaml::from_str(&config_str)?;\r\n\r\n    Ok((config_value, config_str))\r\n  }\r\n\r\n  /// Initializes a new config file from the sample config resource.\r\n  fn create_sample(config_path: &PathBuf) -> Result<()> {\r\n    let parent_dir =\r\n      config_path.parent().context(\"Invalid config path.\")?;\r\n\r\n    fs::create_dir_all(parent_dir).with_context(|| {\r\n      format!(\"Unable to create directory {}.\", &config_path.display())\r\n    })?;\r\n\r\n    fs::write(config_path, SAMPLE_CONFIG).with_context(|| {\r\n      format!(\"Unable to write to {}.\", config_path.display())\r\n    })?;\r\n\r\n    Ok(())\r\n  }\r\n\r\n  pub fn reload(&mut self) -> anyhow::Result<()> {\r\n    let (config_value, config_str) = Self::read(&self.path)?;\r\n\r\n    self.window_rules_by_event =\r\n      Self::window_rules_by_event(&config_value);\r\n    self.value = config_value;\r\n    self.value_str = config_str;\r\n\r\n    Ok(())\r\n  }\r\n\r\n  fn default_window_rules(\r\n    config_value: &ParsedConfig,\r\n  ) -> Vec<WindowRuleConfig> {\r\n    let mut window_rules = Vec::new();\r\n\r\n    let floating_defaults =\r\n      &config_value.window_behavior.state_defaults.floating;\r\n\r\n    // Default float rules.\r\n    window_rules.push(WindowRuleConfig {\r\n      commands: vec![InvokeCommand::SetFloating {\r\n        centered: Some(floating_defaults.centered),\r\n        shown_on_top: Some(floating_defaults.shown_on_top),\r\n        x_pos: None,\r\n        y_pos: None,\r\n        width: None,\r\n        height: None,\r\n      }],\r\n      match_window: vec![\r\n        WindowMatchConfig {\r\n          window_class: Some(MatchType::Equals { equals:\r\n          // W10/W11 system dialog shown when moving and deleting files.\r\n          \"OperationStatusWindow\".to_string(),\r\n        }),\r\n          ..WindowMatchConfig::default()\r\n        },\r\n        WindowMatchConfig {\r\n          window_class: Some(MatchType::Equals { equals:\r\n          // W10/W11 system dialogs (e.g. File Explorer save/open dialog).\r\n          \"#32770\".to_string(),\r\n        }),\r\n          ..WindowMatchConfig::default()\r\n        },\r\n      ],\r\n      on: vec![WindowRuleEvent::Manage],\r\n      run_once: true,\r\n    });\r\n\r\n    // Default ignore rules.\r\n    window_rules.push(WindowRuleConfig {\r\n      commands: vec![InvokeCommand::Ignore],\r\n      match_window: vec![\r\n        WindowMatchConfig {\r\n          window_process: Some(MatchType::Equals {\r\n            equals: \"SearchApp\".to_string(),\r\n          }),\r\n          ..WindowMatchConfig::default()\r\n        },\r\n        WindowMatchConfig {\r\n          window_process: Some(MatchType::Equals {\r\n            equals: \"SearchHost\".to_string(),\r\n          }),\r\n          ..WindowMatchConfig::default()\r\n        },\r\n        WindowMatchConfig {\r\n          window_process: Some(MatchType::Equals {\r\n            equals: \"ShellExperienceHost\".to_string(),\r\n          }),\r\n          ..WindowMatchConfig::default()\r\n        },\r\n        WindowMatchConfig {\r\n          window_process: Some(MatchType::Equals {\r\n            // W10/11 start menu.\r\n            equals: \"StartMenuExperienceHost\".to_string(),\r\n          }),\r\n          ..WindowMatchConfig::default()\r\n        },\r\n        WindowMatchConfig {\r\n          window_process: Some(MatchType::Equals {\r\n            // W10/11 screen snipping tool.\r\n            equals: \"ScreenClippingHost\".to_string(),\r\n          }),\r\n          ..WindowMatchConfig::default()\r\n        },\r\n        WindowMatchConfig {\r\n          window_process: Some(MatchType::Equals {\r\n            // W11 lock screen.\r\n            equals: \"LockApp\".to_string(),\r\n          }),\r\n          ..WindowMatchConfig::default()\r\n        },\r\n      ],\r\n      on: vec![WindowRuleEvent::Manage],\r\n      run_once: true,\r\n    });\r\n\r\n    window_rules\r\n  }\r\n\r\n  fn window_rules_by_event(\r\n    config_value: &ParsedConfig,\r\n  ) -> HashMap<WindowRuleEvent, Vec<WindowRuleConfig>> {\r\n    let mut window_rules_by_event = HashMap::new();\r\n\r\n    // Combine user-defined window rules with the default ones.\r\n    let default_window_rules = Self::default_window_rules(config_value);\r\n    let all_window_rules = config_value\r\n      .window_rules\r\n      .iter()\r\n      .chain(default_window_rules.iter());\r\n\r\n    for window_rule in all_window_rules {\r\n      for event_type in &window_rule.on {\r\n        window_rules_by_event\r\n          .entry(event_type.clone())\r\n          .or_insert_with(Vec::new)\r\n          .push(window_rule.clone());\r\n      }\r\n    }\r\n\r\n    window_rules_by_event\r\n  }\r\n\r\n  /// Window rules that should be applied to the window when the given\r\n  /// event occurs.\r\n  pub fn pending_window_rules(\r\n    &self,\r\n    window: &WindowContainer,\r\n    event: &WindowRuleEvent,\r\n  ) -> Vec<WindowRuleConfig> {\r\n    let window_title = window.native_properties().title;\r\n    #[cfg(target_os = \"windows\")]\r\n    let window_class = window.native_properties().class_name;\r\n    let window_process = window.native_properties().process_name;\r\n\r\n    let pending_window_rules = self\r\n      .window_rules_by_event\r\n      .get(event)\r\n      .unwrap_or(&Vec::new())\r\n      .iter()\r\n      .filter(|rule| {\r\n        // Skip if window has already ran the rule.\r\n        if window.done_window_rules().contains(rule) {\r\n          return false;\r\n        }\r\n\r\n        // Check if the window matches the rule.\r\n        rule.match_window.iter().any(|match_config| {\r\n          let is_process_match = match_config\r\n            .window_process\r\n            .as_ref()\r\n            .is_none_or(|match_type| {\r\n              // TODO: Temp fix for matching Zebar on both platforms with\r\n              // the same process name. Consider using lowercase for every\r\n              // `equals` match type.\r\n              if window_process == \"Zebar\" {\r\n                match_type.is_match(\"Zebar\")\r\n                  || match_type.is_match(\"zebar\")\r\n              } else {\r\n                match_type.is_match(&window_process)\r\n              }\r\n            });\r\n\r\n          let is_class_match = {\r\n            #[cfg(target_os = \"windows\")]\r\n            {\r\n              match_config.window_class.as_ref().is_none_or(|match_type| {\r\n                match_type.is_match(&window_class)\r\n              })\r\n            }\r\n            #[cfg(not(target_os = \"windows\"))]\r\n            {\r\n              match_config.window_class.is_none()\r\n            }\r\n          };\r\n\r\n          let is_title_match = match_config\r\n            .window_title\r\n            .as_ref()\r\n            .is_none_or(|match_type| match_type.is_match(&window_title));\r\n\r\n          is_process_match && is_class_match && is_title_match\r\n        })\r\n      })\r\n      .cloned()\r\n      .collect::<Vec<_>>();\r\n\r\n    pending_window_rules\r\n  }\r\n\r\n  pub fn inactive_workspace_configs(\r\n    &self,\r\n    active_workspaces: &[Workspace],\r\n  ) -> Vec<&WorkspaceConfig> {\r\n    self\r\n      .value\r\n      .workspaces\r\n      .iter()\r\n      .filter(|config| {\r\n        !active_workspaces\r\n          .iter()\r\n          .any(|workspace| workspace.config().name == config.name)\r\n      })\r\n      .collect()\r\n  }\r\n\r\n  pub fn workspace_config_for_monitor(\r\n    &self,\r\n    monitor: &Monitor,\r\n    active_workspaces: &[Workspace],\r\n  ) -> Option<&WorkspaceConfig> {\r\n    let inactive_configs =\r\n      self.inactive_workspace_configs(active_workspaces);\r\n\r\n    inactive_configs.into_iter().find(|&config| {\r\n      config\r\n        .bind_to_monitor\r\n        .as_ref()\r\n        .is_some_and(|monitor_index| {\r\n          monitor.index() == *monitor_index as usize\r\n        })\r\n    })\r\n  }\r\n\r\n  /// Gets the first inactive workspace config, prioritizing configs that\r\n  /// don't have a monitor binding.\r\n  pub fn next_inactive_workspace_config(\r\n    &self,\r\n    active_workspaces: &[Workspace],\r\n  ) -> Option<&WorkspaceConfig> {\r\n    let inactive_configs =\r\n      self.inactive_workspace_configs(active_workspaces);\r\n\r\n    inactive_configs\r\n      .iter()\r\n      .find(|config| config.bind_to_monitor.is_none())\r\n      .or(inactive_configs.first())\r\n      .copied()\r\n  }\r\n\r\n  pub fn workspace_config_index(\r\n    &self,\r\n    workspace_name: &str,\r\n  ) -> Option<usize> {\r\n    self\r\n      .value\r\n      .workspaces\r\n      .iter()\r\n      .position(|config| config.name == workspace_name)\r\n  }\r\n\r\n  pub fn sort_workspaces(&self, workspaces: &mut [Workspace]) {\r\n    workspaces.sort_by_key(|workspace| {\r\n      self.workspace_config_index(&workspace.config().name)\r\n    });\r\n  }\r\n\r\n  /// Keybinding configs that should be active for the current binding mode\r\n  /// and pause state.\r\n  ///\r\n  /// When paused, only the configs with `InvokeCommand::WmTogglePause` are\r\n  /// returned so that unpausing remains possible.\r\n  pub fn active_keybinding_configs(\r\n    &self,\r\n    binding_modes: &[wm_common::BindingModeConfig],\r\n    is_paused: bool,\r\n  ) -> impl Iterator<Item = KeybindingConfig> {\r\n    let source_configs = if let Some(first_mode) = binding_modes.first() {\r\n      &first_mode.keybindings\r\n    } else {\r\n      &self.value.keybindings\r\n    }\r\n    .clone();\r\n\r\n    source_configs.into_iter().filter(move |kb| {\r\n      if is_paused {\r\n        kb.commands\r\n          .contains(&wm_common::InvokeCommand::WmTogglePause)\r\n      } else {\r\n        true\r\n      }\r\n    })\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/wm.rs",
    "content": "use anyhow::{bail, Context};\r\nuse tokio::sync::mpsc::{self};\r\nuse tracing::warn;\r\nuse uuid::Uuid;\r\n#[cfg(target_os = \"windows\")]\r\nuse wm_common::TitleBarVisibility;\r\nuse wm_common::{\r\n  FloatingStateConfig, FullscreenStateConfig, InvokeCommand, WindowState,\r\n  WmEvent,\r\n};\r\n#[cfg(target_os = \"windows\")]\r\nuse wm_platform::NativeWindowWindowsExt;\r\nuse wm_platform::{\r\n  Dispatcher, LengthValue, PlatformEvent, RectDelta, WindowEvent,\r\n};\r\n\r\nuse crate::{\r\n  commands::{\r\n    container::{\r\n      focus_container_by_id, focus_in_direction, set_tiling_direction,\r\n      toggle_tiling_direction,\r\n    },\r\n    general::{\r\n      cycle_focus, disable_binding_mode, enable_binding_mode,\r\n      platform_sync, reload_config, shell_exec, toggle_pause,\r\n    },\r\n    monitor::focus_monitor,\r\n    window::{\r\n      ignore_window, move_window_in_direction, move_window_to_workspace,\r\n      resize_window, set_window_position, set_window_size,\r\n      update_window_state, WindowPositionTarget,\r\n    },\r\n    workspace::{\r\n      focus_workspace, move_workspace_in_direction,\r\n      update_workspace_config,\r\n    },\r\n  },\r\n  events::{\r\n    handle_display_settings_changed, handle_mouse_move,\r\n    handle_window_destroyed, handle_window_focused, handle_window_hidden,\r\n    handle_window_minimize_ended, handle_window_minimized,\r\n    handle_window_moved_or_resized, handle_window_shown,\r\n    handle_window_title_changed,\r\n  },\r\n  ipc_server::IpcServer,\r\n  models::{Container, WorkspaceTarget},\r\n  traits::{CommonGetters, WindowGetters},\r\n  user_config::UserConfig,\r\n  wm_state::WmState,\r\n};\r\n\r\npub struct WindowManager {\r\n  pub event_rx: mpsc::UnboundedReceiver<WmEvent>,\r\n  pub exit_rx: mpsc::UnboundedReceiver<()>,\r\n  pub state: WmState,\r\n}\r\n\r\nimpl WindowManager {\r\n  pub fn new(\r\n    config: &mut UserConfig,\r\n    dispatcher: Dispatcher,\r\n  ) -> anyhow::Result<Self> {\r\n    let (event_tx, event_rx) = mpsc::unbounded_channel();\r\n    let (exit_tx, exit_rx) = mpsc::unbounded_channel();\r\n\r\n    let mut state = WmState::new(dispatcher, event_tx, exit_tx);\r\n    state.populate(config)?;\r\n\r\n    Ok(Self {\r\n      event_rx,\r\n      exit_rx,\r\n      state,\r\n    })\r\n  }\r\n\r\n  pub fn process_event(\r\n    &mut self,\r\n    event: PlatformEvent,\r\n    config: &mut UserConfig,\r\n  ) -> anyhow::Result<()> {\r\n    let state = &mut self.state;\r\n\r\n    match event {\r\n      PlatformEvent::DisplaySettingsChanged => {\r\n        handle_display_settings_changed(state, config)\r\n      }\r\n      PlatformEvent::Keybinding(keybinding_event) => {\r\n        // Find the keybinding config that matches this keybinding.\r\n        let commands = config\r\n          .active_keybinding_configs(\r\n            &self.state.binding_modes,\r\n            self.state.is_paused,\r\n          )\r\n          .find(|kb_config| {\r\n            kb_config.bindings.contains(&keybinding_event.0)\r\n          })\r\n          .map(|kb_config| kb_config.commands.clone());\r\n\r\n        if let Some(commands) = commands {\r\n          self.process_commands(&commands, None, config)?;\r\n        }\r\n\r\n        // Return early since we don't want to redraw twice.\r\n        return Ok(());\r\n      }\r\n      PlatformEvent::Mouse(event) => {\r\n        handle_mouse_move(&event, state, config)\r\n      }\r\n      PlatformEvent::Window(window_event) => match window_event {\r\n        WindowEvent::Focused { window, .. } => {\r\n          handle_window_focused(&window, state, config)\r\n        }\r\n        WindowEvent::Shown { window, .. } => {\r\n          handle_window_shown(window, state, config)\r\n        }\r\n        WindowEvent::Hidden { window, .. } => {\r\n          handle_window_hidden(&window, state, config)\r\n        }\r\n        WindowEvent::MovedOrResized {\r\n          window,\r\n          is_interactive_start,\r\n          is_interactive_end,\r\n          ..\r\n        } => handle_window_moved_or_resized(\r\n          &window,\r\n          is_interactive_start,\r\n          is_interactive_end,\r\n          state,\r\n          config,\r\n        ),\r\n        WindowEvent::Minimized { window, .. } => {\r\n          handle_window_minimized(&window, state, config)\r\n        }\r\n        WindowEvent::MinimizeEnded { window, .. } => {\r\n          handle_window_minimize_ended(&window, state, config)\r\n        }\r\n        WindowEvent::TitleChanged { window, .. } => {\r\n          handle_window_title_changed(&window, state, config)\r\n        }\r\n        WindowEvent::Destroyed { window_id, .. } => {\r\n          handle_window_destroyed(window_id, state)\r\n        }\r\n      },\r\n    }?;\r\n\r\n    if !state.is_paused && state.pending_sync.has_changes() {\r\n      platform_sync(state, config)?;\r\n    }\r\n\r\n    Ok(())\r\n  }\r\n\r\n  pub fn process_commands(\r\n    &mut self,\r\n    commands: &Vec<InvokeCommand>,\r\n    subject_container_id: Option<Uuid>,\r\n    config: &mut UserConfig,\r\n  ) -> anyhow::Result<Uuid> {\r\n    let state = &mut self.state;\r\n\r\n    // Get the container to run WM commands with.\r\n    let subject_container = match subject_container_id {\r\n      Some(id) => state.container_by_id(id).with_context(|| {\r\n        format!(\"No container found with the given ID '{id}'.\")\r\n      })?,\r\n      None => state\r\n        .focused_container()\r\n        .context(\"No subject container for command.\")?,\r\n    };\r\n\r\n    let new_subject_container_id = WindowManager::run_commands(\r\n      commands,\r\n      subject_container,\r\n      state,\r\n      config,\r\n    )?;\r\n\r\n    if state.pending_sync.has_changes() {\r\n      platform_sync(state, config)?;\r\n    }\r\n\r\n    Ok(new_subject_container_id)\r\n  }\r\n\r\n  pub fn run_commands(\r\n    commands: &Vec<InvokeCommand>,\r\n    subject_container: Container,\r\n    state: &mut WmState,\r\n    config: &mut UserConfig,\r\n  ) -> anyhow::Result<Uuid> {\r\n    let mut current_subject_container = subject_container;\r\n\r\n    for command in commands {\r\n      WindowManager::run_command(\r\n        command,\r\n        current_subject_container.clone(),\r\n        state,\r\n        config,\r\n      )?;\r\n\r\n      // Update the subject container in case the container type changes.\r\n      // For example, when going from a tiling to a floating window.\r\n      current_subject_container =\r\n        if current_subject_container.is_detached() {\r\n          match state.container_by_id(current_subject_container.id()) {\r\n            Some(container) => container,\r\n            None => break,\r\n          }\r\n        } else {\r\n          current_subject_container\r\n        }\r\n    }\r\n\r\n    Ok(current_subject_container.id())\r\n  }\r\n\r\n  #[allow(clippy::too_many_lines)]\r\n  pub fn run_command(\r\n    command: &InvokeCommand,\r\n    subject_container: Container,\r\n    state: &mut WmState,\r\n    config: &mut UserConfig,\r\n  ) -> anyhow::Result<()> {\r\n    // No-op if WM is currently paused.\r\n    if state.is_paused && *command != InvokeCommand::WmTogglePause {\r\n      return Ok(());\r\n    }\r\n\r\n    if subject_container.is_detached() {\r\n      bail!(\"Cannot run command because subject container is detached.\");\r\n    }\r\n\r\n    match &command {\r\n      InvokeCommand::AdjustBorders(args) => {\r\n        match subject_container.as_window_container() {\r\n          Ok(window) => {\r\n            let args = args.clone();\r\n            let border_delta = RectDelta::new(\r\n              args.left.unwrap_or(LengthValue::from_px(0)),\r\n              args.top.unwrap_or(LengthValue::from_px(0)),\r\n              args.right.unwrap_or(LengthValue::from_px(0)),\r\n              args.bottom.unwrap_or(LengthValue::from_px(0)),\r\n            );\r\n\r\n            window.set_border_delta(border_delta);\r\n            state.pending_sync.queue_container_to_redraw(window);\r\n\r\n            Ok(())\r\n          }\r\n          _ => Ok(()),\r\n        }\r\n      }\r\n      InvokeCommand::Close => {\r\n        match subject_container.as_window_container() {\r\n          Ok(window) => {\r\n            // Window handle might no longer be valid here.\r\n            if let Err(err) = window.native().close() {\r\n              warn!(\"Failed to close window: {:?}\", err);\r\n            }\r\n\r\n            Ok(())\r\n          }\r\n          _ => Ok(()),\r\n        }\r\n      }\r\n      InvokeCommand::Focus(args) => {\r\n        if let Some(direction) = &args.direction {\r\n          focus_in_direction(&subject_container, direction, state)?;\r\n        }\r\n\r\n        if let Some(direction) = &args.workspace_in_direction {\r\n          focus_workspace(\r\n            WorkspaceTarget::Direction(direction.clone()),\r\n            state,\r\n            config,\r\n          )?;\r\n        }\r\n\r\n        if let Some(container_id) = &args.container_id {\r\n          focus_container_by_id(container_id, state)?;\r\n        }\r\n\r\n        if let Some(name) = &args.workspace {\r\n          focus_workspace(\r\n            WorkspaceTarget::Name(name.clone()),\r\n            state,\r\n            config,\r\n          )?;\r\n        }\r\n\r\n        if let Some(monitor_index) = &args.monitor {\r\n          focus_monitor(*monitor_index, state, config)?;\r\n        }\r\n\r\n        if args.next_active_workspace {\r\n          focus_workspace(WorkspaceTarget::NextActive, state, config)?;\r\n        }\r\n\r\n        if args.prev_active_workspace {\r\n          focus_workspace(WorkspaceTarget::PreviousActive, state, config)?;\r\n        }\r\n\r\n        if args.next_workspace {\r\n          focus_workspace(WorkspaceTarget::Next, state, config)?;\r\n        }\r\n\r\n        if args.prev_workspace {\r\n          focus_workspace(WorkspaceTarget::Previous, state, config)?;\r\n        }\r\n\r\n        if args.recent_workspace {\r\n          focus_workspace(WorkspaceTarget::Recent, state, config)?;\r\n        }\r\n\r\n        if args.next_active_workspace_on_monitor {\r\n          focus_workspace(\r\n            WorkspaceTarget::NextActiveInMonitor,\r\n            state,\r\n            config,\r\n          )?;\r\n        }\r\n\r\n        if args.prev_active_workspace_on_monitor {\r\n          focus_workspace(\r\n            WorkspaceTarget::PreviousActiveInMonitor,\r\n            state,\r\n            config,\r\n          )?;\r\n        }\r\n\r\n        Ok(())\r\n      }\r\n      InvokeCommand::Ignore => {\r\n        match subject_container.as_window_container() {\r\n          Ok(window) => ignore_window(window, state),\r\n          _ => Ok(()),\r\n        }\r\n      }\r\n      InvokeCommand::Move(args) => {\r\n        match subject_container.as_window_container() {\r\n          Ok(window) => {\r\n            if let Some(direction) = &args.direction {\r\n              move_window_in_direction(\r\n                window.clone(),\r\n                direction,\r\n                state,\r\n                config,\r\n              )?;\r\n            }\r\n\r\n            if let Some(direction) = &args.workspace_in_direction {\r\n              move_window_to_workspace(\r\n                window.clone(),\r\n                WorkspaceTarget::Direction(direction.clone()),\r\n                state,\r\n                config,\r\n              )?;\r\n            }\r\n\r\n            if let Some(name) = &args.workspace {\r\n              move_window_to_workspace(\r\n                window.clone(),\r\n                WorkspaceTarget::Name(name.clone()),\r\n                state,\r\n                config,\r\n              )?;\r\n            }\r\n\r\n            if args.next_active_workspace {\r\n              move_window_to_workspace(\r\n                window.clone(),\r\n                WorkspaceTarget::NextActive,\r\n                state,\r\n                config,\r\n              )?;\r\n            }\r\n\r\n            if args.prev_active_workspace {\r\n              move_window_to_workspace(\r\n                window.clone(),\r\n                WorkspaceTarget::PreviousActive,\r\n                state,\r\n                config,\r\n              )?;\r\n            }\r\n\r\n            if args.next_workspace {\r\n              move_window_to_workspace(\r\n                window.clone(),\r\n                WorkspaceTarget::Next,\r\n                state,\r\n                config,\r\n              )?;\r\n            }\r\n\r\n            if args.prev_workspace {\r\n              move_window_to_workspace(\r\n                window.clone(),\r\n                WorkspaceTarget::Previous,\r\n                state,\r\n                config,\r\n              )?;\r\n            }\r\n\r\n            if args.recent_workspace {\r\n              move_window_to_workspace(\r\n                window.clone(),\r\n                WorkspaceTarget::Recent,\r\n                state,\r\n                config,\r\n              )?;\r\n            }\r\n\r\n            if args.next_active_workspace_on_monitor {\r\n              move_window_to_workspace(\r\n                window.clone(),\r\n                WorkspaceTarget::NextActiveInMonitor,\r\n                state,\r\n                config,\r\n              )?;\r\n            }\r\n\r\n            if args.prev_active_workspace_on_monitor {\r\n              move_window_to_workspace(\r\n                window,\r\n                WorkspaceTarget::PreviousActiveInMonitor,\r\n                state,\r\n                config,\r\n              )?;\r\n            }\r\n            Ok(())\r\n          }\r\n\r\n          _ => Ok(()),\r\n        }\r\n      }\r\n      InvokeCommand::MoveWorkspace { direction } => {\r\n        let workspace =\r\n          subject_container.workspace().context(\"No workspace.\")?;\r\n\r\n        move_workspace_in_direction(&workspace, direction, state, config)\r\n      }\r\n      InvokeCommand::Position(args) => {\r\n        match subject_container.as_window_container() {\r\n          Ok(window) => {\r\n            if args.centered {\r\n              set_window_position(\r\n                window,\r\n                &WindowPositionTarget::Centered,\r\n                state,\r\n              )\r\n            } else {\r\n              set_window_position(\r\n                window,\r\n                &WindowPositionTarget::Coordinates(args.x_pos, args.y_pos),\r\n                state,\r\n              )\r\n            }\r\n          }\r\n          _ => Ok(()),\r\n        }\r\n      }\r\n      InvokeCommand::UpdateWorkspaceConfig {\r\n        workspace,\r\n        new_config,\r\n      } => {\r\n        let workspace = if let Some(workspace_name) = workspace {\r\n          state\r\n            .workspace_by_name(workspace_name)\r\n            .context(\"Workspace doesn't exist.\")?\r\n        } else {\r\n          subject_container.workspace().context(\"No workspace.\")?\r\n        };\r\n        update_workspace_config(&workspace, state, config, new_config)\r\n      }\r\n      InvokeCommand::Resize(args) => {\r\n        match subject_container.as_window_container() {\r\n          Ok(window) => resize_window(\r\n            &window,\r\n            args.width.clone(),\r\n            args.height.clone(),\r\n            state,\r\n          ),\r\n          _ => Ok(()),\r\n        }\r\n      }\r\n      InvokeCommand::SetFloating {\r\n        centered,\r\n        shown_on_top,\r\n        x_pos,\r\n        y_pos,\r\n        width,\r\n        height,\r\n      } => match subject_container.as_window_container() {\r\n        Ok(window) => {\r\n          let floating_defaults =\r\n            &config.value.window_behavior.state_defaults.floating;\r\n          let centered = centered.unwrap_or(floating_defaults.centered);\r\n\r\n          let window = update_window_state(\r\n            window.clone(),\r\n            WindowState::Floating(FloatingStateConfig {\r\n              centered,\r\n              shown_on_top: shown_on_top\r\n                .unwrap_or(floating_defaults.shown_on_top),\r\n            }),\r\n            state,\r\n            config,\r\n          )?;\r\n\r\n          // Allow size and position to be set if window has not previously\r\n          // been manually placed.\r\n          if !window.has_custom_floating_placement() {\r\n            if width.is_some() || height.is_some() {\r\n              set_window_size(\r\n                window.clone(),\r\n                width.clone(),\r\n                height.clone(),\r\n                state,\r\n              )?;\r\n            }\r\n\r\n            if centered {\r\n              set_window_position(\r\n                window,\r\n                &WindowPositionTarget::Centered,\r\n                state,\r\n              )?;\r\n            } else if x_pos.is_some() || y_pos.is_some() {\r\n              set_window_position(\r\n                window,\r\n                &WindowPositionTarget::Coordinates(*x_pos, *y_pos),\r\n                state,\r\n              )?;\r\n            }\r\n          }\r\n\r\n          Ok(())\r\n        }\r\n        _ => Ok(()),\r\n      },\r\n      InvokeCommand::SetFullscreen {\r\n        maximized,\r\n        shown_on_top,\r\n      } => match subject_container.as_window_container() {\r\n        Ok(window) => {\r\n          let fullscreen_defaults =\r\n            &config.value.window_behavior.state_defaults.fullscreen;\r\n\r\n          update_window_state(\r\n            window.clone(),\r\n            WindowState::Fullscreen(FullscreenStateConfig {\r\n              maximized: maximized\r\n                .unwrap_or(fullscreen_defaults.maximized),\r\n              shown_on_top: shown_on_top\r\n                .unwrap_or(fullscreen_defaults.shown_on_top),\r\n            }),\r\n            state,\r\n            config,\r\n          )?;\r\n\r\n          Ok(())\r\n        }\r\n        _ => Ok(()),\r\n      },\r\n      InvokeCommand::SetMinimized => {\r\n        match subject_container.as_window_container() {\r\n          Ok(window) => {\r\n            update_window_state(\r\n              window.clone(),\r\n              WindowState::Minimized,\r\n              state,\r\n              config,\r\n            )?;\r\n\r\n            Ok(())\r\n          }\r\n          _ => Ok(()),\r\n        }\r\n      }\r\n      InvokeCommand::SetTiling => {\r\n        match subject_container.as_window_container() {\r\n          Ok(window) => {\r\n            update_window_state(\r\n              window,\r\n              WindowState::Tiling,\r\n              state,\r\n              config,\r\n            )?;\r\n\r\n            Ok(())\r\n          }\r\n          _ => Ok(()),\r\n        }\r\n      }\r\n      InvokeCommand::SetTitleBarVisibility {\r\n        // LINT: `visibility` is only used on Windows.\r\n        #[cfg_attr(not(target_os = \"windows\"), allow(unused_variables))]\r\n        visibility,\r\n      } => match subject_container.as_window_container() {\r\n        #[cfg(target_os = \"windows\")]\r\n        Ok(window) => {\r\n          _ = window.native().set_title_bar_visibility(\r\n            *visibility == TitleBarVisibility::Shown,\r\n          );\r\n          Ok(())\r\n        }\r\n        _ => Ok(()),\r\n      },\r\n      // LINT: `args` is only used on Windows.\r\n      #[cfg_attr(not(target_os = \"windows\"), allow(unused_variables))]\r\n      InvokeCommand::SetTransparency(args) => {\r\n        match subject_container.as_window_container() {\r\n          #[cfg(target_os = \"windows\")]\r\n          Ok(window) => {\r\n            if let Some(opacity) = &args.opacity {\r\n              _ = window.native().set_transparency(opacity);\r\n            }\r\n\r\n            if let Some(opacity_delta) = &args.opacity_delta {\r\n              _ = window.native().adjust_transparency(opacity_delta);\r\n            }\r\n\r\n            Ok(())\r\n          }\r\n          _ => Ok(()),\r\n        }\r\n      }\r\n      InvokeCommand::ShellExec {\r\n        hide_window,\r\n        command,\r\n      } => shell_exec(&command.join(\" \"), *hide_window, state),\r\n      InvokeCommand::Size(args) => {\r\n        match subject_container.as_window_container() {\r\n          Ok(window) => set_window_size(\r\n            window,\r\n            args.width.clone(),\r\n            args.height.clone(),\r\n            state,\r\n          ),\r\n          _ => Ok(()),\r\n        }\r\n      }\r\n      InvokeCommand::ToggleFloating {\r\n        centered,\r\n        shown_on_top,\r\n      } => match subject_container.as_window_container() {\r\n        Ok(window) => {\r\n          let floating_defaults =\r\n            &config.value.window_behavior.state_defaults.floating;\r\n\r\n          let centered = centered.unwrap_or(floating_defaults.centered);\r\n          let target_state = WindowState::Floating(FloatingStateConfig {\r\n            centered,\r\n            shown_on_top: shown_on_top\r\n              .unwrap_or(floating_defaults.shown_on_top),\r\n          });\r\n\r\n          let window = update_window_state(\r\n            window.clone(),\r\n            window.toggled_state(target_state, config),\r\n            state,\r\n            config,\r\n          )?;\r\n\r\n          if !window.has_custom_floating_placement() && centered {\r\n            set_window_position(\r\n              window,\r\n              &WindowPositionTarget::Centered,\r\n              state,\r\n            )?;\r\n          }\r\n\r\n          Ok(())\r\n        }\r\n        _ => Ok(()),\r\n      },\r\n      InvokeCommand::ToggleFullscreen {\r\n        maximized,\r\n        shown_on_top,\r\n      } => match subject_container.as_window_container() {\r\n        Ok(window) => {\r\n          let fullscreen_defaults =\r\n            &config.value.window_behavior.state_defaults.fullscreen;\r\n\r\n          let target_state =\r\n            WindowState::Fullscreen(FullscreenStateConfig {\r\n              maximized: maximized\r\n                .unwrap_or(fullscreen_defaults.maximized),\r\n              shown_on_top: shown_on_top\r\n                .unwrap_or(fullscreen_defaults.shown_on_top),\r\n            });\r\n\r\n          update_window_state(\r\n            window.clone(),\r\n            window.toggled_state(target_state, config),\r\n            state,\r\n            config,\r\n          )?;\r\n\r\n          Ok(())\r\n        }\r\n        _ => Ok(()),\r\n      },\r\n      InvokeCommand::ToggleMinimized => {\r\n        match subject_container.as_window_container() {\r\n          Ok(window) => {\r\n            update_window_state(\r\n              window.clone(),\r\n              window.toggled_state(WindowState::Minimized, config),\r\n              state,\r\n              config,\r\n            )?;\r\n\r\n            Ok(())\r\n          }\r\n          _ => Ok(()),\r\n        }\r\n      }\r\n      InvokeCommand::ToggleTiling => {\r\n        match subject_container.as_window_container() {\r\n          Ok(window) => {\r\n            update_window_state(\r\n              window.clone(),\r\n              window.toggled_state(WindowState::Tiling, config),\r\n              state,\r\n              config,\r\n            )?;\r\n\r\n            Ok(())\r\n          }\r\n          _ => Ok(()),\r\n        }\r\n      }\r\n      InvokeCommand::ToggleTilingDirection => {\r\n        toggle_tiling_direction(subject_container, state, config)\r\n      }\r\n      InvokeCommand::SetTilingDirection { tiling_direction } => {\r\n        set_tiling_direction(\r\n          subject_container,\r\n          state,\r\n          config,\r\n          tiling_direction,\r\n        )\r\n      }\r\n      InvokeCommand::WmCycleFocus {\r\n        omit_floating,\r\n        omit_fullscreen,\r\n        omit_minimized,\r\n        omit_tiling,\r\n      } => cycle_focus(\r\n        *omit_floating,\r\n        *omit_fullscreen,\r\n        *omit_minimized,\r\n        *omit_tiling,\r\n        state,\r\n        config,\r\n      ),\r\n      InvokeCommand::WmDisableBindingMode { name } => {\r\n        disable_binding_mode(name, state);\r\n        Ok(())\r\n      }\r\n      InvokeCommand::WmEnableBindingMode { name } => {\r\n        enable_binding_mode(name, state, config)\r\n      }\r\n      InvokeCommand::WmExit => state.emit_exit(),\r\n      InvokeCommand::WmRedraw => {\r\n        state\r\n          .pending_sync\r\n          .queue_container_to_redraw(state.root_container.clone());\r\n\r\n        Ok(())\r\n      }\r\n      InvokeCommand::WmReloadConfig => reload_config(state, config),\r\n      InvokeCommand::WmTogglePause => {\r\n        toggle_pause(state);\r\n        Ok(())\r\n      }\r\n    }\r\n  }\r\n\r\n  /// Runs cleanup tasks when the WM is exiting.\r\n  pub(crate) fn cleanup(\r\n    &mut self,\r\n    config: &mut UserConfig,\r\n    ipc_server: &mut IpcServer,\r\n  ) {\r\n    self.state.emit_event(WmEvent::ApplicationExiting);\r\n\r\n    // Ensure that the WM is unpaused, otherwise, shutdown commands won't\r\n    // get executed.\r\n    self.state.is_paused = false;\r\n\r\n    // Run user's shutdown commands.\r\n    if let Err(err) = self.process_commands(\r\n      &config.value.general.shutdown_commands.clone(),\r\n      None,\r\n      config,\r\n    ) {\r\n      tracing::warn!(\"Failed to run shutdown commands: {:?}\", err);\r\n    }\r\n\r\n    // Emit remaining WM events before exiting.\r\n    while let Ok(wm_event) = self.event_rx.try_recv() {\r\n      tracing::info!(\r\n        \"Emitting WM event before shutting down: {:?}\",\r\n        wm_event\r\n      );\r\n\r\n      if let Err(err) = ipc_server.process_event(wm_event) {\r\n        tracing::warn!(\"{:?}\", err);\r\n      }\r\n    }\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm/src/wm_state.rs",
    "content": "use std::time::Instant;\r\n\r\nuse anyhow::Context;\r\nuse tokio::sync::mpsc::{self};\r\nuse tracing::warn;\r\nuse uuid::Uuid;\r\nuse wm_common::{BindingModeConfig, HideCorner, WindowState, WmEvent};\r\nuse wm_platform::{\r\n  Direction, Dispatcher, Display, NativeWindow, Point, Rect,\r\n};\r\n#[cfg(target_os = \"windows\")]\r\nuse wm_platform::{NativeWindowWindowsExt, OpacityValue};\r\n\r\nuse crate::{\r\n  commands::{\r\n    container::set_focused_descendant,\r\n    general::platform_sync,\r\n    monitor::{add_monitor, move_bounded_workspaces_to_new_monitor},\r\n    window::{manage_window, unmanage_window},\r\n  },\r\n  models::{\r\n    Container, Monitor, NativeMonitorProperties, RootContainer,\r\n    WindowContainer, Workspace, WorkspaceTarget,\r\n  },\r\n  pending_sync::PendingSync,\r\n  traits::{CommonGetters, PositionGetters, WindowGetters},\r\n  user_config::UserConfig,\r\n};\r\n\r\npub struct WmState {\r\n  /// Root node of the container tree. Monitors are the children of the\r\n  /// root node, followed by workspaces, then split containers/windows.\r\n  pub root_container: RootContainer,\r\n\r\n  pub dispatcher: Dispatcher,\r\n\r\n  pub pending_sync: PendingSync,\r\n\r\n  /// Name of the most recently focused workspace.\r\n  ///\r\n  /// Used for the `general.toggle_workspace_on_refocus` option on\r\n  /// workspace focus.\r\n  pub recent_workspace_name: Option<String>,\r\n\r\n  /// The previously focused window that had focus effects applied.\r\n  ///\r\n  /// Used to efficiently update window effects by only removing focus\r\n  /// effects from the previous window rather than all windows when focus\r\n  /// changes.\r\n  pub prev_effects_window: Option<WindowContainer>,\r\n\r\n  /// Time since a previously focused window was unmanaged or minimized.\r\n  ///\r\n  /// Used to decide whether to override incoming focus events.\r\n  pub unmanaged_or_minimized_timestamp: Option<Instant>,\r\n\r\n  /// Configs of currently enabled binding modes.\r\n  pub binding_modes: Vec<BindingModeConfig>,\r\n\r\n  /// Windows that the WM should ignore. Windows can be added via the\r\n  /// `ignore` command.\r\n  pub ignored_windows: Vec<NativeWindow>,\r\n\r\n  /// Whether the WM is paused.\r\n  pub is_paused: bool,\r\n\r\n  /// Whether the OS focused window is the same as the WM focused window.\r\n  pub is_focus_synced: bool,\r\n\r\n  /// Whether the initial state has been populated.\r\n  has_initialized: bool,\r\n\r\n  /// Sender for emitting WM-related events.\r\n  event_tx: mpsc::UnboundedSender<WmEvent>,\r\n\r\n  /// Sender for gracefully shutting down the WM.\r\n  exit_tx: mpsc::UnboundedSender<()>,\r\n}\r\n\r\nimpl WmState {\r\n  pub fn new(\r\n    dispatcher: Dispatcher,\r\n    event_tx: mpsc::UnboundedSender<WmEvent>,\r\n    exit_tx: mpsc::UnboundedSender<()>,\r\n  ) -> Self {\r\n    Self {\r\n      root_container: RootContainer::new(),\r\n      dispatcher,\r\n      pending_sync: PendingSync::default(),\r\n      prev_effects_window: None,\r\n      recent_workspace_name: None,\r\n      unmanaged_or_minimized_timestamp: None,\r\n      binding_modes: Vec::new(),\r\n      ignored_windows: Vec::new(),\r\n      is_paused: false,\r\n      is_focus_synced: false,\r\n      has_initialized: false,\r\n      event_tx,\r\n      exit_tx,\r\n    }\r\n  }\r\n\r\n  /// Populates the initial WM state by creating containers for all\r\n  /// existing windows and monitors.\r\n  pub fn populate(\r\n    &mut self,\r\n    config: &mut UserConfig,\r\n  ) -> anyhow::Result<()> {\r\n    // Get the originally focused window when the WM was started.\r\n    let focused_window = self.dispatcher.focused_window().ok();\r\n\r\n    // Create a monitor, and consequently a workspace, for each detected\r\n    // native monitor.\r\n    for native_display in self.dispatcher.sorted_displays()? {\r\n      if let Ok(native_properties) =\r\n        NativeMonitorProperties::try_from(&native_display)\r\n      {\r\n        let monitor =\r\n          add_monitor(native_display, native_properties, self)?;\r\n        move_bounded_workspaces_to_new_monitor(&monitor, self, config)?;\r\n      }\r\n    }\r\n\r\n    // Manage windows in reverse z-order (bottom to top). This helps to\r\n    // preserve the original stacking order.\r\n    for native_window in\r\n      self.dispatcher.visible_windows()?.into_iter().rev()\r\n    {\r\n      let nearest_workspace = self\r\n        .nearest_monitor(&native_window)\r\n        .and_then(|m| m.displayed_workspace());\r\n\r\n      if let Some(workspace) = nearest_workspace {\r\n        manage_window(\r\n          native_window,\r\n          Some(workspace.into()),\r\n          self,\r\n          config,\r\n        )?;\r\n      }\r\n    }\r\n\r\n    let container_to_focus = focused_window\r\n      .and_then(|focused_window| {\r\n        self.window_from_native(&focused_window).map(Into::into)\r\n      })\r\n      .or_else(|| self.windows().pop().map(Into::into))\r\n      .or_else(|| self.workspaces().pop().map(Into::into))\r\n      .context(\"Failed to get container to focus.\")?;\r\n\r\n    set_focused_descendant(&container_to_focus, None);\r\n    self.is_focus_synced = true;\r\n\r\n    self\r\n      .pending_sync\r\n      .queue_focus_change()\r\n      .queue_all_effects_update();\r\n\r\n    for workspace in self.workspaces() {\r\n      self.pending_sync.queue_workspace_to_reorder(workspace);\r\n    }\r\n\r\n    platform_sync(self, config)?;\r\n    self.has_initialized = true;\r\n\r\n    Ok(())\r\n  }\r\n\r\n  pub fn monitors(&self) -> Vec<Monitor> {\r\n    self.root_container.monitors()\r\n  }\r\n\r\n  pub fn workspaces(&self) -> Vec<Workspace> {\r\n    self\r\n      .monitors()\r\n      .iter()\r\n      .flat_map(Monitor::workspaces)\r\n      .collect()\r\n  }\r\n\r\n  /// Gets workspaces sorted by their position in the user config.\r\n  pub fn sorted_workspaces(&self, config: &UserConfig) -> Vec<Workspace> {\r\n    let mut workspaces = self.workspaces();\r\n    config.sort_workspaces(&mut workspaces);\r\n    workspaces\r\n  }\r\n\r\n  pub fn windows(&self) -> Vec<WindowContainer> {\r\n    self\r\n      .root_container\r\n      .descendants()\r\n      .filter_map(|container| container.try_into().ok())\r\n      .collect()\r\n  }\r\n\r\n  /// Gets the monitor that encompasses the largest portion of a given\r\n  /// window.\r\n  ///\r\n  /// Defaults to the first monitor if the nearest monitor is invalid.\r\n  pub fn nearest_monitor(\r\n    &self,\r\n    native_window: &NativeWindow,\r\n  ) -> Option<Monitor> {\r\n    self\r\n      .monitor_from_native(\r\n        &self.dispatcher.nearest_display(native_window).ok()?,\r\n      )\r\n      .or(self.monitors().first().cloned())\r\n  }\r\n\r\n  /// Gets monitor that corresponds to the given `Display`.\r\n  pub fn monitor_from_native(\r\n    &self,\r\n    native_display: &Display,\r\n  ) -> Option<Monitor> {\r\n    self\r\n      .monitors()\r\n      .into_iter()\r\n      .find(|monitor| monitor.native() == *native_display)\r\n  }\r\n\r\n  /// Gets the closest monitor in a given direction.\r\n  ///\r\n  /// Uses i3wm's algorithm for finding best guess.\r\n  pub fn monitor_in_direction(\r\n    &self,\r\n    origin_monitor: &Monitor,\r\n    direction: &Direction,\r\n  ) -> anyhow::Result<Option<Monitor>> {\r\n    let origin_rect = origin_monitor.native_properties().bounds;\r\n\r\n    // Create a tuple of monitors and their rect.\r\n    let monitors_with_rect = self\r\n      .monitors()\r\n      .into_iter()\r\n      .map(|monitor| {\r\n        let rect = monitor.native_properties().bounds;\r\n        anyhow::Ok((monitor, rect))\r\n      })\r\n      .try_collect::<Vec<_>>()?;\r\n\r\n    let closest_monitor = monitors_with_rect\r\n      .into_iter()\r\n      .filter(|(_, rect)| match direction {\r\n        Direction::Right => {\r\n          rect.x() > origin_rect.x() && rect.y_overlap(&origin_rect) > 0\r\n        }\r\n        Direction::Left => {\r\n          rect.x() < origin_rect.x() && rect.y_overlap(&origin_rect) > 0\r\n        }\r\n        Direction::Down => {\r\n          rect.y() > origin_rect.y() && rect.x_overlap(&origin_rect) > 0\r\n        }\r\n        Direction::Up => {\r\n          rect.y() < origin_rect.y() && rect.x_overlap(&origin_rect) > 0\r\n        }\r\n      })\r\n      .min_by(|(_, rect_a), (_, rect_b)| match direction {\r\n        Direction::Right => rect_a.x().cmp(&rect_b.x()),\r\n        Direction::Left => rect_b.x().cmp(&rect_a.x()),\r\n        Direction::Down => rect_a.y().cmp(&rect_b.y()),\r\n        Direction::Up => rect_b.y().cmp(&rect_a.y()),\r\n      })\r\n      .map(|(monitor, _)| monitor);\r\n\r\n    Ok(closest_monitor)\r\n  }\r\n\r\n  /// Determines the preferred hide corner for each monitor. Used for\r\n  /// [`HideMethod::PlaceInCorner`].\r\n  ///\r\n  /// The corner is chosen by simulating a 400x400 window frame in the\r\n  /// bottom-left and bottom-right of the monitor's working area, then\r\n  /// picking the side that overlaps the least with other monitors'\r\n  /// working areas (ties favor bottom-right).\r\n  pub fn monitors_by_hide_corner(&self) -> Vec<(Monitor, HideCorner)> {\r\n    const TEST_FRAME_SIZE: i32 = 400;\r\n    const VISIBLE_SLIVER: i32 = 1;\r\n\r\n    let monitors = self.monitors();\r\n    let working_areas = monitors\r\n      .iter()\r\n      .map(|monitor| monitor.native_properties().working_area)\r\n      .collect::<Vec<_>>();\r\n\r\n    monitors\r\n      .into_iter()\r\n      .enumerate()\r\n      .map(|(idx, monitor)| {\r\n        let monitor_rect = &working_areas[idx];\r\n        let test_frame_y = monitor_rect.bottom - TEST_FRAME_SIZE;\r\n\r\n        let left_test_frame = Rect::from_xy(\r\n          monitor_rect.left - TEST_FRAME_SIZE + VISIBLE_SLIVER,\r\n          test_frame_y,\r\n          TEST_FRAME_SIZE,\r\n          TEST_FRAME_SIZE,\r\n        );\r\n\r\n        let right_test_frame = Rect::from_xy(\r\n          monitor_rect.right - VISIBLE_SLIVER,\r\n          test_frame_y,\r\n          TEST_FRAME_SIZE,\r\n          TEST_FRAME_SIZE,\r\n        );\r\n\r\n        let overlap_area = |test_frame: &Rect| -> i32 {\r\n          working_areas\r\n            .iter()\r\n            .enumerate()\r\n            .filter(|(i, _)| *i != idx)\r\n            .map(|(_, rect)| test_frame.intersection_area(rect))\r\n            .sum()\r\n        };\r\n\r\n        let left_overlap = overlap_area(&left_test_frame);\r\n        let right_overlap = overlap_area(&right_test_frame);\r\n\r\n        let corner = if left_overlap < right_overlap {\r\n          HideCorner::BottomLeft\r\n        } else {\r\n          HideCorner::BottomRight\r\n        };\r\n\r\n        (monitor, corner)\r\n      })\r\n      .collect()\r\n  }\r\n\r\n  /// Gets window that corresponds to the given `NativeWindow`.\r\n  pub fn window_from_native(\r\n    &self,\r\n    native_window: &NativeWindow,\r\n  ) -> Option<WindowContainer> {\r\n    self\r\n      .windows()\r\n      .into_iter()\r\n      .find(|window| &*window.native() == native_window)\r\n  }\r\n\r\n  pub fn workspace_by_name(\r\n    &self,\r\n    workspace_name: &str,\r\n  ) -> Option<Workspace> {\r\n    self\r\n      .workspaces()\r\n      .into_iter()\r\n      .find(|workspace| workspace.config().name == workspace_name)\r\n  }\r\n\r\n  /// Gets a workspace and its name by the given target.\r\n  ///\r\n  /// Returns a tuple of the workspace name and the `Workspace` instance\r\n  /// if active.\r\n  #[allow(clippy::too_many_lines)]\r\n  pub fn workspace_by_target(\r\n    &self,\r\n    origin_workspace: &Workspace,\r\n    target: WorkspaceTarget,\r\n    config: &UserConfig,\r\n  ) -> anyhow::Result<(Option<String>, Option<Workspace>)> {\r\n    let (name, workspace) = match target {\r\n      WorkspaceTarget::Name(name) => {\r\n        #[allow(clippy::match_bool)]\r\n        match origin_workspace.config().name == name {\r\n          false => (Some(name.clone()), self.workspace_by_name(&name)),\r\n          // Toggle the workspace if it's already focused.\r\n          true if config.value.general.toggle_workspace_on_refocus => (\r\n            self.recent_workspace_name.clone(),\r\n            self\r\n              .recent_workspace_name\r\n              .as_ref()\r\n              .and_then(|name| self.workspace_by_name(name)),\r\n          ),\r\n          true => (None, None),\r\n        }\r\n      }\r\n      WorkspaceTarget::Recent => (\r\n        self.recent_workspace_name.clone(),\r\n        self\r\n          .recent_workspace_name\r\n          .as_ref()\r\n          .and_then(|name| self.workspace_by_name(name)),\r\n      ),\r\n      WorkspaceTarget::NextActive => {\r\n        let active_workspaces = self.sorted_workspaces(config);\r\n        let origin_index = active_workspaces\r\n          .iter()\r\n          .position(|workspace| workspace.id() == origin_workspace.id())\r\n          .context(\"Failed to get index of given workspace.\")?;\r\n\r\n        let next_active_workspace = active_workspaces\r\n          .get(origin_index + 1)\r\n          .or_else(|| active_workspaces.first());\r\n\r\n        (\r\n          next_active_workspace.map(|workspace| workspace.config().name),\r\n          next_active_workspace.cloned(),\r\n        )\r\n      }\r\n      WorkspaceTarget::PreviousActive => {\r\n        let active_workspaces = self.sorted_workspaces(config);\r\n        let origin_index = active_workspaces\r\n          .iter()\r\n          .position(|workspace| workspace.id() == origin_workspace.id())\r\n          .context(\"Failed to get index of given workspace.\")?;\r\n\r\n        let prev_active_workspace = active_workspaces.get(\r\n          origin_index\r\n            .checked_sub(1)\r\n            .unwrap_or(active_workspaces.len() - 1),\r\n        );\r\n\r\n        (\r\n          prev_active_workspace.map(|workspace| workspace.config().name),\r\n          prev_active_workspace.cloned(),\r\n        )\r\n      }\r\n      WorkspaceTarget::NextActiveInMonitor => {\r\n        let monitor = origin_workspace\r\n          .monitor()\r\n          .context(\"No monitor in workspace\")?;\r\n\r\n        let mut workspace_in_monitor = monitor.workspaces();\r\n        config.sort_workspaces(&mut workspace_in_monitor);\r\n\r\n        let origin_index = workspace_in_monitor\r\n          .iter()\r\n          .position(|workspace| workspace.id() == origin_workspace.id())\r\n          .context(\"Failed to get index of give workspace\")?;\r\n\r\n        let next_active_workspace_in_monitor = workspace_in_monitor\r\n          .get(origin_index + 1)\r\n          .or_else(|| workspace_in_monitor.first());\r\n\r\n        (\r\n          next_active_workspace_in_monitor\r\n            .map(|workspace| workspace.config().name),\r\n          next_active_workspace_in_monitor.cloned(),\r\n        )\r\n      }\r\n      WorkspaceTarget::PreviousActiveInMonitor => {\r\n        let monitor = origin_workspace\r\n          .monitor()\r\n          .context(\"No monitor in workspace\")?;\r\n\r\n        let mut workspace_in_monitor = monitor.workspaces();\r\n        config.sort_workspaces(&mut workspace_in_monitor);\r\n\r\n        let origin_index = workspace_in_monitor\r\n          .iter()\r\n          .position(|workspace| workspace.id() == origin_workspace.id())\r\n          .context(\"Failed to get index of give workspace\")?;\r\n\r\n        let prev_active_workspace_in_monitor = workspace_in_monitor.get(\r\n          origin_index\r\n            .checked_sub(1)\r\n            .unwrap_or(workspace_in_monitor.len() - 1),\r\n        );\r\n\r\n        (\r\n          prev_active_workspace_in_monitor\r\n            .map(|workspace| workspace.config().name),\r\n          prev_active_workspace_in_monitor.cloned(),\r\n        )\r\n      }\r\n      WorkspaceTarget::Next => {\r\n        let workspaces = &config.value.workspaces;\r\n        let origin_name = origin_workspace.config().name.clone();\r\n        let origin_index = workspaces\r\n          .iter()\r\n          .position(|workspace| workspace.name == origin_name)\r\n          .context(\"Failed to get index of given workspace.\")?;\r\n\r\n        let next_workspace_config = workspaces\r\n          .get(origin_index + 1)\r\n          .or_else(|| workspaces.first());\r\n\r\n        let next_workspace_name =\r\n          next_workspace_config.map(|config| config.name.clone());\r\n\r\n        let next_workspace = next_workspace_name\r\n          .as_ref()\r\n          .and_then(|name| self.workspace_by_name(name));\r\n\r\n        (next_workspace_name, next_workspace)\r\n      }\r\n      WorkspaceTarget::Previous => {\r\n        let workspaces = &config.value.workspaces;\r\n        let origin_name = origin_workspace.config().name.clone();\r\n        let origin_index = workspaces\r\n          .iter()\r\n          .position(|workspace| workspace.name == origin_name)\r\n          .context(\"Failed to get index of given workspace.\")?;\r\n\r\n        let previous_workspace_config = workspaces.get(\r\n          origin_index.checked_sub(1).unwrap_or(workspaces.len() - 1),\r\n        );\r\n\r\n        let previous_workspace_name =\r\n          previous_workspace_config.map(|config| config.name.clone());\r\n\r\n        let previous_workspace = previous_workspace_name\r\n          .as_ref()\r\n          .and_then(|name| self.workspace_by_name(name));\r\n\r\n        (previous_workspace_name, previous_workspace)\r\n      }\r\n\r\n      WorkspaceTarget::Direction(direction) => {\r\n        let origin_monitor =\r\n          origin_workspace.monitor().context(\"No focused monitor.\")?;\r\n\r\n        let target_workspace = self\r\n          .monitor_in_direction(&origin_monitor, &direction)?\r\n          .and_then(|monitor| monitor.displayed_workspace());\r\n\r\n        (\r\n          target_workspace\r\n            .as_ref()\r\n            .map(|workspace| workspace.config().name),\r\n          target_workspace,\r\n        )\r\n      }\r\n    };\r\n\r\n    Ok((name, workspace))\r\n  }\r\n\r\n  /// Gets windows that should be redrawn.\r\n  ///\r\n  /// When redrawing after a command that changes a window's type (e.g.\r\n  /// tiling -> floating), the original detached window might still be\r\n  /// queued for a redraw and should be filtered out.\r\n  pub fn windows_to_redraw(&self) -> Vec<WindowContainer> {\r\n    self\r\n      .pending_sync\r\n      .containers_to_redraw()\r\n      .values()\r\n      .flat_map(CommonGetters::self_and_descendants)\r\n      .filter(|container| !container.is_detached())\r\n      .filter_map(|container| container.try_into().ok())\r\n      .collect()\r\n  }\r\n\r\n  /// Gets the currently focused container. This can either be a window or\r\n  /// a workspace without any descendant windows.\r\n  pub fn focused_container(&self) -> Option<Container> {\r\n    self.root_container.descendant_focus_order().next()\r\n  }\r\n\r\n  /// Emits a WM event through an MSPC channel.\r\n  ///\r\n  /// Does not emit events while the WM is paused or populating initial\r\n  /// state. This is to prevent events (e.g. workspace activation events)\r\n  /// from being emitted via IPC server before the initial state is\r\n  /// prepared.\r\n  pub fn emit_event(&self, event: WmEvent) {\r\n    if self.has_initialized\r\n      && (!self.is_paused || matches!(event, WmEvent::PauseChanged { .. }))\r\n    {\r\n      if let Err(err) = self.event_tx.send(event) {\r\n        warn!(\"Failed to send event: {}\", err);\r\n      }\r\n    }\r\n  }\r\n\r\n  /// Starts graceful shutdown via an MSPC channel.\r\n  pub fn emit_exit(&self) -> anyhow::Result<()> {\r\n    self.exit_tx.send(())?;\r\n    Ok(())\r\n  }\r\n\r\n  pub fn container_by_id(&self, id: Uuid) -> Option<Container> {\r\n    self\r\n      .root_container\r\n      .self_and_descendants()\r\n      .find(|container| container.id() == id)\r\n  }\r\n\r\n  /// Gets container to focus after the given window is unmanaged,\r\n  /// minimized, or moved to another workspace.\r\n  pub fn focus_target_after_removal(\r\n    &self,\r\n    removed_window: &WindowContainer,\r\n  ) -> Option<Container> {\r\n    // If the removed window is not focused, no need to change focus.\r\n    if self.focused_container() != Some(removed_window.clone().into()) {\r\n      return None;\r\n    }\r\n\r\n    // Get descendant focus order excluding the removed container.\r\n    let workspace = removed_window.workspace()?;\r\n    let descendant_focus_order = workspace\r\n      .descendant_focus_order()\r\n      .filter(|descendant| descendant.id() != removed_window.id())\r\n      .collect::<Vec<_>>();\r\n\r\n    // Get focus target that matches the removed window type. This applies\r\n    // for windows that aren't in a minimized state.\r\n    let focus_target_of_type = descendant_focus_order\r\n      .iter()\r\n      .filter_map(|descendant| descendant.as_window_container().ok())\r\n      .find(|descendant| {\r\n        matches!(\r\n          (descendant.state(), removed_window.state()),\r\n          (WindowState::Tiling, WindowState::Tiling)\r\n            | (WindowState::Floating(_), WindowState::Floating(_))\r\n            | (WindowState::Fullscreen(_), WindowState::Fullscreen(_))\r\n        )\r\n      })\r\n      .map(Into::into);\r\n\r\n    if focus_target_of_type.is_some() {\r\n      return focus_target_of_type;\r\n    }\r\n\r\n    let non_minimized_focus_target = descendant_focus_order\r\n      .iter()\r\n      .filter_map(|descendant| descendant.as_window_container().ok())\r\n      .find(|descendant| descendant.state() != WindowState::Minimized)\r\n      .map(Into::into);\r\n\r\n    non_minimized_focus_target\r\n      .or(descendant_focus_order.first().cloned())\r\n      .or(Some(workspace.into()))\r\n  }\r\n\r\n  /// Returns all containers that contain the given point.\r\n  #[allow(clippy::unused_self)]\r\n  pub fn containers_at_point(\r\n    &self,\r\n    origin_container: &Container,\r\n    point: &Point,\r\n  ) -> Vec<Container> {\r\n    origin_container\r\n      .descendants()\r\n      .filter(|descendant| {\r\n        descendant\r\n          .to_rect()\r\n          .is_ok_and(|rect| rect.contains_point(point))\r\n      })\r\n      .collect()\r\n  }\r\n\r\n  /// Returns the monitor that contains the given point.\r\n  pub fn monitor_at_point(&self, point: &Point) -> Option<Monitor> {\r\n    self\r\n      .monitors()\r\n      .iter()\r\n      .find(|monitor| {\r\n        monitor\r\n          .to_rect()\r\n          .is_ok_and(|rect| rect.contains_point(point))\r\n      })\r\n      .cloned()\r\n  }\r\n\r\n  /// Cleans up windows that are no longer alive.\r\n  ///\r\n  /// This addresses the \"ghost window\" issue where applications may\r\n  /// terminate without sending window destroy events, leaving invalid\r\n  /// windows in WM state.\r\n  ///\r\n  /// See: <https://github.com/glzr-io/glazewm/issues/1219>\r\n  pub fn cleanup_invalid_windows(&mut self) -> anyhow::Result<()> {\r\n    let invalid_windows = self\r\n      .windows()\r\n      .into_iter()\r\n      .filter(|window| !window.native().is_valid());\r\n\r\n    for window in invalid_windows {\r\n      tracing::info!(\"Removing invalid window: {}\", window);\r\n      unmanage_window(window, self)?;\r\n    }\r\n\r\n    Ok(())\r\n  }\r\n}\r\n\r\nimpl Drop for WmState {\r\n  fn drop(&mut self) {\r\n    let managed_windows = self.windows();\r\n\r\n    for window in &managed_windows {\r\n      // Redraw windows to their intended positions. On macOS, this will\r\n      // unhide windows that are on other workspaces.\r\n      if let Ok(rect) = window.to_rect() {\r\n        if let Err(err) = window.native().set_frame(&rect) {\r\n          warn!(\"Failed to redraw window on cleanup: {:?}\", err);\r\n        }\r\n      }\r\n\r\n      // Reset any effects on Windows.\r\n      #[cfg(target_os = \"windows\")]\r\n      {\r\n        if let Err(err) = window.native().show() {\r\n          warn!(\"Failed to show window: {:?}\", err);\r\n        }\r\n\r\n        let _ = window.native().set_taskbar_visibility(true);\r\n        let _ = window.native().set_border_color(None);\r\n        let _ = window\r\n          .native()\r\n          .set_transparency(&OpacityValue::from_alpha(u8::MAX));\r\n      }\r\n    }\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-cli/Cargo.toml",
    "content": "[package]\r\nname = \"wm-cli\"\r\nversion = \"0.0.0\"\r\nedition = \"2021\"\r\n\r\n[lib]\r\npath = \"src/lib.rs\"\r\n\r\n[[bin]]\r\nname = \"glazewm-cli\"\r\npath = \"src/main.rs\"\r\n\r\n[build-dependencies]\r\ntauri-winres = { workspace = true }\r\n\r\n[dependencies]\r\nanyhow = { workspace = true }\r\nfutures-util = { workspace = true }\r\nserde_json = { workspace = true }\r\ntokio = { workspace = true }\r\ntokio-tungstenite = { workspace = true }\r\nuuid = { workspace = true }\r\nwm-common = { path = \"../wm-common\" }\r\nwm-ipc-client = { path = \"../wm-ipc-client\" }\r\n"
  },
  {
    "path": "packages/wm-cli/build.rs",
    "content": "use tauri_winres::VersionInfo;\r\n\r\nfn main() {\r\n  println!(\"cargo:rerun-if-env-changed=VERSION_NUMBER\");\r\n  let mut res = tauri_winres::WindowsResource::new();\r\n\r\n  res.set_icon(\"../../resources/assets/icon.ico\");\r\n\r\n  // Set language to English (US).\r\n  res.set_language(0x0409);\r\n\r\n  res.set(\"OriginalFilename\", \"glazewm.exe\");\r\n  res.set(\"ProductName\", \"GlazeWM CLI\");\r\n  res.set(\"FileDescription\", \"GlazeWM CLI\");\r\n\r\n  let version_parts = env!(\"VERSION_NUMBER\")\r\n    .split('.')\r\n    .take(3)\r\n    .map(|part| part.parse().unwrap_or(0))\r\n    .collect::<Vec<u16>>();\r\n\r\n  let [major, minor, patch] =\r\n    <[u16; 3]>::try_from(version_parts).unwrap_or([0, 0, 0]);\r\n\r\n  let version_str = format!(\"{major}.{minor}.{patch}.0\");\r\n  res.set(\"FileVersion\", &version_str);\r\n  res.set(\"ProductVersion\", &version_str);\r\n\r\n  let version_u64 = (u64::from(major) << 48)\r\n    | (u64::from(minor) << 32)\r\n    | (u64::from(patch) << 16);\r\n\r\n  res.set_version_info(VersionInfo::FILEVERSION, version_u64);\r\n  res.set_version_info(VersionInfo::PRODUCTVERSION, version_u64);\r\n\r\n  res.compile().unwrap();\r\n}\r\n"
  },
  {
    "path": "packages/wm-cli/src/lib.rs",
    "content": "#![warn(clippy::all, clippy::pedantic)]\r\n#![allow(clippy::missing_errors_doc)]\r\n\r\nuse anyhow::Context;\r\nuse wm_common::ClientResponseData;\r\nuse wm_ipc_client::IpcClient;\r\n\r\npub async fn start(args: Vec<String>) -> anyhow::Result<()> {\r\n  let mut client = IpcClient::connect().await?;\r\n\r\n  let message = args[1..].join(\" \");\r\n  client\r\n    .send(&message)\r\n    .await\r\n    .context(\"Failed to send command to IPC server.\")?;\r\n\r\n  let client_response = client\r\n    .client_response(&message)\r\n    .await\r\n    .context(\"Failed to receive response from IPC server.\")?;\r\n\r\n  match client_response.data {\r\n    // For event subscriptions, omit the initial response message and\r\n    // continuously output subsequent event messages.\r\n    Some(ClientResponseData::EventSubscribe(data)) => loop {\r\n      let event_subscription = client\r\n        .event_subscription(&data.subscription_id)\r\n        .await\r\n        .context(\"Failed to receive response from IPC server.\")?;\r\n\r\n      println!(\"{}\", serde_json::to_string(&event_subscription)?);\r\n    },\r\n    // For all other messages, output and exit when the first response\r\n    // message is received.\r\n    _ => {\r\n      println!(\"{}\", serde_json::to_string(&client_response)?);\r\n    }\r\n  }\r\n\r\n  Ok(())\r\n}\r\n"
  },
  {
    "path": "packages/wm-cli/src/main.rs",
    "content": "use std::{env, process::Command};\r\n\r\nuse anyhow::Context;\r\nuse wm_cli::start;\r\nuse wm_common::AppCommand;\r\n\r\n#[tokio::main]\r\nasync fn main() -> anyhow::Result<()> {\r\n  let args = std::env::args().collect::<Vec<_>>();\r\n  let app_command = AppCommand::parse_with_default(&args);\r\n\r\n  match app_command {\r\n    AppCommand::Start { .. } => {\r\n      let exe_path = env::current_exe()?;\r\n      let exe_dir = exe_path\r\n        .parent()\r\n        .context(\"Failed to resolve path to the current executable.\")?\r\n        .to_owned();\r\n\r\n      // Main executable is either in the current directory (when running\r\n      // debug/release builds) or in the parent directory when packaged.\r\n      let main_path =\r\n        [exe_dir.join(\"glazewm.exe\"), exe_dir.join(\"../glazewm.exe\")]\r\n          .into_iter()\r\n          .find(|path| path.exists() && *path != exe_path)\r\n          .and_then(|path| path.to_str().map(ToString::to_string))\r\n          .context(\"Failed to resolve path to the main executable.\")?;\r\n\r\n      // UIAccess applications can't be started directly, so we need to use\r\n      // CMD to start it. The start command is used to avoid a long-running\r\n      // CMD process in the background.\r\n      Command::new(\"cmd\")\r\n        .args(\r\n          [\"/C\", \"start\", \"\", &main_path]\r\n            .into_iter()\r\n            .chain(args.iter().skip(1).map(String::as_str)),\r\n        )\r\n        .spawn()\r\n        .context(\"Failed to start main executable.\")?;\r\n\r\n      Ok(())\r\n    }\r\n    _ => start(args).await,\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-common/Cargo.toml",
    "content": "[package]\r\nname = \"wm-common\"\r\nversion = \"0.0.0\"\r\nedition = \"2021\"\r\n\r\n[lib]\r\npath = \"src/lib.rs\"\r\n\r\n[dependencies]\r\nanyhow = { workspace = true }\r\nclap = { workspace = true }\r\nregex = { workspace = true }\r\nserde = { workspace = true }\r\ntracing = { workspace = true }\r\nuuid = { workspace = true }\r\n\r\nwm-platform = { path = \"../wm-platform\" }\r\n"
  },
  {
    "path": "packages/wm-common/src/active_drag.rs",
    "content": "use serde::{Deserialize, Serialize};\r\nuse wm_platform::Rect;\r\n\r\n#[derive(Debug, Clone, Deserialize, Serialize)]\r\npub struct ActiveDrag {\r\n  /// Whether the drag is a move or resize.\r\n  pub operation: Option<ActiveDragOperation>,\r\n\r\n  /// Whether the drag is from a floating window.\r\n  ///\r\n  /// If `true`, it means we shouldn't drop the window as a tiling window\r\n  /// on drag end.\r\n  pub is_from_floating: bool,\r\n\r\n  /// Initial position when the drag started.\r\n  ///\r\n  /// Used to calculate movement distance.\r\n  pub initial_position: Rect,\r\n}\r\n\r\n#[derive(Debug, Copy, Clone, Deserialize, PartialEq, Serialize)]\r\npub enum ActiveDragOperation {\r\n  Move,\r\n  Resize,\r\n}\r\n"
  },
  {
    "path": "packages/wm-common/src/app_command.rs",
    "content": "use std::{iter, path::PathBuf};\r\n\r\nuse clap::{error::KindFormatter, Args, Parser, ValueEnum};\r\nuse serde::{Deserialize, Deserializer, Serialize};\r\nuse tracing::Level;\r\nuse uuid::Uuid;\r\nuse wm_platform::{Delta, Direction, LengthValue, OpacityValue};\r\n\r\nuse crate::TilingDirection;\r\n\r\nconst VERSION: &str = env!(\"VERSION_NUMBER\");\r\n\r\n#[derive(Clone, Debug, Parser)]\r\n#[clap(name = \"glazewm\", author, version = VERSION, about, long_about = None)]\r\npub enum AppCommand {\r\n  /// Starts the window manager.\r\n  Start {\r\n    /// Custom path to user config file.\r\n    ///\r\n    /// The default path is `%userprofile%/.glzr/glazewm/config.yaml`\r\n    #[clap(short = 'c', long = \"config\", value_hint = clap::ValueHint::FilePath)]\r\n    config_path: Option<PathBuf>,\r\n\r\n    #[clap(flatten)]\r\n    verbosity: Verbosity,\r\n  },\r\n\r\n  /// Retrieves and outputs a specific part of the window manager's state.\r\n  ///\r\n  /// Requires an already running instance of the window manager.\r\n  #[clap(alias = \"q\")]\r\n  Query {\r\n    #[clap(subcommand)]\r\n    command: QueryCommand,\r\n  },\r\n\r\n  /// Invokes a window manager command.\r\n  ///\r\n  /// Requires an already running instance of the window manager.\r\n  #[clap(alias = \"c\")]\r\n  Command {\r\n    #[clap(long = \"id\")]\r\n    subject_container_id: Option<Uuid>,\r\n\r\n    #[clap(subcommand)]\r\n    command: InvokeCommand,\r\n  },\r\n\r\n  /// Subscribes to one or more WM events (e.g. `window_close`), and\r\n  /// continuously outputs the incoming events.\r\n  ///\r\n  /// Requires an already running instance of the window manager.\r\n  Sub {\r\n    /// WM event(s) to subscribe to.\r\n    #[clap(short = 'e', long, value_enum, num_args = 1..)]\r\n    events: Vec<SubscribableEvent>,\r\n  },\r\n\r\n  /// Unsubscribes from a prior event subscription.\r\n  ///\r\n  /// Requires an already running instance of the window manager.\r\n  Unsub {\r\n    /// Subscription ID to unsubscribe from.\r\n    #[clap(long = \"id\")]\r\n    subscription_id: Uuid,\r\n  },\r\n}\r\n\r\nimpl AppCommand {\r\n  /// Parses `AppCommand` from command line arguments.\r\n  ///\r\n  /// Defaults to `AppCommand::Start` if no arguments are provided.\r\n  #[must_use]\r\n  pub fn parse_with_default(args: &Vec<String>) -> Self {\r\n    if args.len() == 1 {\r\n      AppCommand::Start {\r\n        config_path: None,\r\n        verbosity: Verbosity {\r\n          verbose: false,\r\n          quiet: false,\r\n        },\r\n      }\r\n    } else {\r\n      AppCommand::parse_from(args)\r\n    }\r\n  }\r\n}\r\n\r\n/// Verbosity flags to be used with `#[command(flatten)]`.\r\n#[derive(Args, Clone, Debug)]\r\n#[clap(about = None, long_about = None)]\r\npub struct Verbosity {\r\n  /// Enables verbose logging.\r\n  #[clap(short = 'v', long, action)]\r\n  verbose: bool,\r\n\r\n  /// Disables logging.\r\n  #[clap(short = 'q', long, action, conflicts_with = \"verbose\")]\r\n  quiet: bool,\r\n}\r\n\r\nimpl Verbosity {\r\n  /// Gets the log level based on the verbosity flags.\r\n  #[must_use]\r\n  pub fn level(&self) -> Level {\r\n    match (self.verbose, self.quiet) {\r\n      (true, _) => Level::DEBUG,\r\n      (_, true) => Level::ERROR,\r\n      _ => Level::INFO,\r\n    }\r\n  }\r\n}\r\n\r\n#[derive(Clone, Debug, Parser)]\r\npub enum QueryCommand {\r\n  /// Outputs metadata about the application (e.g. version number).\r\n  AppMetadata,\r\n  /// Outputs the active binding modes.\r\n  BindingModes,\r\n  /// Outputs the focused container (either a window or an empty\r\n  /// workspace).\r\n  Focused,\r\n  /// Outputs the tiling direction of the focused container.\r\n  TilingDirection,\r\n  /// Outputs all monitors.\r\n  Monitors,\r\n  /// Outputs all windows.\r\n  Windows,\r\n  /// Outputs all active workspaces.\r\n  Workspaces,\r\n  /// Outputs whether the window manager is paused.\r\n  Paused,\r\n}\r\n\r\n#[derive(Clone, Debug, PartialEq, ValueEnum)]\r\n#[clap(rename_all = \"snake_case\")]\r\npub enum SubscribableEvent {\r\n  All,\r\n  ApplicationExiting,\r\n  BindingModesChanged,\r\n  FocusChanged,\r\n  FocusedContainerMoved,\r\n  MonitorAdded,\r\n  MonitorUpdated,\r\n  MonitorRemoved,\r\n  TilingDirectionChanged,\r\n  UserConfigChanged,\r\n  WindowManaged,\r\n  WindowUnmanaged,\r\n  WorkspaceActivated,\r\n  WorkspaceDeactivated,\r\n  WorkspaceUpdated,\r\n  PauseChanged,\r\n}\r\n\r\n#[derive(Clone, Debug, Parser, PartialEq, Serialize)]\r\npub enum InvokeCommand {\r\n  AdjustBorders(InvokeAdjustBordersCommand),\r\n  Close,\r\n  Focus(InvokeFocusCommand),\r\n  Ignore,\r\n  Move(InvokeMoveCommand),\r\n  MoveWorkspace {\r\n    #[clap(long)]\r\n    direction: Direction,\r\n  },\r\n  Position(InvokePositionCommand),\r\n  Resize(InvokeResizeCommand),\r\n  UpdateWorkspaceConfig {\r\n    #[clap(long, allow_hyphen_values = true)]\r\n    workspace: Option<String>,\r\n    #[clap(flatten)]\r\n    new_config: InvokeUpdateWorkspaceConfig,\r\n  },\r\n  SetFloating {\r\n    #[clap(long, default_missing_value = \"true\", require_equals = true, num_args = 0..=1)]\r\n    shown_on_top: Option<bool>,\r\n\r\n    #[clap(long, default_missing_value = \"true\", require_equals = true, num_args = 0..=1)]\r\n    centered: Option<bool>,\r\n\r\n    #[clap(long, allow_hyphen_values = true)]\r\n    x_pos: Option<i32>,\r\n\r\n    #[clap(long, allow_hyphen_values = true)]\r\n    y_pos: Option<i32>,\r\n\r\n    #[clap(long, allow_hyphen_values = true)]\r\n    width: Option<LengthValue>,\r\n\r\n    #[clap(long, allow_hyphen_values = true)]\r\n    height: Option<LengthValue>,\r\n  },\r\n  SetFullscreen {\r\n    #[clap(long, default_missing_value = \"true\", require_equals = true, num_args = 0..=1)]\r\n    shown_on_top: Option<bool>,\r\n\r\n    #[clap(long, default_missing_value = \"true\", require_equals = true, num_args = 0..=1)]\r\n    maximized: Option<bool>,\r\n  },\r\n  SetMinimized,\r\n  SetTiling,\r\n  SetTitleBarVisibility {\r\n    #[clap(required = true, value_enum)]\r\n    visibility: TitleBarVisibility,\r\n  },\r\n  SetTransparency(SetTransparencyCommand),\r\n  ShellExec {\r\n    #[clap(long, action)]\r\n    hide_window: bool,\r\n\r\n    #[clap(required = true, trailing_var_arg = true)]\r\n    command: Vec<String>,\r\n  },\r\n  // Reuse `InvokeResizeCommand` struct.\r\n  Size(InvokeResizeCommand),\r\n  ToggleFloating {\r\n    #[clap(long, default_missing_value = \"true\", require_equals = true, num_args = 0..=1)]\r\n    shown_on_top: Option<bool>,\r\n\r\n    #[clap(long, default_missing_value = \"true\", require_equals = true, num_args = 0..=1)]\r\n    centered: Option<bool>,\r\n  },\r\n  ToggleFullscreen {\r\n    #[clap(long, default_missing_value = \"true\", require_equals = true, num_args = 0..=1)]\r\n    shown_on_top: Option<bool>,\r\n\r\n    #[clap(long, default_missing_value = \"true\", require_equals = true, num_args = 0..=1)]\r\n    maximized: Option<bool>,\r\n  },\r\n  ToggleMinimized,\r\n  ToggleTiling,\r\n  ToggleTilingDirection,\r\n  SetTilingDirection {\r\n    #[clap(required = true)]\r\n    tiling_direction: TilingDirection,\r\n  },\r\n  WmCycleFocus {\r\n    #[clap(long, default_value_t = false)]\r\n    omit_floating: bool,\r\n\r\n    #[clap(long, default_value_t = false)]\r\n    omit_fullscreen: bool,\r\n\r\n    #[clap(long, default_value_t = true)]\r\n    omit_minimized: bool,\r\n\r\n    #[clap(long, default_value_t = false)]\r\n    omit_tiling: bool,\r\n  },\r\n  WmDisableBindingMode {\r\n    #[clap(long)]\r\n    name: String,\r\n  },\r\n  WmEnableBindingMode {\r\n    #[clap(long)]\r\n    name: String,\r\n  },\r\n  WmExit,\r\n  WmRedraw,\r\n  WmReloadConfig,\r\n  WmTogglePause,\r\n}\r\n\r\nimpl<'de> Deserialize<'de> for InvokeCommand {\r\n  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\r\n  where\r\n    D: Deserializer<'de>,\r\n  {\r\n    // Clap expects an array of string slices where the first argument is\r\n    // the binary name/path. When deserializing commands from the user\r\n    // config, we therefore have to prepend an additional empty argument.\r\n    let unparsed = String::deserialize(deserializer)?;\r\n    let unparsed_split = iter::once(\"\").chain(unparsed.split_whitespace());\r\n\r\n    InvokeCommand::try_parse_from(unparsed_split).map_err(|err| {\r\n      // Format the error message and remove the \"error: \" prefix.\r\n      let err_msg = err.apply::<KindFormatter>().to_string();\r\n      serde::de::Error::custom(err_msg.trim_start_matches(\"error: \"))\r\n    })\r\n  }\r\n}\r\n\r\n#[derive(Clone, Debug, PartialEq, Serialize, ValueEnum)]\r\n#[clap(rename_all = \"snake_case\")]\r\n#[serde(rename_all = \"snake_case\")]\r\npub enum TitleBarVisibility {\r\n  Shown,\r\n  Hidden,\r\n}\r\n\r\n#[derive(Args, Clone, Debug, PartialEq, Serialize)]\r\n#[group(required = true, multiple = true)]\r\npub struct InvokeAdjustBordersCommand {\r\n  #[clap(long, allow_hyphen_values = true)]\r\n  pub top: Option<LengthValue>,\r\n\r\n  #[clap(long, allow_hyphen_values = true)]\r\n  pub right: Option<LengthValue>,\r\n\r\n  #[clap(long, allow_hyphen_values = true)]\r\n  pub bottom: Option<LengthValue>,\r\n\r\n  #[clap(long, allow_hyphen_values = true)]\r\n  pub left: Option<LengthValue>,\r\n}\r\n\r\n#[derive(Args, Clone, Debug, PartialEq, Serialize)]\r\n#[group(required = true, multiple = false)]\r\n#[allow(clippy::struct_excessive_bools)]\r\npub struct InvokeFocusCommand {\r\n  #[clap(long)]\r\n  pub direction: Option<Direction>,\r\n\r\n  #[clap(long)]\r\n  pub container_id: Option<Uuid>,\r\n\r\n  #[clap(long)]\r\n  pub workspace_in_direction: Option<Direction>,\r\n\r\n  #[clap(long)]\r\n  pub workspace: Option<String>,\r\n\r\n  #[clap(long)]\r\n  pub monitor: Option<usize>,\r\n\r\n  #[clap(long)]\r\n  pub next_active_workspace: bool,\r\n\r\n  #[clap(long)]\r\n  pub prev_active_workspace: bool,\r\n\r\n  #[clap(long)]\r\n  pub next_workspace: bool,\r\n\r\n  #[clap(long)]\r\n  pub prev_workspace: bool,\r\n\r\n  #[clap(long)]\r\n  pub next_active_workspace_on_monitor: bool,\r\n\r\n  #[clap(long)]\r\n  pub prev_active_workspace_on_monitor: bool,\r\n\r\n  #[clap(long)]\r\n  pub recent_workspace: bool,\r\n}\r\n\r\n#[derive(Args, Clone, Debug, PartialEq, Serialize)]\r\n#[group(required = true, multiple = false)]\r\n#[allow(clippy::struct_excessive_bools)]\r\npub struct InvokeMoveCommand {\r\n  /// Direction to move the window.\r\n  #[clap(long)]\r\n  pub direction: Option<Direction>,\r\n\r\n  /// Move window to workspace in specified direction.\r\n  #[clap(long)]\r\n  pub workspace_in_direction: Option<Direction>,\r\n\r\n  /// Name of workspace to move the window.\r\n  #[clap(long)]\r\n  pub workspace: Option<String>,\r\n\r\n  #[clap(long)]\r\n  pub next_active_workspace: bool,\r\n\r\n  #[clap(long)]\r\n  pub prev_active_workspace: bool,\r\n\r\n  #[clap(long)]\r\n  pub next_workspace: bool,\r\n\r\n  #[clap(long)]\r\n  pub prev_workspace: bool,\r\n\r\n  #[clap(long)]\r\n  pub next_active_workspace_on_monitor: bool,\r\n\r\n  #[clap(long)]\r\n  pub prev_active_workspace_on_monitor: bool,\r\n\r\n  #[clap(long)]\r\n  pub recent_workspace: bool,\r\n}\r\n\r\n#[derive(Args, Clone, Debug, PartialEq, Serialize)]\r\n#[group(required = true, multiple = true)]\r\npub struct InvokeResizeCommand {\r\n  #[clap(long, allow_hyphen_values = true)]\r\n  pub width: Option<LengthValue>,\r\n\r\n  #[clap(long, allow_hyphen_values = true)]\r\n  pub height: Option<LengthValue>,\r\n}\r\n\r\n#[derive(Args, Clone, Debug, PartialEq, Serialize)]\r\n#[group(required = true, multiple = true)]\r\npub struct SetTransparencyCommand {\r\n  #[clap(long)]\r\n  pub opacity: Option<OpacityValue>,\r\n\r\n  #[clap(long, allow_hyphen_values = true)]\r\n  pub opacity_delta: Option<Delta<OpacityValue>>,\r\n}\r\n\r\n#[derive(Args, Clone, Debug, PartialEq, Serialize)]\r\n#[group(required = true, multiple = true)]\r\npub struct InvokePositionCommand {\r\n  #[clap(long, action)]\r\n  pub centered: bool,\r\n\r\n  #[clap(long, allow_hyphen_values = true)]\r\n  pub x_pos: Option<i32>,\r\n\r\n  #[clap(long, allow_hyphen_values = true)]\r\n  pub y_pos: Option<i32>,\r\n}\r\n\r\n#[derive(Args, Clone, Debug, PartialEq, Serialize)]\r\n#[group(required = true, multiple = true)]\r\npub struct InvokeUpdateWorkspaceConfig {\r\n  #[clap(long, allow_hyphen_values = true)]\r\n  pub name: Option<String>,\r\n\r\n  #[clap(long, allow_hyphen_values = true)]\r\n  pub display_name: Option<String>,\r\n\r\n  #[clap(long)]\r\n  pub bind_to_monitor: Option<u32>,\r\n\r\n  #[clap(long)]\r\n  pub keep_alive: Option<bool>,\r\n}\r\n"
  },
  {
    "path": "packages/wm-common/src/display_state.rs",
    "content": "use serde::{Deserialize, Serialize};\r\n\r\n/// Represents whether something is shown, hidden, or in an intermediary\r\n/// state.\r\n#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]\r\n#[serde(rename_all = \"snake_case\")]\r\npub enum DisplayState {\r\n  Shown,\r\n  Showing,\r\n  Hidden,\r\n  Hiding,\r\n}\r\n"
  },
  {
    "path": "packages/wm-common/src/dtos/container_dto.rs",
    "content": "use serde::{Deserialize, Serialize};\r\n\r\nuse super::{\r\n  MonitorDto, RootContainerDto, SplitContainerDto, WindowDto, WorkspaceDto,\r\n};\r\n\r\n/// User-friendly representation of a container.\r\n///\r\n/// Used for IPC and debug logging.\r\n#[derive(Clone, Debug, Deserialize, Serialize)]\r\n#[serde(tag = \"type\", rename_all = \"snake_case\")]\r\npub enum ContainerDto {\r\n  Root(RootContainerDto),\r\n  Monitor(MonitorDto),\r\n  Workspace(WorkspaceDto),\r\n  Split(SplitContainerDto),\r\n  Window(WindowDto),\r\n}\r\n"
  },
  {
    "path": "packages/wm-common/src/dtos/mod.rs",
    "content": "mod container_dto;\r\nmod monitor_dto;\r\nmod root_container_dto;\r\nmod split_container_dto;\r\nmod window_dto;\r\nmod workspace_dto;\r\n\r\npub use container_dto::*;\r\npub use monitor_dto::*;\r\npub use root_container_dto::*;\r\npub use split_container_dto::*;\r\npub use window_dto::*;\r\npub use workspace_dto::*;\r\n"
  },
  {
    "path": "packages/wm-common/src/dtos/monitor_dto.rs",
    "content": "use serde::{Deserialize, Serialize};\r\nuse uuid::Uuid;\r\nuse wm_platform::Rect;\r\n\r\nuse super::ContainerDto;\r\n\r\n/// User-friendly representation of a monitor.\r\n///\r\n/// Used for IPC and debug logging.\r\n#[derive(Clone, Debug, Deserialize, Serialize)]\r\n#[serde(rename_all = \"camelCase\")]\r\npub struct MonitorDto {\r\n  pub id: Uuid,\r\n  pub parent_id: Option<Uuid>,\r\n  pub children: Vec<ContainerDto>,\r\n  pub child_focus_order: Vec<Uuid>,\r\n  pub has_focus: bool,\r\n  pub width: i32,\r\n  pub height: i32,\r\n  pub x: i32,\r\n  pub y: i32,\r\n  pub dpi: u32,\r\n  pub scale_factor: f32,\r\n  pub handle: Option<isize>,\r\n  pub device_name: String,\r\n  pub device_path: Option<String>,\r\n  pub hardware_id: Option<String>,\r\n  pub working_rect: Rect,\r\n}\r\n"
  },
  {
    "path": "packages/wm-common/src/dtos/root_container_dto.rs",
    "content": "use serde::{Deserialize, Serialize};\r\nuse uuid::Uuid;\r\n\r\nuse super::ContainerDto;\r\n\r\n/// User-friendly representation of a root container.\r\n///\r\n/// Used for IPC and debug logging.\r\n#[derive(Clone, Debug, Deserialize, Serialize)]\r\n#[serde(rename_all = \"camelCase\")]\r\npub struct RootContainerDto {\r\n  pub id: Uuid,\r\n  pub parent_id: Option<Uuid>,\r\n  pub children: Vec<ContainerDto>,\r\n  pub child_focus_order: Vec<Uuid>,\r\n}\r\n"
  },
  {
    "path": "packages/wm-common/src/dtos/split_container_dto.rs",
    "content": "use serde::{Deserialize, Serialize};\r\nuse uuid::Uuid;\r\n\r\nuse super::ContainerDto;\r\nuse crate::TilingDirection;\r\n\r\n/// User-friendly representation of a split container.\r\n///\r\n/// Used for IPC and debug logging.\r\n#[derive(Clone, Debug, Deserialize, Serialize)]\r\n#[serde(rename_all = \"camelCase\")]\r\npub struct SplitContainerDto {\r\n  pub id: Uuid,\r\n  pub parent_id: Option<Uuid>,\r\n  pub children: Vec<ContainerDto>,\r\n  pub child_focus_order: Vec<Uuid>,\r\n  pub has_focus: bool,\r\n  pub tiling_size: f32,\r\n  pub width: i32,\r\n  pub height: i32,\r\n  pub x: i32,\r\n  pub y: i32,\r\n  pub tiling_direction: TilingDirection,\r\n}\r\n"
  },
  {
    "path": "packages/wm-common/src/dtos/window_dto.rs",
    "content": "use serde::{Deserialize, Serialize};\r\nuse uuid::Uuid;\r\nuse wm_platform::{Rect, RectDelta};\r\n\r\nuse crate::{ActiveDrag, DisplayState, WindowState};\r\n\r\n/// User-friendly representation of a tiling or non-tiling window.\r\n///\r\n/// Used for IPC and debug logging.\r\n#[derive(Clone, Debug, Deserialize, Serialize)]\r\n#[serde(rename_all = \"camelCase\")]\r\npub struct WindowDto {\r\n  pub id: Uuid,\r\n  pub parent_id: Option<Uuid>,\r\n  pub has_focus: bool,\r\n  pub tiling_size: Option<f32>,\r\n  pub width: i32,\r\n  pub height: i32,\r\n  pub x: i32,\r\n  pub y: i32,\r\n  pub state: WindowState,\r\n  pub prev_state: Option<WindowState>,\r\n  pub display_state: DisplayState,\r\n  pub border_delta: RectDelta,\r\n  pub floating_placement: Rect,\r\n  pub handle: isize,\r\n  pub title: String,\r\n  #[cfg(target_os = \"windows\")]\r\n  pub class_name: String,\r\n  pub process_name: String,\r\n  pub active_drag: Option<ActiveDrag>,\r\n}\r\n"
  },
  {
    "path": "packages/wm-common/src/dtos/workspace_dto.rs",
    "content": "use serde::{Deserialize, Serialize};\r\nuse uuid::Uuid;\r\n\r\nuse super::ContainerDto;\r\nuse crate::TilingDirection;\r\n\r\n/// User-friendly representation of a workspace.\r\n///\r\n/// Used for IPC and debug logging.\r\n#[derive(Clone, Debug, Deserialize, Serialize)]\r\n#[serde(rename_all = \"camelCase\")]\r\npub struct WorkspaceDto {\r\n  pub id: Uuid,\r\n  pub name: String,\r\n  pub display_name: Option<String>,\r\n  pub parent_id: Option<Uuid>,\r\n  pub children: Vec<ContainerDto>,\r\n  pub child_focus_order: Vec<Uuid>,\r\n  pub has_focus: bool,\r\n  pub is_displayed: bool,\r\n  pub width: i32,\r\n  pub height: i32,\r\n  pub x: i32,\r\n  pub y: i32,\r\n  pub tiling_direction: TilingDirection,\r\n}\r\n"
  },
  {
    "path": "packages/wm-common/src/hide_corner.rs",
    "content": "/// Represents the corner of a monitor that a window should be hidden in.\r\n#[derive(Debug, Clone, Copy, PartialEq)]\r\npub enum HideCorner {\r\n  BottomLeft,\r\n  BottomRight,\r\n}\r\n"
  },
  {
    "path": "packages/wm-common/src/ipc.rs",
    "content": "use serde::{Deserialize, Serialize};\r\nuse uuid::Uuid;\r\n\r\nuse crate::{BindingModeConfig, ContainerDto, TilingDirection, WmEvent};\r\n\r\npub const DEFAULT_IPC_PORT: u32 = 6123;\r\n\r\n#[derive(Clone, Debug, Deserialize, Serialize)]\r\n#[serde(tag = \"messageType\", rename_all = \"snake_case\")]\r\npub enum ServerMessage {\r\n  ClientResponse(ClientResponseMessage),\r\n  EventSubscription(EventSubscriptionMessage),\r\n}\r\n\r\n#[derive(Clone, Debug, Deserialize, Serialize)]\r\n#[serde(rename_all = \"camelCase\")]\r\npub struct ClientResponseMessage {\r\n  pub client_message: String,\r\n  pub data: Option<ClientResponseData>,\r\n  pub error: Option<String>,\r\n  pub success: bool,\r\n}\r\n\r\n#[derive(Clone, Debug, Deserialize, Serialize)]\r\n#[serde(untagged)]\r\npub enum ClientResponseData {\r\n  AppMetadata(AppMetadataData),\r\n  BindingModes(BindingModesData),\r\n  Command(CommandData),\r\n  EventSubscribe(EventSubscribeData),\r\n  EventUnsubscribe,\r\n  Focused(FocusedData),\r\n  Monitors(MonitorsData),\r\n  TilingDirection(TilingDirectionData),\r\n  Windows(WindowsData),\r\n  Workspaces(WorkspacesData),\r\n  Paused(bool),\r\n}\r\n\r\n#[derive(Clone, Debug, Deserialize, Serialize)]\r\n#[serde(rename_all = \"camelCase\")]\r\npub struct AppMetadataData {\r\n  pub version: String,\r\n}\r\n\r\n#[derive(Clone, Debug, Deserialize, Serialize)]\r\n#[serde(rename_all = \"camelCase\")]\r\npub struct BindingModesData {\r\n  pub binding_modes: Vec<BindingModeConfig>,\r\n}\r\n\r\n#[derive(Clone, Debug, Deserialize, Serialize)]\r\n#[serde(rename_all = \"camelCase\")]\r\npub struct CommandData {\r\n  pub subject_container_id: Uuid,\r\n}\r\n\r\n#[derive(Clone, Debug, Deserialize, Serialize)]\r\n#[serde(rename_all = \"camelCase\")]\r\npub struct EventSubscribeData {\r\n  pub subscription_id: Uuid,\r\n}\r\n\r\n#[derive(Clone, Debug, Deserialize, Serialize)]\r\n#[serde(rename_all = \"camelCase\")]\r\npub struct FocusedData {\r\n  pub focused: ContainerDto,\r\n}\r\n\r\n#[derive(Clone, Debug, Deserialize, Serialize)]\r\n#[serde(rename_all = \"camelCase\")]\r\npub struct MonitorsData {\r\n  pub monitors: Vec<ContainerDto>,\r\n}\r\n\r\n#[derive(Clone, Debug, Deserialize, Serialize)]\r\n#[serde(rename_all = \"camelCase\")]\r\npub struct TilingDirectionData {\r\n  pub tiling_direction: TilingDirection,\r\n  pub direction_container: ContainerDto,\r\n}\r\n\r\n#[derive(Clone, Debug, Deserialize, Serialize)]\r\n#[serde(rename_all = \"camelCase\")]\r\npub struct WindowsData {\r\n  pub windows: Vec<ContainerDto>,\r\n}\r\n\r\n#[derive(Clone, Debug, Deserialize, Serialize)]\r\n#[serde(rename_all = \"camelCase\")]\r\npub struct WorkspacesData {\r\n  pub workspaces: Vec<ContainerDto>,\r\n}\r\n\r\n#[derive(Clone, Debug, Deserialize, Serialize)]\r\n#[serde(rename_all = \"camelCase\")]\r\npub struct EventSubscriptionMessage {\r\n  pub data: Option<WmEvent>,\r\n  pub error: Option<String>,\r\n  pub subscription_id: Uuid,\r\n  pub success: bool,\r\n}\r\n"
  },
  {
    "path": "packages/wm-common/src/lib.rs",
    "content": "#![warn(clippy::all, clippy::pedantic)]\r\n#![allow(clippy::missing_errors_doc)]\r\n\r\nmod active_drag;\r\nmod app_command;\r\nmod display_state;\r\nmod dtos;\r\nmod hide_corner;\r\nmod ipc;\r\nmod parsed_config;\r\nmod tiling_direction;\r\nmod utils;\r\nmod window_state;\r\nmod wm_event;\r\n\r\npub use active_drag::*;\r\npub use app_command::*;\r\npub use display_state::*;\r\npub use dtos::*;\r\npub use hide_corner::*;\r\npub use ipc::*;\r\npub use parsed_config::*;\r\npub use tiling_direction::*;\r\npub use utils::*;\r\npub use window_state::*;\r\npub use wm_event::*;\r\n"
  },
  {
    "path": "packages/wm-common/src/parsed_config.rs",
    "content": "use serde::{Deserialize, Serialize};\r\nuse wm_platform::{\r\n  Color, CornerStyle, Key, Keybinding, LengthValue, OpacityValue,\r\n  RectDelta,\r\n};\r\n\r\nuse crate::app_command::InvokeCommand;\r\n\r\n#[derive(Clone, Debug, Default, Deserialize, Serialize)]\r\n#[serde(default, rename_all(serialize = \"camelCase\"))]\r\npub struct ParsedConfig {\r\n  pub binding_modes: Vec<BindingModeConfig>,\r\n  pub gaps: GapsConfig,\r\n  pub general: GeneralConfig,\r\n  pub keybindings: Vec<KeybindingConfig>,\r\n  pub window_behavior: WindowBehaviorConfig,\r\n  pub window_effects: WindowEffectsConfig,\r\n  pub window_rules: Vec<WindowRuleConfig>,\r\n  pub workspaces: Vec<WorkspaceConfig>,\r\n}\r\n\r\n#[derive(Clone, Debug, Deserialize, Serialize)]\r\n#[serde(rename_all(serialize = \"camelCase\"))]\r\npub struct BindingModeConfig {\r\n  /// Name of the binding mode.\r\n  pub name: String,\r\n\r\n  /// Display name of the binding mode.\r\n  #[serde(default)]\r\n  pub display_name: Option<String>,\r\n\r\n  /// Keybindings that will be active when the binding mode is active.\r\n  #[serde(default)]\r\n  pub keybindings: Vec<KeybindingConfig>,\r\n}\r\n\r\n#[derive(Clone, Debug, Deserialize, Serialize)]\r\n#[serde(default, rename_all(serialize = \"camelCase\"))]\r\npub struct GapsConfig {\r\n  /// Whether to scale the gaps with the DPI of the monitor.\r\n  pub scale_with_dpi: bool,\r\n\r\n  /// Gap between adjacent windows.\r\n  pub inner_gap: LengthValue,\r\n\r\n  /// Gap between windows and the screen edge.\r\n  pub outer_gap: RectDelta,\r\n\r\n  /// Gap between window and the screen edge if there is only one window\r\n  /// in the workspace\r\n  pub single_window_outer_gap: Option<RectDelta>,\r\n}\r\n\r\nimpl Default for GapsConfig {\r\n  fn default() -> Self {\r\n    GapsConfig {\r\n      scale_with_dpi: true,\r\n      inner_gap: LengthValue::from_px(0),\r\n      outer_gap: RectDelta::new(\r\n        LengthValue::from_px(0),\r\n        LengthValue::from_px(0),\r\n        LengthValue::from_px(0),\r\n        LengthValue::from_px(0),\r\n      ),\r\n      single_window_outer_gap: None,\r\n    }\r\n  }\r\n}\r\n\r\n#[derive(Clone, Debug, Deserialize, Serialize)]\r\n#[serde(default, rename_all(serialize = \"camelCase\"))]\r\npub struct GeneralConfig {\r\n  /// Config for automatically moving the cursor.\r\n  pub cursor_jump: CursorJumpConfig,\r\n\r\n  /// Whether to automatically focus windows underneath the cursor.\r\n  pub focus_follows_cursor: bool,\r\n\r\n  /// Whether to switch back and forth between the previously focused\r\n  /// workspace when focusing the current workspace.\r\n  pub toggle_workspace_on_refocus: bool,\r\n\r\n  /// Commands to run when the WM has started (e.g. to run a script or\r\n  /// launch another application).\r\n  pub startup_commands: Vec<InvokeCommand>,\r\n\r\n  /// Commands to run just before the WM is shutdown.\r\n  pub shutdown_commands: Vec<InvokeCommand>,\r\n\r\n  /// Commands to run after the WM config has reloaded.\r\n  pub config_reload_commands: Vec<InvokeCommand>,\r\n\r\n  /// How windows should be hidden when switching workspaces.\r\n  #[serde(deserialize_with = \"deserialize_hide_method\")]\r\n  pub hide_method: HideMethod,\r\n\r\n  /// Affects which windows get shown in the native Windows taskbar.\r\n  pub show_all_in_taskbar: bool,\r\n}\r\n\r\nimpl Default for GeneralConfig {\r\n  fn default() -> Self {\r\n    GeneralConfig {\r\n      cursor_jump: CursorJumpConfig::default(),\r\n      focus_follows_cursor: false,\r\n      toggle_workspace_on_refocus: true,\r\n      startup_commands: vec![],\r\n      shutdown_commands: vec![],\r\n      config_reload_commands: vec![],\r\n      hide_method: {\r\n        #[cfg(target_os = \"macos\")]\r\n        {\r\n          HideMethod::PlaceInCorner\r\n        }\r\n        #[cfg(not(target_os = \"macos\"))]\r\n        {\r\n          HideMethod::Cloak\r\n        }\r\n      },\r\n      show_all_in_taskbar: false,\r\n    }\r\n  }\r\n}\r\n\r\n#[derive(Clone, Debug, Default, Deserialize, Serialize)]\r\n#[serde(default, rename_all(serialize = \"camelCase\"))]\r\npub struct CursorJumpConfig {\r\n  /// Whether to automatically move the cursor on the specified trigger.\r\n  pub enabled: bool,\r\n\r\n  /// Trigger for cursor jump.\r\n  pub trigger: CursorJumpTrigger,\r\n}\r\n\r\n#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]\r\n#[serde(rename_all = \"snake_case\")]\r\npub enum CursorJumpTrigger {\r\n  #[default]\r\n  MonitorFocus,\r\n  WindowFocus,\r\n}\r\n\r\n#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]\r\n#[serde(rename_all = \"snake_case\")]\r\npub enum HideMethod {\r\n  Hide,\r\n  #[default]\r\n  Cloak,\r\n  PlaceInCorner,\r\n}\r\n\r\n#[derive(Clone, Debug, Default, Deserialize, Serialize)]\r\n#[serde(default, rename_all(serialize = \"camelCase\"))]\r\npub struct KeybindingConfig {\r\n  /// Keyboard shortcut to trigger the keybinding.\r\n  #[serde(\r\n    deserialize_with = \"deserialize_bindings\",\r\n    serialize_with = \"serialize_bindings\"\r\n  )]\r\n  pub bindings: Vec<Keybinding>,\r\n\r\n  /// WM commands to run when the keybinding is triggered.\r\n  pub commands: Vec<InvokeCommand>,\r\n}\r\n\r\n#[derive(Clone, Debug, Default, Deserialize, Serialize)]\r\n#[serde(default, rename_all(serialize = \"camelCase\"))]\r\npub struct WindowBehaviorConfig {\r\n  /// New windows are created in this state whenever possible.\r\n  pub initial_state: InitialWindowState,\r\n\r\n  /// Sets the default options for when a new window is created. This also\r\n  /// changes the defaults for when the state change commands, like\r\n  /// `set_floating`, are used without any flags.\r\n  pub state_defaults: WindowStateDefaultsConfig,\r\n}\r\n\r\n#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]\r\n#[serde(rename_all = \"snake_case\")]\r\npub enum InitialWindowState {\r\n  #[default]\r\n  Tiling,\r\n  Floating,\r\n}\r\n\r\n#[derive(Clone, Debug, Default, Deserialize, Serialize)]\r\n#[serde(default, rename_all(serialize = \"camelCase\"))]\r\npub struct WindowStateDefaultsConfig {\r\n  pub floating: FloatingStateConfig,\r\n  pub fullscreen: FullscreenStateConfig,\r\n}\r\n\r\n#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]\r\n#[serde(default, rename_all(serialize = \"camelCase\"))]\r\npub struct FloatingStateConfig {\r\n  /// Whether to center new floating windows.\r\n  pub centered: bool,\r\n\r\n  /// Whether to show floating windows as always on top.\r\n  pub shown_on_top: bool,\r\n}\r\n\r\nimpl Default for FloatingStateConfig {\r\n  fn default() -> Self {\r\n    FloatingStateConfig {\r\n      centered: true,\r\n      shown_on_top: false,\r\n    }\r\n  }\r\n}\r\n\r\n#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]\r\n#[serde(default, rename_all(serialize = \"camelCase\"))]\r\npub struct FullscreenStateConfig {\r\n  /// Whether to prefer fullscreen windows to be maximized.\r\n  pub maximized: bool,\r\n\r\n  /// Whether to show fullscreen windows as always on top.\r\n  pub shown_on_top: bool,\r\n}\r\n\r\nimpl Default for FullscreenStateConfig {\r\n  fn default() -> Self {\r\n    FullscreenStateConfig {\r\n      maximized: true,\r\n      shown_on_top: false,\r\n    }\r\n  }\r\n}\r\n\r\n#[derive(Clone, Debug, Default, Deserialize, Serialize)]\r\n#[serde(default, rename_all(serialize = \"camelCase\"))]\r\npub struct WindowEffectsConfig {\r\n  /// Visual effects to apply to the focused window.\r\n  pub focused_window: WindowEffectConfig,\r\n\r\n  /// Visual effects to apply to non-focused windows.\r\n  pub other_windows: WindowEffectConfig,\r\n}\r\n\r\n#[derive(Clone, Debug, Default, Deserialize, Serialize)]\r\n#[serde(default, rename_all(serialize = \"camelCase\"))]\r\npub struct WindowEffectConfig {\r\n  /// Config for optionally applying a colored border.\r\n  pub border: BorderEffectConfig,\r\n\r\n  /// Config for optionally hiding the title bar.\r\n  pub hide_title_bar: HideTitleBarEffectConfig,\r\n\r\n  /// Config for optionally changing the corner style.\r\n  pub corner_style: CornerEffectConfig,\r\n\r\n  /// Config for optionally applying transparency.\r\n  pub transparency: TransparencyEffectConfig,\r\n}\r\n\r\n#[derive(Clone, Debug, Deserialize, Serialize)]\r\n#[serde(default, rename_all(serialize = \"camelCase\"))]\r\npub struct BorderEffectConfig {\r\n  /// Whether to enable the effect.\r\n  pub enabled: bool,\r\n\r\n  /// Color of the window border.\r\n  pub color: Color,\r\n}\r\n\r\nimpl Default for BorderEffectConfig {\r\n  fn default() -> Self {\r\n    BorderEffectConfig {\r\n      enabled: false,\r\n      color: Color {\r\n        r: 140,\r\n        g: 190,\r\n        b: 255,\r\n        a: 255,\r\n      },\r\n    }\r\n  }\r\n}\r\n\r\n#[derive(Clone, Debug, Default, Deserialize, Serialize)]\r\n#[serde(default, rename_all(serialize = \"camelCase\"))]\r\npub struct HideTitleBarEffectConfig {\r\n  /// Whether to enable the effect.\r\n  pub enabled: bool,\r\n}\r\n\r\n#[derive(Clone, Debug, Default, Deserialize, Serialize)]\r\n#[serde(default, rename_all(serialize = \"camelCase\"))]\r\npub struct CornerEffectConfig {\r\n  /// Whether to enable the effect.\r\n  pub enabled: bool,\r\n\r\n  /// Style of the window corners.\r\n  pub style: CornerStyle,\r\n}\r\n\r\n#[derive(Clone, Debug, Default, Deserialize, Serialize)]\r\n#[serde(default, rename_all(serialize = \"camelCase\"))]\r\npub struct TransparencyEffectConfig {\r\n  /// Whether to enable the effect.\r\n  pub enabled: bool,\r\n\r\n  /// The opacity to apply.\r\n  pub opacity: OpacityValue,\r\n}\r\n\r\n#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]\r\n#[serde(rename_all(serialize = \"camelCase\"))]\r\npub struct WindowRuleConfig {\r\n  pub commands: Vec<InvokeCommand>,\r\n\r\n  #[serde(rename = \"match\")]\r\n  pub match_window: Vec<WindowMatchConfig>,\r\n\r\n  #[serde(default = \"default_window_rule_on\")]\r\n  pub on: Vec<WindowRuleEvent>,\r\n\r\n  #[serde(default = \"default_bool::<true>\")]\r\n  pub run_once: bool,\r\n}\r\n\r\n#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]\r\n#[serde(default, rename_all(serialize = \"camelCase\"))]\r\npub struct WindowMatchConfig {\r\n  pub window_process: Option<MatchType>,\r\n  pub window_class: Option<MatchType>,\r\n  pub window_title: Option<MatchType>,\r\n}\r\n\r\n/// Due to limitations in `serde_yaml`, we need to use an untagged enum\r\n/// instead of a regular enum for serialization. Using a regular enum\r\n/// causes issues with flow-style objects in YAML.\r\n#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]\r\n#[serde(untagged)]\r\npub enum MatchType {\r\n  Equals { equals: String },\r\n  Includes { includes: String },\r\n  Regex { regex: String },\r\n  NotEquals { not_equals: String },\r\n  NotRegex { not_regex: String },\r\n}\r\n\r\nimpl MatchType {\r\n  /// Whether the given value is a match for the match type.\r\n  #[must_use]\r\n  pub fn is_match(&self, value: &str) -> bool {\r\n    match self {\r\n      MatchType::Equals { equals } => value == equals,\r\n      MatchType::Includes { includes } => value.contains(includes),\r\n      MatchType::Regex { regex } => {\r\n        regex::Regex::new(regex).is_ok_and(|re| re.is_match(value))\r\n      }\r\n      MatchType::NotEquals { not_equals } => value != not_equals,\r\n      MatchType::NotRegex { not_regex } => {\r\n        regex::Regex::new(not_regex).is_ok_and(|re| !re.is_match(value))\r\n      }\r\n    }\r\n  }\r\n}\r\n\r\n#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]\r\n#[serde(rename_all = \"snake_case\")]\r\npub enum WindowRuleEvent {\r\n  /// When a window receives native focus.\r\n  Focus,\r\n\r\n  /// When a window is initially managed.\r\n  Manage,\r\n\r\n  /// When the title of a window changes.\r\n  TitleChange,\r\n}\r\n\r\n#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]\r\n#[serde(rename_all(serialize = \"camelCase\"))]\r\npub struct WorkspaceConfig {\r\n  pub name: String,\r\n\r\n  #[serde(default)]\r\n  pub display_name: Option<String>,\r\n\r\n  #[serde(default)]\r\n  pub bind_to_monitor: Option<u32>,\r\n\r\n  #[serde(default = \"default_bool::<false>\")]\r\n  pub keep_alive: bool,\r\n}\r\n\r\n/// Helper function for setting a default value for a boolean field.\r\nconst fn default_bool<const V: bool>() -> bool {\r\n  V\r\n}\r\n\r\n/// Helper function for setting a default value for window rule events.\r\nfn default_window_rule_on() -> Vec<WindowRuleEvent> {\r\n  vec![WindowRuleEvent::Manage, WindowRuleEvent::TitleChange]\r\n}\r\n\r\n/// Helper function for serializing a vector of keybindings.\r\n///\r\n/// Returns a vector of strings (e.g. `[\"cmd+shift+a\", \"ctrl+shift+b\"]`).\r\nfn serialize_bindings<S>(\r\n  bindings: &[Keybinding],\r\n  serializer: S,\r\n) -> Result<S::Ok, S::Error>\r\nwhere\r\n  S: serde::Serializer,\r\n{\r\n  let binding_strings: Vec<String> = bindings\r\n    .iter()\r\n    .map(|binding| {\r\n      binding\r\n        .keys()\r\n        .iter()\r\n        .map(|key| key.to_string().to_lowercase())\r\n        .collect::<Vec<_>>()\r\n        .join(\"+\")\r\n    })\r\n    .collect();\r\n\r\n  binding_strings.serialize(serializer)\r\n}\r\n\r\n/// Helper function for deserializing a vector of strings into keybindings.\r\n///\r\n/// Returns a vector of [`Keybinding`].\r\nfn deserialize_bindings<'de, D>(\r\n  deserializer: D,\r\n) -> Result<Vec<Keybinding>, D::Error>\r\nwhere\r\n  D: serde::de::Deserializer<'de>,\r\n{\r\n  let s: Vec<&str> = serde::de::Deserialize::deserialize(deserializer)?;\r\n  s.iter()\r\n    .map(|keybinding_str| {\r\n      let keys: Vec<Key> = keybinding_str\r\n        .split('+')\r\n        .map(|key| {\r\n          key.trim().parse().or_else(|_| Key::try_from_literal(key))\r\n        })\r\n        .collect::<Result<Vec<Key>, _>>()\r\n        .map_err(serde::de::Error::custom)?;\r\n\r\n      Keybinding::new(keys).map_err(serde::de::Error::custom)\r\n    })\r\n    .collect()\r\n}\r\n\r\n/// Helper function for deserializing [`HideMethod`].\r\n///\r\n/// On macOS, [`HideMethod::Hide`] and [`HideMethod::Cloak`] are not valid\r\n/// and are automatically converted to [`HideMethod::PlaceInCorner`].\r\nfn deserialize_hide_method<'de, D>(\r\n  deserializer: D,\r\n) -> Result<HideMethod, D::Error>\r\nwhere\r\n  D: serde::de::Deserializer<'de>,\r\n{\r\n  // LINT: The deserialized value is ignored on macOS, but we still want\r\n  // to produce an error for invalid values.\r\n  #[allow(unused_variables)]\r\n  let method = HideMethod::deserialize(deserializer)?;\r\n\r\n  #[cfg(target_os = \"macos\")]\r\n  {\r\n    Ok(HideMethod::PlaceInCorner)\r\n  }\r\n\r\n  #[cfg(not(target_os = \"macos\"))]\r\n  {\r\n    Ok(method)\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-common/src/tiling_direction.rs",
    "content": "use std::str::FromStr;\r\n\r\nuse anyhow::bail;\r\nuse serde::{Deserialize, Serialize};\r\nuse wm_platform::Direction;\r\n\r\n#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]\r\n#[serde(rename_all = \"snake_case\")]\r\npub enum TilingDirection {\r\n  Horizontal,\r\n  Vertical,\r\n}\r\n\r\nimpl TilingDirection {\r\n  /// Gets the inverse of a given tiling direction.\r\n  ///\r\n  /// Example:\r\n  /// ```\r\n  /// # use wm_platform::TilingDirection;\r\n  /// let dir = TilingDirection::Horizontal.inverse();\r\n  /// assert_eq!(dir, TilingDirection::Vertical);\r\n  /// ```\r\n  #[must_use]\r\n  pub fn inverse(&self) -> Self {\r\n    match self {\r\n      Self::Horizontal => Self::Vertical,\r\n      Self::Vertical => Self::Horizontal,\r\n    }\r\n  }\r\n\r\n  /// Gets the tiling direction that is needed when moving or shifting\r\n  /// focus in a given direction.\r\n  ///\r\n  /// Example:\r\n  /// ```\r\n  /// # use wm_platform::{Direction, TilingDirection};\r\n  /// let dir = TilingDirection::from_direction(&Direction::Left);\r\n  /// assert_eq!(dir, TilingDirection::Horizontal);\r\n  /// ```\r\n  #[must_use]\r\n  pub fn from_direction(direction: &Direction) -> Self {\r\n    match direction {\r\n      Direction::Left | Direction::Right => Self::Horizontal,\r\n      Direction::Up | Direction::Down => Self::Vertical,\r\n    }\r\n  }\r\n}\r\n\r\nimpl FromStr for TilingDirection {\r\n  type Err = anyhow::Error;\r\n\r\n  /// Parses a string into a tiling direction.\r\n  ///\r\n  /// Example:\r\n  /// ```\r\n  /// # use wm_platform::TilingDirection;\r\n  /// # use std::str::FromStr;\r\n  /// let dir = TilingDirection::from_str(\"horizontal\");\r\n  /// assert_eq!(dir.unwrap(), TilingDirection::Horizontal);\r\n  ///\r\n  /// let dir = TilingDirection::from_str(\"vertical\");\r\n  /// assert_eq!(dir.unwrap(), TilingDirection::Vertical);\r\n  /// ```\r\n  fn from_str(unparsed: &str) -> anyhow::Result<Self> {\r\n    match unparsed {\r\n      \"horizontal\" => Ok(Self::Horizontal),\r\n      \"vertical\" => Ok(Self::Vertical),\r\n      _ => bail!(\"Not a valid tiling direction: {}\", unparsed),\r\n    }\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-common/src/utils/iterator_ext.rs",
    "content": "use std::{collections::HashSet, hash::Hash};\r\n\r\n/// Extension trait for iterators.\r\npub trait UniqueExt: Iterator {\r\n  /// Returns an iterator that yields unique elements based on the key\r\n  /// function.\r\n  ///\r\n  /// The key function must return a value that implements `Hash` and `Eq`.\r\n  fn unique_by<K, F>(self, key_fn: F) -> UniqueBy<Self, K, F>\r\n  where\r\n    Self: Sized,\r\n    K: Hash + Eq,\r\n    F: FnMut(&Self::Item) -> K;\r\n}\r\n\r\npub struct UniqueBy<I: Iterator, K, F> {\r\n  iter: I,\r\n  key_fn: F,\r\n  seen: HashSet<K>,\r\n}\r\n\r\n// Implement the `Iterator` trait for the `UniqueBy` struct.\r\nimpl<I, K, F> Iterator for UniqueBy<I, K, F>\r\nwhere\r\n  I: Iterator,\r\n  K: Hash + Eq,\r\n  F: FnMut(&I::Item) -> K,\r\n{\r\n  type Item = I::Item;\r\n\r\n  fn next(&mut self) -> Option<Self::Item> {\r\n    for item in self.iter.by_ref() {\r\n      let key = (self.key_fn)(&item);\r\n      if self.seen.insert(key) {\r\n        return Some(item);\r\n      }\r\n    }\r\n\r\n    None\r\n  }\r\n}\r\n\r\n// Implement the extension trait for all iterators.\r\nimpl<I: Iterator> UniqueExt for I {\r\n  fn unique_by<K, F>(self, key_fn: F) -> UniqueBy<Self, K, F>\r\n  where\r\n    Self: Sized,\r\n    K: Hash + Eq,\r\n    F: FnMut(&Self::Item) -> K,\r\n  {\r\n    UniqueBy {\r\n      iter: self,\r\n      key_fn,\r\n      seen: HashSet::new(),\r\n    }\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-common/src/utils/mod.rs",
    "content": "mod iterator_ext;\r\nmod try_warn;\r\nmod vec_deque_ext;\r\n\r\npub use iterator_ext::*;\r\npub use vec_deque_ext::*;\r\n"
  },
  {
    "path": "packages/wm-common/src/utils/try_warn.rs",
    "content": "/// Utility macro that logs a warning and returns early if the given\r\n/// expression is an error.\r\n#[macro_export]\r\nmacro_rules! try_warn {\r\n  ($expr:expr) => {\r\n    match $expr {\r\n      Ok(val) => val,\r\n      Err(err) => {\r\n        tracing::warn!(\"Operation failed: {:?}\", err);\r\n        return Ok(());\r\n      }\r\n    }\r\n  };\r\n}\r\n"
  },
  {
    "path": "packages/wm-common/src/utils/vec_deque_ext.rs",
    "content": "use std::collections::VecDeque;\r\n\r\npub trait VecDequeExt<T>\r\nwhere\r\n  T: PartialEq,\r\n{\r\n  /// Shifts a value to a specified index in a `VecDeque`.\r\n  ///\r\n  /// Inserts at index if value doesn't already exist in the `VecDeque`.\r\n  fn shift_to_index(&mut self, target_index: usize, item: T);\r\n}\r\n\r\nimpl<T> VecDequeExt<T> for VecDeque<T>\r\nwhere\r\n  T: PartialEq,\r\n{\r\n  fn shift_to_index(&mut self, target_index: usize, value: T) {\r\n    if let Some(index) = self.iter().position(|e| e == &value) {\r\n      self.remove(index);\r\n\r\n      // Adjust for when the target index becomes out of bounds because of\r\n      // the removal above.\r\n      self.insert(target_index.clamp(0, self.len()), value);\r\n    }\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-common/src/window_state.rs",
    "content": "use serde::{Deserialize, Serialize};\r\n\r\nuse crate::{\r\n  parsed_config::{\r\n    FloatingStateConfig, FullscreenStateConfig, InitialWindowState,\r\n  },\r\n  ParsedConfig,\r\n};\r\n\r\n/// Represents the possible states a window can have.\r\n#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]\r\n#[serde(tag = \"type\", rename_all = \"snake_case\")]\r\npub enum WindowState {\r\n  Floating(FloatingStateConfig),\r\n  Fullscreen(FullscreenStateConfig),\r\n  Minimized,\r\n  Tiling,\r\n}\r\n\r\nimpl WindowState {\r\n  #[must_use]\r\n  pub fn default_from_config(config: &ParsedConfig) -> Self {\r\n    match config.window_behavior.initial_state {\r\n      InitialWindowState::Tiling => Self::Tiling,\r\n      InitialWindowState::Floating => Self::Floating(\r\n        config.window_behavior.state_defaults.floating.clone(),\r\n      ),\r\n    }\r\n  }\r\n\r\n  #[must_use]\r\n  pub fn is_same_state(&self, other: &Self) -> bool {\r\n    std::mem::discriminant(self) == std::mem::discriminant(other)\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-common/src/wm_event.rs",
    "content": "use serde::{Deserialize, Serialize};\r\nuse uuid::Uuid;\r\n\r\nuse crate::{\r\n  dtos::ContainerDto,\r\n  parsed_config::{BindingModeConfig, ParsedConfig},\r\n  TilingDirection,\r\n};\r\n\r\n#[derive(Clone, Debug, Deserialize, Serialize)]\r\n#[serde(\r\n  tag = \"eventType\",\r\n  rename_all = \"snake_case\",\r\n  rename_all_fields = \"camelCase\"\r\n)]\r\npub enum WmEvent {\r\n  ApplicationExiting,\r\n  BindingModesChanged {\r\n    new_binding_modes: Vec<BindingModeConfig>,\r\n  },\r\n  FocusChanged {\r\n    focused_container: ContainerDto,\r\n  },\r\n  FocusedContainerMoved {\r\n    focused_container: ContainerDto,\r\n  },\r\n  MonitorAdded {\r\n    added_monitor: ContainerDto,\r\n  },\r\n  MonitorRemoved {\r\n    removed_id: Uuid,\r\n    removed_device_name: String,\r\n  },\r\n  MonitorUpdated {\r\n    updated_monitor: ContainerDto,\r\n  },\r\n  TilingDirectionChanged {\r\n    direction_container: ContainerDto,\r\n    new_tiling_direction: TilingDirection,\r\n  },\r\n  UserConfigChanged {\r\n    config_path: String,\r\n    config_string: String,\r\n    parsed_config: ParsedConfig,\r\n  },\r\n  WindowManaged {\r\n    managed_window: ContainerDto,\r\n  },\r\n  WindowUnmanaged {\r\n    unmanaged_id: Uuid,\r\n    unmanaged_handle: isize,\r\n  },\r\n  WorkspaceActivated {\r\n    activated_workspace: ContainerDto,\r\n  },\r\n  WorkspaceDeactivated {\r\n    deactivated_id: Uuid,\r\n    deactivated_name: String,\r\n  },\r\n  WorkspaceUpdated {\r\n    updated_workspace: ContainerDto,\r\n  },\r\n  PauseChanged {\r\n    is_paused: bool,\r\n  },\r\n}\r\n"
  },
  {
    "path": "packages/wm-ipc-client/Cargo.toml",
    "content": "[package]\r\nname = \"wm-ipc-client\"\r\nversion = \"0.0.0\"\r\nedition = \"2021\"\r\n\r\n[lib]\r\npath = \"src/lib.rs\"\r\n\r\n[dependencies]\r\nanyhow = { workspace = true }\r\nfutures-util = { workspace = true }\r\nserde_json = { workspace = true }\r\ntokio = { workspace = true }\r\ntokio-tungstenite = { workspace = true }\r\nuuid = { workspace = true }\r\nwm-common = { path = \"../wm-common\" }\r\n"
  },
  {
    "path": "packages/wm-ipc-client/src/lib.rs",
    "content": "#![allow(clippy::missing_errors_doc)]\r\n\r\nuse anyhow::Context;\r\nuse futures_util::{SinkExt, StreamExt};\r\nuse tokio::net::TcpStream;\r\nuse tokio_tungstenite::{\r\n  connect_async, tungstenite::Message, MaybeTlsStream, WebSocketStream,\r\n};\r\nuse uuid::Uuid;\r\nuse wm_common::{\r\n  ClientResponseMessage, EventSubscriptionMessage, ServerMessage,\r\n  DEFAULT_IPC_PORT,\r\n};\r\n\r\npub struct IpcClient {\r\n  stream: WebSocketStream<MaybeTlsStream<TcpStream>>,\r\n}\r\n\r\nimpl IpcClient {\r\n  pub async fn connect() -> anyhow::Result<Self> {\r\n    let server_addr = format!(\"ws://127.0.0.1:{DEFAULT_IPC_PORT}\");\r\n\r\n    let (stream, _) = connect_async(server_addr)\r\n      .await\r\n      .context(\"Failed to connect to IPC server.\")?;\r\n\r\n    Ok(Self { stream })\r\n  }\r\n\r\n  /// Sends a message to the IPC server.\r\n  pub async fn send(&mut self, message: &str) -> anyhow::Result<()> {\r\n    self\r\n      .stream\r\n      .send(Message::Text(message.into()))\r\n      .await\r\n      .context(\"Failed to send command.\")?;\r\n\r\n    Ok(())\r\n  }\r\n\r\n  /// Waits and returns the next reply from the IPC server.\r\n  pub async fn next_message(&mut self) -> anyhow::Result<ServerMessage> {\r\n    let response = self\r\n      .stream\r\n      .next()\r\n      .await\r\n      .context(\"Failed to receive response.\")?\r\n      .context(\"Invalid response message.\")?;\r\n\r\n    let json_response =\r\n      serde_json::from_str::<ServerMessage>(response.to_text()?)?;\r\n\r\n    Ok(json_response)\r\n  }\r\n\r\n  pub async fn client_response(\r\n    &mut self,\r\n    client_message: &str,\r\n  ) -> Option<ClientResponseMessage> {\r\n    while let Ok(response) = self.next_message().await {\r\n      if let ServerMessage::ClientResponse(client_response) = response {\r\n        if client_response.client_message == client_message {\r\n          return Some(client_response);\r\n        }\r\n      }\r\n    }\r\n\r\n    None\r\n  }\r\n\r\n  pub async fn event_subscription(\r\n    &mut self,\r\n    subscription_id: &Uuid,\r\n  ) -> Option<EventSubscriptionMessage> {\r\n    while let Ok(response) = self.next_message().await {\r\n      if let ServerMessage::EventSubscription(event_sub) = response {\r\n        if &event_sub.subscription_id == subscription_id {\r\n          return Some(event_sub);\r\n        }\r\n      }\r\n    }\r\n\r\n    None\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-macros/Cargo.toml",
    "content": "[package]\r\nname = \"wm-macros\"\r\nversion = \"0.1.0\"\r\nedition = \"2024\"\r\n\r\n[lib]\r\nproc-macro = true\r\n\r\n[dependencies]\r\nsyn = \"2.0.103\"\r\nquote = \"1.0\"\r\nproc-macro2 = \"1.0\"\r\n"
  },
  {
    "path": "packages/wm-macros/src/common/attributes.rs",
    "content": "//! Utilities for working with [syn::Attribute]\r\n\r\npub mod prelude {\r\n  pub use super::FindAttributes;\r\n}\r\n\r\n/// Trait for filtering lists of [syn::Attribute] by name and type.\r\npub trait FindAttributes {\r\n  /// Find attributes by name. E.g. `#[name]`, `#[name(...)]` or `#[name =\r\n  /// <value>]`\r\n  fn find_attrs(\r\n    &self,\r\n    name: &str,\r\n  ) -> impl Iterator<Item = &syn::Attribute>;\r\n  /// Find list attributes by name. E.g. `#[name(...)]`\r\n  fn find_list_attrs(\r\n    &self,\r\n    name: &str,\r\n  ) -> impl Iterator<Item = &syn::MetaList> {\r\n    self\r\n      .find_attrs(name)\r\n      .filter_map(|attr| attr.meta.require_list().ok())\r\n  }\r\n}\r\n\r\nimpl FindAttributes for Vec<syn::Attribute> {\r\n  fn find_attrs(\r\n    &self,\r\n    name: &str,\r\n  ) -> impl Iterator<Item = &syn::Attribute> {\r\n    self.iter().filter(move |attr| {\r\n      attr.path().get_ident().is_some_and(|ident| ident == name)\r\n    })\r\n  }\r\n}\r\n\r\nimpl FindAttributes for &[syn::Attribute] {\r\n  fn find_attrs(\r\n    &self,\r\n    name: &str,\r\n  ) -> impl Iterator<Item = &syn::Attribute> {\r\n    self.iter().filter(move |attr| {\r\n      attr.path().get_ident().is_some_and(|ident| ident == name)\r\n    })\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-macros/src/common/branch.rs",
    "content": "//! Types and traits for branching and combining parsing operations.\r\n//! Implements functionality similar to `nom`s alternatives and\r\n//! combinators, but for `syn::parse::ParseStream`.\r\n\r\nuse crate::prelude::*;\r\n\r\n/// Trait for tuples where all items can be parsed from a\r\n/// parse stream.\r\npub trait ParseableTuple\r\nwhere\r\n  Self: Sized,\r\n{\r\n  type FirstItem: syn::parse::Parse;\r\n\r\n  /// Parses all items in the tuple `T` from the stream in the order they\r\n  /// appear in the tuple, and parses `Sep` in between each item. Returns\r\n  /// all parsed items in a tuple, or the first error to occur.\r\n  ///\r\n  /// Do not use this directly, use the [Ordered] type instead,\r\n  fn parse_tuple<Sep>(\r\n    stream: syn::parse::ParseStream,\r\n  ) -> syn::Result<Self>\r\n  where\r\n    Sep: syn::parse::Parse;\r\n}\r\n\r\n/// Trait for tuples where all items can be peeked and parsed from a parse\r\n/// stream.\r\n// Used with [Unordered]\r\n#[allow(dead_code)]\r\npub trait PeekableTuple\r\nwhere\r\n  Self: Sized,\r\n{\r\n  /// Iterates until all items in the tuple `T` have been parsed, or an\r\n  /// error occurs. Parsing is attempted in the order of the items in\r\n  /// the tuple, although if an item is not found, it may be skipped and\r\n  /// reattempted for the next item(s).\r\n  ///\r\n  /// Do not use this directly, use the [Unordered] type instead.\r\n  fn peek_parse_tuple<Sep>(\r\n    stream: syn::parse::ParseStream,\r\n  ) -> syn::Result<Self>\r\n  where\r\n    Sep: syn::parse::Parse + crate::common::peekable::Peekable;\r\n}\r\n\r\nmacro_rules! replace_expr {\r\n  ($idc:expr, $sub:expr) => {\r\n    $sub\r\n  };\r\n}\r\n\r\nmacro_rules! get_first_item {\r\n  ($first:tt, $($types:tt),+) => {\r\n    $first\r\n  };\r\n}\r\n\r\nmacro_rules! impl_for_tuple {\r\n  ($($types:tt),+ | $($numbers:tt),+) => {\r\n    // Generic ensures that all types in the tuple implement `syn::parse::Parse`.\r\n    impl<$($types),+> ParseableTuple for ($($types,)+) where $($types : syn::parse::Parse),+ {\r\n      type FirstItem = get_first_item!($($types),+);\r\n\r\n      fn parse_tuple<Sep>(stream: syn::parse::ParseStream) -> syn::Result<Self> where Sep: syn::parse::Parse {\r\n        $(\r\n          // Also check if the stream is empty to allow for missing trailing Optional items\r\n          if $numbers != 0 && !stream.is_empty() {\r\n            stream.parse::<Sep>()?;\r\n          }\r\n\r\n          #[allow(non_snake_case)]\r\n          let $types = stream.parse::<$types>()?;\r\n        )+\r\n\r\n        // Pack the parsed items into a tuple\r\n        Ok(($($types),+))\r\n      }\r\n    }\r\n\r\n    // Generic ensures that all types in the tuple implement `syn::parse::Parse` and `Peekable`.\r\n    impl<$($types),+> PeekableTuple for ($($types,)+) where $($types : syn::parse::Parse + crate::common::peekable::Peekable),+{\r\n\r\n      fn peek_parse_tuple<Sep>(stream: syn::parse::ParseStream) -> syn::Result<Self>\r\n        where Sep: syn::parse::Parse + crate::common::peekable::Peekable\r\n      {\r\n        use crate::common::peekable::prelude::*;\r\n\r\n        // Creates a tuple with the same number of items as the tuple, but with each item being\r\n        // `None`.\r\n        let mut output: ($(Option<$types>,)+) = ($(replace_expr!($types, None),)+);\r\n\r\n        // Iterate while any of the items in the tuple are `None`.\r\n        while $(output.$numbers.is_none())||+ {\r\n          let lookahead = stream.lookahead1();\r\n\r\n          $(\r\n            if output.$numbers.is_none() && lookahead.tpeek::<$types>() {\r\n\r\n              output.$numbers = Some(stream.parse::<$types>()?);\r\n            }\r\n            // Insert an else before the next item in the tuple, to create `else if` on subsequent\r\n            // unpacking\r\n            )else+\r\n            else {\r\n              return Err(lookahead.error());\r\n            }\r\n\r\n          // If we havn't yet parsed all items in the tuple, parse the separator.\r\n          // Also check if the stream is empty, which allows missing Optional items to be skipped\r\n          // TODO: Better handling of [Optional] items, so that we can handle them without needing\r\n          // the stream to end.\r\n          if !stream.is_empty() && $(output.$numbers.is_none())||+ {\r\n            stream.parse::<Sep>()?;\r\n          }\r\n        }\r\n\r\n        Ok(($(\r\n        // Saftey, the output is guaranteed to have all items otherwise the loop would have errored\r\n        // out.\r\n          output.$numbers.unwrap(),\r\n          )+))\r\n      }\r\n    }\r\n  };\r\n}\r\n\r\n// Implement the `ParseableTuple` and `PeekableTuple` traits for tuples of\r\n// different sizes.\r\nimpl_for_tuple!(T, U | 0, 1);\r\nimpl_for_tuple!(T, U, V | 0, 1, 2);\r\nimpl_for_tuple!(T, U, V, W | 0, 1, 2, 3);\r\nimpl_for_tuple!(T, U, V, W, X | 0, 1, 2, 3, 4);\r\nimpl_for_tuple!(T, U, V, W, X, Y | 0, 1, 2, 3, 4, 5);\r\n\r\n/// Type wrapper to parse all items in tuple `T` in order, using\r\n/// `Sep` as the separator between items.\r\n///\r\n/// # Example\r\n/// Parse [syn::Ident] and [syn::LitStr] from the stream, which are\r\n/// separated by a comma. E.g. `some_name, \"some string\"`. If the order is\r\n/// reversed, it will fail to parse.\r\n/// ```\r\n/// fn example(stream: syn::parse::ParseStream) -> syn::Result<()> {\r\n///   type T = (syn::Ident, syn::LitStr);\r\n///\r\n///   stream.parse::<Ordered<T, syn::Token![,]>>()?;\r\n/// }\r\n///\r\n/// fn main() {\r\n///   # use quote::quote;\r\n///   let tokens = quote! { some_name, \"some string\" }.into();\r\n///   let error = quote! { \"some string\", some_name }.into();\r\n///\r\n///   assert!(example(tokens).is_ok());\r\n///   assert!(example(error).is_err());\r\n/// }\r\n/// ```\r\npub struct Ordered<T, Sep>\r\nwhere\r\n  T: ParseableTuple,\r\n  Sep: syn::parse::Parse,\r\n{\r\n  pub items: T,\r\n  sep: std::marker::PhantomData<Sep>,\r\n}\r\n\r\nimpl<T, Sep> syn::parse::Parse for Ordered<T, Sep>\r\nwhere\r\n  T: ParseableTuple,\r\n  Sep: syn::parse::Parse,\r\n{\r\n  fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {\r\n    let output = T::parse_tuple::<Sep>(input)? as T;\r\n    Ok(Self {\r\n      items: output,\r\n      sep: std::marker::PhantomData,\r\n    })\r\n  }\r\n}\r\n\r\n/// Implement [Peekable] for [Ordered] if the first item in the\r\n/// tuple is peekable, by forwarding the implementation to the first item.\r\nimpl<T, FirstItem, Sep> crate::common::peekable::Peekable\r\n  for Ordered<T, Sep>\r\nwhere\r\n  T: ParseableTuple<FirstItem = FirstItem>,\r\n  Sep: syn::parse::Parse,\r\n  FirstItem: crate::common::peekable::Peekable,\r\n{\r\n  fn display() -> &'static str {\r\n    FirstItem::display()\r\n  }\r\n\r\n  fn peek<S>(stream: S) -> bool\r\n  where\r\n    S: crate::common::peekable::PeekableStream,\r\n  {\r\n    FirstItem::peek(stream)\r\n  }\r\n}\r\n\r\nimpl<T, Sep> core::ops::Deref for Ordered<T, Sep>\r\nwhere\r\n  T: ParseableTuple,\r\n  Sep: syn::parse::Parse,\r\n{\r\n  type Target = T;\r\n\r\n  fn deref(&self) -> &Self::Target {\r\n    &self.items\r\n  }\r\n}\r\n\r\n/// Type wrapper to parse all items in tuple `T` in any order, using `Sep`\r\n/// as the separator between items.\r\n///\r\n/// # Example\r\n/// Parse [syn::Ident] and [syn::LitStr] from the stream in any order,\r\n/// which are separated by a comma. E.g. `some_name, \"some string\"` or\r\n/// `\"some string\", some_name`.\r\n/// ```\r\n/// fn example(stream: proc_macro::TokenStream) -> syn::Result<(syn::Ident, syn::LitStr)> {\r\n///   type T = (syn::Ident, syn::LitStr);\r\n///\r\n///   stream.parse2::<Unordered<T, syn::Token![,]>>().map(|Unordered(t, _)| t)\r\n/// }\r\n///\r\n/// fn main() {\r\n///   # use quote::quote;\r\n///   let normal = quote! { some_name, \"some string\" }.into();\r\n///   let reversed = quote! { \"some string\", some_name }.into();\r\n///   let error = quote! {some_name, other_name}.into();\r\n///\r\n///   assert!(example(normal).is_ok());\r\n///   assert!(example(reversed).is_ok());\r\n///   assert!(example(error).is_err());\r\n/// }\r\n/// ```\r\n// Util function that will be used in future.\r\n#[allow(dead_code)]\r\npub struct Unordered<T, Sep>\r\nwhere\r\n  T: PeekableTuple,\r\n  Sep: syn::parse::Parse + crate::common::peekable::Peekable,\r\n{\r\n  pub items: T,\r\n  sep: std::marker::PhantomData<Sep>,\r\n}\r\n\r\nimpl<T, Sep> syn::parse::Parse for Unordered<T, Sep>\r\nwhere\r\n  T: PeekableTuple,\r\n  Sep: syn::parse::Parse + crate::common::peekable::Peekable,\r\n{\r\n  fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {\r\n    let output = T::peek_parse_tuple::<Sep>(input)? as T;\r\n    Ok(Self {\r\n      items: output,\r\n      sep: std::marker::PhantomData,\r\n    })\r\n  }\r\n}\r\n\r\nimpl<T, Sep> core::ops::Deref for Unordered<T, Sep>\r\nwhere\r\n  T: PeekableTuple,\r\n  Sep: syn::parse::Parse + crate::common::peekable::Peekable,\r\n{\r\n  type Target = T;\r\n\r\n  fn deref(&self) -> &Self::Target {\r\n    &self.items\r\n  }\r\n}\r\n\r\n/// Type wrapper to parse `If` if it is present, otherwise parse `Else`.\r\n/// `If` must be peekable so it can be checked if it is present before\r\n/// parsing.\r\n///\r\n/// # Example\r\n/// Parse [syn::Ident] if it is present, otherwise parse [syn::LitStr].\r\n/// ```\r\n/// type IfElseType = IfElse<syn::Ident, syn::LitStr>;\r\n///\r\n/// fn example(stream: syn::parse::ParseStream) -> syn::Result<IfElseType> {\r\n///   stream.parse::<IfElseType>()\r\n/// }\r\n///\r\n/// fn main() {\r\n///   # use quote::quote;\r\n///   let if_tokens = quote! { some_name }.into();\r\n///   let else_tokens = quote! { \"some string\" }.into();\r\n///\r\n///   assert!(example(if_tokens).is_ok_and(|if_else| if_else.is_if()));\r\n///   assert!(example(else_tokens).is_ok_and(|if_else| if_else.is_else()));\r\n/// }\r\npub enum IfElse<If, Else>\r\nwhere\r\n  If: syn::parse::Parse + crate::common::peekable::Peekable,\r\n  Else: syn::parse::Parse,\r\n{\r\n  If(If),\r\n  Else(Else),\r\n}\r\n\r\n// Methods are used in doc tests, but linter doesn't pick that up.\r\n#[allow(dead_code)]\r\nimpl<If, Else> IfElse<If, Else>\r\nwhere\r\n  If: syn::parse::Parse + crate::common::peekable::Peekable,\r\n  Else: syn::parse::Parse,\r\n{\r\n  pub fn is_if(&self) -> bool {\r\n    matches!(self, Self::If(_))\r\n  }\r\n\r\n  pub fn is_else(&self) -> bool {\r\n    matches!(self, Self::Else(_))\r\n  }\r\n}\r\n\r\nimpl<If, Else> syn::parse::Parse for IfElse<If, Else>\r\nwhere\r\n  If: syn::parse::Parse + crate::common::peekable::Peekable,\r\n  Else: syn::parse::Parse,\r\n{\r\n  fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {\r\n    if input.tpeek::<If>() {\r\n      Ok(Self::If(input.parse()?))\r\n    } else {\r\n      Ok(Self::Else(input.parse()?))\r\n    }\r\n  }\r\n}\r\n\r\n/// Type wrapper to parse `T` only if it is present, otherwise returns\r\n/// None.\r\n///\r\n/// To be used in combination with [Ordered] then all optional items must\r\n/// be last in the tuple, and the stream must end to indicate that the\r\n/// optional items will not be present.\r\n///\r\n/// To be used in combination with [Unordered] the stream must end to\r\n/// indicate that the [Optional]s will not be present.\r\n///\r\n/// # Example\r\n/// Parse [syn::Ident] if it is present, otherwise return None.\r\n/// ```\r\n/// type OptionalType = Optional<syn::Ident>;\r\n/// fn example(stream: syn::parse::ParseStream) -> syn::Result<OptionalType> {\r\n///   stream.parse::<OptionalType>()\r\n/// }\r\n///\r\n/// fn main() {\r\n///   # use quote::quote;\r\n///   let present = quote! { some_name }.into();\r\n///   let not_present = quote! {}.into();\r\n///   let other_present = quote! { \"some_string\" }.into();\r\n///\r\n///   assert!(example(present).is_ok_and(|opt| opt.is_some()));\r\n///   assert!(example(not_present).is_ok_and(|opt| opt.is_none()));\r\n///   assert!(example(other_present).is_ok_and(|opt| opt.is_none()));\r\n/// }\r\n/// ```\r\n/// Used in combination with [Ordered] to parse a [syn::Ident] and\r\n/// optionally a [syn::LitStr]:\r\n/// ```\r\n/// type OrderedOptionalType = Ordered<(syn::Ident, Optional<syn::LitStr>),\r\n/// syn::Token![,]>;\r\n///\r\n/// fn example(stream: syn::parse::ParseStream) ->\r\n/// syn::Result<OrderedOptionalType> {\r\n///   stream.parse::<OrderedOptionalType>()\r\n/// }\r\n///\r\n/// fn main() {\r\n///   let present = quote! { some_name, \"some string\" }.into();\r\n///   let missing = quote! { some_name }.into();\r\n///   // Will error since the stream did not end after the Ordered to indicate no optionals.\r\n///   let error = quote! { some_name, not_a_string }.into();\r\n///\r\n///   assert!(example(present).is_ok_and(|Ordered { items: (ident, string), ..}| string.is_some()));\r\n///   assert!(example(missing).is_ok_and(|Ordered { items: (ident, string), ..}| string.is_none()));\r\n///   assert!(example(error).is_err());\r\n/// }\r\n/// ```\r\n/// Used in combination with [Unordered] it can be used to parse a\r\n/// [syn::Ident] and optionally a [syn::LitStr] in any order:\r\n/// ```\r\n/// type UnorderedOptionalType = Unordered<(syn::Ident, Optional<syn::LitStr>), syn::Token![,]>;\r\n///\r\n/// fn example(stream: syn::parse::ParseStream) -> syn::Result<UnorderedOptionalType> {\r\n///   stream.parse::<UnorderedOptionalType>()\r\n/// }\r\n///\r\n/// fn main() {\r\n///   let present = quote! { some_name, \"some string\" }.into();\r\n///   let not_present = quote! { some_name }.into();\r\n///   let backwards = quote! { \"some string\", some_name }.into();\r\n///   // Will error since the stream did not end after the Unordered to indicate no optionals.\r\n///   let error = quote! { some_name, not_a_string }.into();\r\n///\r\n///   assert!(example(present).is_ok_and(|Unordered { items: (ident, string), ..}| string.is_some()));\r\n///   assert!(example(not_present).is_ok_and(|Unordered { items: (ident, string), ..}| string.is_none()));\r\n///   assert!(example(backwards).is_ok_and(|Unordered { items: (ident, string), ..}| string.is_some()));\r\n///   assert!(example(error).is_err());\r\n/// }\r\n/// ```\r\npub enum Optional<T>\r\nwhere\r\n  T: syn::parse::Parse + crate::common::peekable::Peekable,\r\n{\r\n  Some(T),\r\n  None,\r\n}\r\n\r\nimpl<T> syn::parse::Parse for Optional<T>\r\nwhere\r\n  T: syn::parse::Parse + crate::common::peekable::Peekable,\r\n{\r\n  fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {\r\n    if input.is_empty() {\r\n      return Ok(Self::None);\r\n    }\r\n\r\n    if input.tpeek::<T>() {\r\n      Ok(Self::Some(input.parse()?))\r\n    } else {\r\n      Ok(Self::None)\r\n    }\r\n  }\r\n}\r\n\r\nimpl<T> crate::common::peekable::Peekable for Optional<T>\r\nwhere\r\n  T: syn::parse::Parse + crate::common::peekable::Peekable,\r\n{\r\n  fn display() -> &'static str {\r\n    T::display()\r\n  }\r\n\r\n  fn peek<S>(stream: S) -> bool\r\n  where\r\n    S: crate::common::peekable::PeekableStream,\r\n  {\r\n    if stream.is_empty() {\r\n      return true;\r\n    }\r\n\r\n    T::peek(stream)\r\n  }\r\n}\r\n\r\n// Methods are used in doc tests, but linter doesn't pick that up.\r\n#[allow(dead_code)]\r\nimpl<T> Optional<T>\r\nwhere\r\n  T: syn::parse::Parse + crate::common::peekable::Peekable,\r\n{\r\n  pub fn is_some(&self) -> bool {\r\n    matches!(self, Self::Some(_))\r\n  }\r\n\r\n  pub fn is_none(&self) -> bool {\r\n    matches!(self, Self::None)\r\n  }\r\n\r\n  #[allow(clippy::wrong_self_convention)]\r\n  pub fn to_opt(self) -> Option<T> {\r\n    match self {\r\n      Self::Some(value) => Some(value),\r\n      Self::None => None,\r\n    }\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-macros/src/common/error_handling.rs",
    "content": "//! Utilities for simplifying error handling and diagnostics.\r\n\r\npub mod prelude {\r\n  // ToSpanError is not used yet, but will be used in the future.\r\n  // TODO: Remove unused_imports allow\r\n  #[allow(unused_imports)]\r\n  pub use super::{EmitError, ThenError, ToError, ToSpanError};\r\n}\r\n\r\n/// Extends the `bool` type with a method that returns an error if the\r\n/// value is `true`.\r\npub trait ThenError<E>\r\nwhere\r\n  Self: Sized,\r\n{\r\n  /// Returns `Err(error)` if the value is `true`, otherwise returns\r\n  /// `Ok(self)`.\r\n  ///\r\n  /// # Example\r\n  /// ```\r\n  /// # fn example(string: &str, string_span: syn::Span) -> syn::Result<()> {\r\n  /// string.is_empty().then_error(string_span.error(\"Expected a non-empty string\"))?;\r\n  /// # }\r\n  /// ```\r\n  fn then_error(self, error: E) -> Result<Self, E>;\r\n}\r\n\r\nimpl<E> ThenError<E> for bool {\r\n  fn then_error(self, error: E) -> Result<Self, E> {\r\n    if self { Err(error) } else { Ok(self) }\r\n  }\r\n}\r\n\r\n/// Extension trait for any type that can be tokenized to create a\r\n/// [syn::Error] at its location.\r\npub trait ToError {\r\n  /// Create a [syn::Error] at the span of this object with the given\r\n  /// message.\r\n  ///\r\n  /// # Example\r\n  /// ```\r\n  /// # fn example(ident: syn::Ident) -> syn::Result<()> {\r\n  /// return Err(ident.error(\"Didn't expect an identifier here\"));\r\n  /// # }\r\n  /// ```\r\n  fn error<D: core::fmt::Display>(&self, message: D) -> syn::Error;\r\n}\r\n\r\nimpl<T> ToError for T\r\nwhere\r\n  T: quote::ToTokens,\r\n{\r\n  fn error<D: core::fmt::Display>(&self, message: D) -> syn::Error {\r\n    syn::Error::new_spanned(self, message)\r\n  }\r\n}\r\n\r\n// Very likely to be used in future.\r\n// TODO: Remove dead code allow\r\n#[allow(dead_code)]\r\n/// Extension trait for any [syn::spanned::Spanned] type that creates a\r\n/// [syn::Error] at its location. Use [ToError] where possible, as it\r\n/// creates more accurately spanned errors.\r\npub trait ToSpanError {\r\n  /// Creates a [syn::Error] at the location of this span with the given\r\n  /// message.\r\n  ///\r\n  /// If the object can be tokenized, prefer using [ToError] instead, as it\r\n  /// gives more accurately spanned errors.\r\n  ///\r\n  /// # Example\r\n  /// ```\r\n  /// # fn example(stream: syn::parse::ParseStream) -> syn::Result<()> {\r\n  /// return Err(stream.span().serror(\"Expected ...\"));\r\n  /// # }\r\n  /// ```\r\n  fn serror<D: core::fmt::Display>(&self, message: D) -> syn::Error;\r\n}\r\n\r\nimpl<T> ToSpanError for T\r\nwhere\r\n  T: syn::spanned::Spanned,\r\n{\r\n  fn serror<D: core::fmt::Display>(&self, message: D) -> syn::Error {\r\n    syn::Error::new(self.span(), message)\r\n  }\r\n}\r\n\r\n// Very likely to be used in future.\r\n// TODO: Remove dead code allow\r\n#[allow(dead_code)]\r\npub trait EmitError {\r\n  /// Directly emits a warning message at the span of this object.\r\n  fn emit_warning<D: Into<String>>(&self, message: D);\r\n  /// Emits a help message at the span of this object.\r\n  fn emit_help<D: Into<String>>(&self, message: D);\r\n  /// Emits a note message at the span of this object.\r\n  fn emit_note<D: Into<String>>(&self, message: D);\r\n}\r\n\r\nimpl<T> EmitError for T\r\nwhere\r\n  T: syn::spanned::Spanned,\r\n{\r\n  fn emit_warning<D: Into<String>>(&self, message: D) {\r\n    self.span().unwrap().warning(message).emit();\r\n  }\r\n\r\n  fn emit_help<D: Into<String>>(&self, message: D) {\r\n    self.span().unwrap().help(message).emit();\r\n  }\r\n\r\n  fn emit_note<D: Into<String>>(&self, message: D) {\r\n    self.span().unwrap().note(message).emit();\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-macros/src/common/mod.rs",
    "content": "/// Utilities for parsing attributes\r\npub mod attributes;\r\n/// Utilities for parsing alternatives and combinators\r\npub mod branch;\r\n/// Extension traits and utilities to make error handling more succinct\r\npub mod error_handling;\r\n/// Utilities for parsing a named parameter (`name = value`)\r\npub mod named_parameter;\r\n/// Type for a parsable type within parentheses\r\npub mod parenthesized;\r\n/// Trait to get a `Peek` object from compatible syn types\r\npub mod peekable;\r\n/// An owned version of a `syn::LitStr`\r\npub mod spanned_string;\r\n\r\npub(crate) use peekable::custom_keyword;\r\n"
  },
  {
    "path": "packages/wm-macros/src/common/named_parameter.rs",
    "content": "//! Types for parsing `name = value` patterns.\r\n\r\n/// Type to represent a `name = value` pair, where `name` can be peeked and\r\n/// both `name` and `value` can be parsed.\r\n///\r\n/// # Example\r\n/// Parse a name-value pair of [syn::Ident] and [syn::LitStr].\r\n/// ```\r\n/// type NamedParam = NamedParameter<syn::Ident, syn::LitStr>;\r\n///\r\n/// fn example(stream: syn::parse::ParseStream) -> syn::Result<NamedParam> {\r\n///   stream.parse::<NamedParam>()\r\n/// }\r\n///\r\n/// fn main() {\r\n///   # use quote::quote;\r\n///   let tokens = quote! { some_name = \"some_value\" }.into();\r\n///\r\n///   assert!(example(tokens).is_ok());\r\n/// }\r\n/// ```\r\n// Will be used in future.\r\n#[allow(dead_code)]\r\npub struct NamedParameter<Name, Param>\r\nwhere\r\n  Name: syn::parse::Parse + crate::common::peekable::Peekable,\r\n  Param: syn::parse::Parse,\r\n{\r\n  pub name: Name,\r\n  pub param: Param,\r\n}\r\n\r\nimpl<Name, Param> syn::parse::Parse for NamedParameter<Name, Param>\r\nwhere\r\n  Name: syn::parse::Parse + crate::common::peekable::Peekable,\r\n  Param: syn::parse::Parse,\r\n{\r\n  fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {\r\n    let name = input.parse()?;\r\n    input.parse::<syn::Token![=]>()?;\r\n    let param = input.parse()?;\r\n    Ok(Self { name, param })\r\n  }\r\n}\r\n\r\nimpl<Name, Param> crate::common::peekable::Peekable\r\n  for NamedParameter<Name, Param>\r\nwhere\r\n  Name: syn::parse::Parse + crate::common::peekable::Peekable,\r\n  Param: syn::parse::Parse,\r\n{\r\n  fn peek<S>(stream: S) -> bool\r\n  where\r\n    S: crate::common::peekable::PeekableStream,\r\n  {\r\n    Name::peek(stream)\r\n  }\r\n\r\n  fn display() -> &'static str {\r\n    Name::display()\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-macros/src/common/parenthesized.rs",
    "content": "//! Type wrappers for parsing content that is within delimiters, such as\r\n//! parenthesis or brackets.\r\n\r\n/// Type wrapper for parsing content within parenthesis.\r\n///\r\n/// # Example\r\n/// Parse a [syn::Ident] within parenthesis:\r\n/// ```\r\n/// type ParenthesizedIdent = wm_macros::Parenthesized<syn::Ident>;\r\n/// fn example(stream: syn::parse::ParseStream) -> syn::Result<ParenthesizedIdent> {\r\n///   stream.parse::<ParenthesizedIdent>()\r\n/// }\r\n///\r\n/// fn main() {\r\n///   # use quote::quote;\r\n///   let tokens = quote! { (some_name) }.into();\r\n///\r\n///   assert!(example(tokens).is_ok());\r\n/// }\r\n/// ```\r\n// Util type that will be used in future\r\n#[allow(dead_code)]\r\npub struct Parenthesized<T>(pub T)\r\nwhere\r\n  T: syn::parse::Parse;\r\n\r\nimpl<T> syn::parse::Parse for Parenthesized<T>\r\nwhere\r\n  T: syn::parse::Parse,\r\n{\r\n  fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {\r\n    let content;\r\n    syn::parenthesized!(content in input);\r\n    Ok(Parenthesized(content.parse()?))\r\n  }\r\n}\r\n\r\nimpl<T> core::ops::Deref for Parenthesized<T>\r\nwhere\r\n  T: syn::parse::Parse,\r\n{\r\n  type Target = T;\r\n\r\n  fn deref(&self) -> &Self::Target {\r\n    &self.0\r\n  }\r\n}\r\n\r\nimpl<T> core::ops::DerefMut for Parenthesized<T>\r\nwhere\r\n  T: syn::parse::Parse,\r\n{\r\n  fn deref_mut(&mut self) -> &mut Self::Target {\r\n    &mut self.0\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-macros/src/common/peekable.rs",
    "content": "//! Custom peek implementation to support generics and custom peek\r\n//! functions.\r\n\r\n// Syn has a `Peek` trait, but it works weirdly. There is `syn::Ident` the\r\n// type which implements `Parse` and `syn::Ident` the function which\r\n// implements `Peek`. This file is for implementing a custom method of\r\n// peeking using the type itself rather than a function using the same name\r\n// as a type. This allows peeking to work nicer with generics.\r\n\r\npub mod prelude {\r\n  pub use super::{Peekable, TPeek};\r\n}\r\n\r\n/// Trait for any stream that can peek at the next token.\r\npub trait PeekableStream {\r\n  fn is_empty(&self) -> bool;\r\n  fn peek<T: syn::parse::Peek>(&self, token: T) -> bool;\r\n}\r\n\r\nimpl PeekableStream for &syn::parse::ParseStream<'_> {\r\n  fn peek<T: syn::parse::Peek>(&self, token: T) -> bool {\r\n    (*self).peek(token)\r\n  }\r\n  fn is_empty(&self) -> bool {\r\n    (*self).is_empty()\r\n  }\r\n}\r\n\r\nimpl PeekableStream for syn::parse::ParseStream<'_> {\r\n  fn peek<T: syn::parse::Peek>(&self, token: T) -> bool {\r\n    (*self).peek(token)\r\n  }\r\n\r\n  fn is_empty(&self) -> bool {\r\n    (*self).is_empty()\r\n  }\r\n}\r\nimpl PeekableStream for syn::parse::ParseBuffer<'_> {\r\n  fn peek<T: syn::parse::Peek>(&self, token: T) -> bool {\r\n    self.peek(token)\r\n  }\r\n\r\n  fn is_empty(&self) -> bool {\r\n    self.is_empty()\r\n  }\r\n}\r\nimpl PeekableStream for syn::parse::Lookahead1<'_> {\r\n  fn peek<T: syn::parse::Peek>(&self, token: T) -> bool {\r\n    self.peek(token)\r\n  }\r\n\r\n  fn is_empty(&self) -> bool {\r\n    self.peek(syn::parse::End)\r\n  }\r\n}\r\nimpl PeekableStream for &syn::parse::Lookahead1<'_> {\r\n  fn peek<T: syn::parse::Peek>(&self, token: T) -> bool {\r\n    (*self).peek(token)\r\n  }\r\n\r\n  fn is_empty(&self) -> bool {\r\n    (*self).peek(syn::parse::End)\r\n  }\r\n}\r\n\r\n/// Custom trait for types that can be peeked.\r\npub trait Peekable {\r\n  /// Gets the type's `Peek` implementation, since in syn the type\r\n  /// implements `Parse` but there is a function with the same path that\r\n  /// implements `Peek`. So this trait is used to get the function (`Peek`)\r\n  /// from the type (`Parse`).\r\n  ///\r\n  /// # Examble\r\n  /// ```\r\n  /// fn peek_then_parse<T: Parse + Peekable>(stream: syn::parse::ParseStream) -> syn::Result<T> {\r\n  ///   if stream.peek(T::peekable()) {\r\n  ///     let value = stream.parse::<T>()?;\r\n  ///   }\r\n  /// }\r\n  /// ```\r\n  fn peek<T>(stream: T) -> bool\r\n  where\r\n    T: PeekableStream;\r\n  // Useful for debugging and custom error messages.\r\n  #[allow(dead_code)]\r\n  fn display() -> &'static str;\r\n}\r\n\r\n/// Trait for the types in syn that have a corresponding function that\r\n/// implements [syn::parse::Peek]. E.g. it implements a method for\r\n/// [syn::Ident] the type that returns [syn::Ident] the function.\r\npub trait SynPeek {\r\n  fn peekable() -> impl syn::parse::Peek;\r\n  // To be forwarded to [Peekable]\r\n  #[allow(dead_code)]\r\n  fn display() -> &'static str;\r\n}\r\n\r\n/// Implement [Peekable] for [SynPeek]\r\nimpl<T: SynPeek> Peekable for T {\r\n  fn peek<S>(stream: S) -> bool\r\n  where\r\n    S: PeekableStream,\r\n  {\r\n    stream.peek(T::peekable())\r\n  }\r\n\r\n  fn display() -> &'static str {\r\n    T::display()\r\n  }\r\n}\r\n\r\n/// Helper fucntion to get the display string for a peekable type.\r\n// Used in a macro call\r\n#[allow(dead_code)]\r\npub fn get_peek_display<T: syn::parse::Peek>(_peek: T) -> &'static str {\r\n  use syn::token::Token;\r\n  T::Token::display()\r\n}\r\n\r\n/// Extends the [PeekableStream] trait with a method to peek at a type\r\n/// rather than a value.\r\n///\r\n/// # Example\r\n/// ```\r\n/// # fn example(stream: syn::parse::ParseStream) -> syn::Result<()> {\r\n/// // Allows for\r\n/// stream.tpeek::<syn::Ident>()?;\r\n/// // Rather than\r\n/// stream.peek(syn::Ident)?;\r\n/// # }\r\npub trait TPeek<'a> {\r\n  fn tpeek<T>(&'a self) -> bool\r\n  where\r\n    T: Peekable;\r\n}\r\n\r\nimpl<'a, T> TPeek<'a> for T\r\nwhere\r\n  &'a T: PeekableStream + 'a,\r\n{\r\n  fn tpeek<U>(&'a self) -> bool\r\n  where\r\n    U: Peekable,\r\n  {\r\n    U::peek(self)\r\n  }\r\n}\r\n\r\n/// Custom keyword macro to define a syn custom keyword that also\r\n/// implements Peekable.\r\nmacro_rules! custom_keyword {\r\n  ($name:ident) => {\r\n    syn::custom_keyword!($name);\r\n\r\n    impl $crate::common::peekable::SynPeek for $name {\r\n      fn peekable() -> impl syn::parse::Peek {\r\n        $name\r\n      }\r\n\r\n      fn display() -> &'static str {\r\n        crate::common::peekable::get_peek_display(Self::peekable())\r\n      }\r\n    }\r\n  };\r\n}\r\npub(crate) use custom_keyword;\r\n\r\n/// Macro for implementing [SynPeek] for a type that implements\r\n/// [syn::parse::Peek].\r\nmacro_rules! impl_syn_peek {\r\n  ($($name:tt)+) => {\r\n    impl SynPeek for $($name)+ {\r\n      fn peekable() -> impl syn::parse::Peek {\r\n        $($name)+\r\n      }\r\n\r\n      fn display() -> &'static str {\r\n        crate::common::peekable::get_peek_display(Self::peekable())\r\n      }\r\n    }\r\n  };\r\n}\r\n\r\nimpl_syn_peek!(syn::Ident);\r\nimpl_syn_peek!(syn::LitStr);\r\n// TODO: Other syn types\r\n\r\n// Copied from syn::Token!\r\nimpl_syn_peek!(syn::Token![abstract]);\r\nimpl_syn_peek!(syn::Token![as]);\r\nimpl_syn_peek!(syn::Token![async]);\r\nimpl_syn_peek!(syn::Token![auto]);\r\nimpl_syn_peek!(syn::Token![await]);\r\nimpl_syn_peek!(syn::Token![become]);\r\nimpl_syn_peek!(syn::Token![box]);\r\nimpl_syn_peek!(syn::Token![break]);\r\nimpl_syn_peek!(syn::Token![const]);\r\nimpl_syn_peek!(syn::Token![continue]);\r\nimpl_syn_peek!(syn::Token![crate]);\r\nimpl_syn_peek!(syn::Token![default]);\r\nimpl_syn_peek!(syn::Token![do]);\r\nimpl_syn_peek!(syn::Token![dyn]);\r\nimpl_syn_peek!(syn::Token![else]);\r\nimpl_syn_peek!(syn::Token![enum]);\r\nimpl_syn_peek!(syn::Token![extern]);\r\nimpl_syn_peek!(syn::Token![final]);\r\nimpl_syn_peek!(syn::Token![fn]);\r\nimpl_syn_peek!(syn::Token![for]);\r\nimpl_syn_peek!(syn::Token![if]);\r\nimpl_syn_peek!(syn::Token![impl]);\r\nimpl_syn_peek!(syn::Token![in]);\r\nimpl_syn_peek!(syn::Token![let]);\r\nimpl_syn_peek!(syn::Token![loop]);\r\nimpl_syn_peek!(syn::Token![macro]);\r\nimpl_syn_peek!(syn::Token![match]);\r\nimpl_syn_peek!(syn::Token![mod]);\r\nimpl_syn_peek!(syn::Token![move]);\r\nimpl_syn_peek!(syn::Token![mut]);\r\nimpl_syn_peek!(syn::Token![override]);\r\nimpl_syn_peek!(syn::Token![priv]);\r\nimpl_syn_peek!(syn::Token![pub]);\r\nimpl_syn_peek!(syn::Token![ref]);\r\nimpl_syn_peek!(syn::Token![return]);\r\nimpl_syn_peek!(syn::Token![Self]);\r\nimpl_syn_peek!(syn::Token![self]);\r\nimpl_syn_peek!(syn::Token![static]);\r\nimpl_syn_peek!(syn::Token![struct]);\r\nimpl_syn_peek!(syn::Token![super]);\r\nimpl_syn_peek!(syn::Token![trait]);\r\nimpl_syn_peek!(syn::Token![try]);\r\nimpl_syn_peek!(syn::Token![type]);\r\nimpl_syn_peek!(syn::Token![typeof]);\r\nimpl_syn_peek!(syn::Token![union]);\r\nimpl_syn_peek!(syn::Token![unsafe]);\r\nimpl_syn_peek!(syn::Token![unsized]);\r\nimpl_syn_peek!(syn::Token![use]);\r\nimpl_syn_peek!(syn::Token![virtual]);\r\nimpl_syn_peek!(syn::Token![where]);\r\nimpl_syn_peek!(syn::Token![while]);\r\nimpl_syn_peek!(syn::Token![yield]);\r\nimpl_syn_peek!(syn::Token![&]);\r\nimpl_syn_peek!(syn::Token![&&]);\r\nimpl_syn_peek!(syn::Token![&=]);\r\nimpl_syn_peek!(syn::Token![@]);\r\nimpl_syn_peek!(syn::Token![^]);\r\nimpl_syn_peek!(syn::Token![^=]);\r\nimpl_syn_peek!(syn::Token![:]);\r\nimpl_syn_peek!(syn::Token![,]);\r\nimpl_syn_peek!(syn::Token![$]);\r\nimpl_syn_peek!(syn::Token![.]);\r\nimpl_syn_peek!(syn::Token![..]);\r\nimpl_syn_peek!(syn::Token![...]);\r\nimpl_syn_peek!(syn::Token![..=]);\r\nimpl_syn_peek!(syn::Token![=]);\r\nimpl_syn_peek!(syn::Token![==]);\r\nimpl_syn_peek!(syn::Token![=>]);\r\nimpl_syn_peek!(syn::Token![>=]);\r\nimpl_syn_peek!(syn::Token![>]);\r\nimpl_syn_peek!(syn::Token![<-]);\r\nimpl_syn_peek!(syn::Token![<=]);\r\nimpl_syn_peek!(syn::Token![<]);\r\nimpl_syn_peek!(syn::Token![-]);\r\nimpl_syn_peek!(syn::Token![-=]);\r\nimpl_syn_peek!(syn::Token![!=]);\r\nimpl_syn_peek!(syn::Token![!]);\r\nimpl_syn_peek!(syn::Token![|]);\r\nimpl_syn_peek!(syn::Token![|=]);\r\nimpl_syn_peek!(syn::Token![||]);\r\nimpl_syn_peek!(syn::Token![::]);\r\nimpl_syn_peek!(syn::Token![%]);\r\nimpl_syn_peek!(syn::Token![%=]);\r\nimpl_syn_peek!(syn::Token![+]);\r\nimpl_syn_peek!(syn::Token![+=]);\r\nimpl_syn_peek!(syn::Token![#]);\r\nimpl_syn_peek!(syn::Token![?]);\r\nimpl_syn_peek!(syn::Token![->]);\r\nimpl_syn_peek!(syn::Token![;]);\r\nimpl_syn_peek!(syn::Token![<<]);\r\nimpl_syn_peek!(syn::Token![<<=]);\r\nimpl_syn_peek!(syn::Token![>>]);\r\nimpl_syn_peek!(syn::Token![>>=]);\r\nimpl_syn_peek!(syn::Token![/]);\r\nimpl_syn_peek!(syn::Token![/=]);\r\nimpl_syn_peek!(syn::Token![*]);\r\nimpl_syn_peek!(syn::Token![*=]);\r\nimpl_syn_peek!(syn::Token![~]);\r\nimpl_syn_peek!(syn::Token![_]);\r\n"
  },
  {
    "path": "packages/wm-macros/src/common/spanned_string.rs",
    "content": "//! Type for a String with an associated source code span.\r\n\r\n/// A String with an associated source code span.\r\n/// An owned version of a syn::LitStr.\r\n#[derive(Debug, Clone)]\r\npub struct SpannedString {\r\n  pub value: String,\r\n  pub span: proc_macro2::Span,\r\n}\r\n\r\nimpl SpannedString {\r\n  pub fn new(value: String, span: proc_macro2::Span) -> Self {\r\n    SpannedString { value, span }\r\n  }\r\n\r\n  pub fn from_lit_str(lit_str: syn::LitStr) -> Self {\r\n    SpannedString {\r\n      value: lit_str.value(),\r\n      span: lit_str.span(),\r\n    }\r\n  }\r\n}\r\n\r\nimpl From<SpannedString> for String {\r\n  fn from(spanned_string: SpannedString) -> Self {\r\n    spanned_string.value\r\n  }\r\n}\r\n\r\nimpl From<&SpannedString> for syn::LitStr {\r\n  fn from(spanned_string: &SpannedString) -> Self {\r\n    syn::LitStr::new(&spanned_string.value, spanned_string.span)\r\n  }\r\n}\r\n\r\nimpl From<syn::LitStr> for SpannedString {\r\n  fn from(lit_str: syn::LitStr) -> Self {\r\n    SpannedString::from_lit_str(lit_str)\r\n  }\r\n}\r\n\r\n/// Parse a `SpannedString` from a `syn::LitStr`.\r\nimpl syn::parse::Parse for SpannedString {\r\n  fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {\r\n    let lit_str: syn::LitStr = input.parse()?;\r\n    Ok(lit_str.into())\r\n  }\r\n}\r\n\r\n/// Convert a `SpannedString` to a `syn::LitStr` for token generation.\r\nimpl quote::ToTokens for SpannedString {\r\n  fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {\r\n    let lit_str: syn::LitStr = self.into();\r\n    lit_str.to_tokens(tokens);\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-macros/src/enum_from_inner/mod.rs",
    "content": "//! Macro to derive `From` and `TryFrom` implementations for enum variants\r\n\r\nuse quote::ToTokens as _;\r\n\r\nuse crate::prelude::*;\r\n\r\n/// Macro to derive `From` and `TryFrom` implementations for enum variants\r\n/// Derives `From<T>` for each variant with format `Enum::Variant(T)`, and\r\n/// implements `TryFrom<Enum>` for each variant, returning an error if the\r\n/// variant does not match.\r\npub fn enum_from_inner(\r\n  input: proc_macro::TokenStream,\r\n) -> proc_macro::TokenStream {\r\n  let input = syn::parse_macro_input!(input as syn::DeriveInput);\r\n\r\n  let name = &input.ident;\r\n\r\n  let enum_data = match input.data {\r\n    syn::Data::Enum(data) => data,\r\n    _ => {\r\n      return name\r\n        .error(\"This macro can only be used on enums\")\r\n        .to_compile_error()\r\n        .into();\r\n    }\r\n  };\r\n\r\n  let variants = enum_data.variants.iter().map(|variant| {\r\n    let ident = &variant.ident;\r\n    let inner_type = match &variant.fields {\r\n      syn::Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {\r\n        &fields.unnamed[0].ty\r\n      }\r\n      // Don't error on Unit variants, just do nothing.\r\n      syn::Fields::Unit => {\r\n        return quote::quote! {};\r\n      }\r\n      _ => {\r\n        return variant\r\n          .error(\"Enum variants must have exactly one unnamed field\")\r\n          .to_compile_error();\r\n      }\r\n    };\r\n\r\n    let error = format!(\r\n      \"Cannot convert this variant of enum `{}` to {}\",\r\n      name,\r\n      // syn::Type doesn't print well, so convert it to a token stream\r\n      // which works well enough.\r\n      inner_type.to_token_stream()\r\n    );\r\n\r\n    quote::quote! {\r\n      impl From<#inner_type> for #name {\r\n        fn from(value: #inner_type) -> Self {\r\n          #name::#ident(value)\r\n        }\r\n      }\r\n\r\n      impl TryFrom<#name> for #inner_type {\r\n        type Error = &'static str;\r\n\r\n        fn try_from(value: #name) -> Result<Self, Self::Error> {\r\n          match value {\r\n            #name::#ident(inner) => Ok(inner),\r\n            _ => Err(#error),\r\n          }\r\n        }\r\n      }\r\n\r\n      impl<'a> TryFrom<&'a #name> for &'a #inner_type {\r\n        type Error = &'static str;\r\n\r\n        fn try_from(value: &'a #name) -> Result<Self, Self::Error> {\r\n          match value {\r\n            #name::#ident(inner) => Ok(inner),\r\n            _ => Err(#error),\r\n          }\r\n        }\r\n      }\r\n    }\r\n  });\r\n\r\n  quote::quote! {\r\n    #(#variants)*\r\n  }\r\n  .into()\r\n}\r\n"
  },
  {
    "path": "packages/wm-macros/src/lib.rs",
    "content": "// Enable proc macro diagnostics to allow emitting warnings and errors in\r\n// line\r\n#![feature(proc_macro_diagnostic)]\r\n\r\nmod common;\r\nmod enum_from_inner;\r\nmod subenum;\r\nuse proc_macro::TokenStream;\r\n\r\nmod prelude {\r\n  pub use crate::common::{\r\n    attributes::prelude::*, error_handling::prelude::*,\r\n    peekable::prelude::*,\r\n  };\r\n}\r\n\r\n/// Creates subenums from a main enum, and defines\r\n/// * `impl From<SubEnum> for MainEnum`\r\n/// * `impl TryFrom<MainEnum> for SubEnum`\r\n/// * `impl TryFrom<SubEnumOne> for SubEnumTwo` where `SubEnumOne` and\r\n///   `SubEnumTwo` share variant(s).\r\n///\r\n/// Accepts a defaults block of attributes to be added to every subenum\r\n/// ```\r\n/// #[subenum(defaults, {\r\n///   /// Subenum of [X]\r\n///   #[derive(Clone, Debug)]\r\n/// })]\r\n/// ```\r\n///\r\n/// And any number of subenum declarations, which are defined as\r\n/// ```\r\n/// #[subenum(SubenumName, {\r\n///   /// Subset of [X] that can be checked for equality.\r\n///   #[derive(PartialEq)] // Will also derive [Clone] and [Debug] from the defaults block\r\n/// })]\r\n/// ```\r\n///\r\n/// # Example\r\n/// ```\r\n/// /// Your main enum documentation\r\n/// // Note that the defaults block does not apply to the main enum itself.\r\n/// #[derive(Clone, Debug, wm_macros::SubEnum)]\r\n/// #[subenum(defaults, {\r\n///   /// Subenum of [MainEnum]\r\n///   #[derive(Clone, Debug)]\r\n/// })]\r\n/// #[subenum(Similar, {\r\n///   /// Subset of [MainEnum] that can be checked for equality.\r\n///   #[derive(PartialEq)] // Will also derive [Clone] and [Debug] from the defaults block\r\n/// })]\r\n/// #[subenum(Hashable, {\r\n///   /// Subset of [MainEnum] that can be hashed.\r\n///   #[derive(Hash)] // Will also derive [Clone] and [Debug] from the defaults block\r\n/// })]\r\n/// pub enum MainEnum {\r\n///   Path(PathBuf),\r\n///   #[subenum(Similar, Hashable)]\r\n///   Length(i32),\r\n///   #[subenum(Hashable)]\r\n///   Name(String)\r\n/// }\r\n///\r\n/// let name = String::from(\"example\");\r\n/// let name_enum = MainEnum::from(name);\r\n///\r\n/// // Try to convert MainEnum to Hashable.\r\n/// let hashable_name = Hashable::try_from(name_enum).unwrap(); // Will succeed, as `Name` is present in the `Hashable` subenum.\r\n/// hashable_name.hash();\r\n///\r\n/// let similar_name = Similar::try_from(hashable_name.clone());\r\n/// assert!(similar_name.is_err()); // Will fail, as `Name` is not present in the `Similar` subenum.\r\n///\r\n/// // And convert it back (infallible).\r\n/// let name_enum: MainEnum = hashable_name.into();\r\n///\r\n/// let length = 42;\r\n/// let length_enum: MainEnum = length.into();\r\n///\r\n/// let similar_length = Similar::try_from(length_enum).unwrap();\r\n///\r\n/// let other_length =  Hashable::Length(42);\r\n/// let other_similar_length = Similar::try_from(other_length).unwrap(); // Convert between subenums that share variants.\r\n///\r\n/// assert_eq!(similar_length, other_similar_length);\r\n/// ```\r\n#[proc_macro_derive(SubEnum, attributes(subenum))]\r\npub fn sub_enum(input: TokenStream) -> TokenStream {\r\n  subenum::sub_enum(input)\r\n}\r\n\r\n/// Creates `impl From<Inner> for Enum` and `impl TryFrom<Enum> for Inner`\r\n///\r\n/// # Example\r\n/// ```\r\n/// struct One;\r\n/// struct Two;\r\n///\r\n/// #[derive(wm_macros::EnumFromInner)]\r\n/// enum MyEnum {\r\n///   One(One),\r\n///   Two(Two),\r\n/// }\r\n///\r\n/// let one = One;\r\n/// let my_enum: MyEnum = one.into(); // Converts One into MyEnum::One(One)\r\n///\r\n/// let one = my_enum.try_into().unwrap(); // Attempts to convert MyEnum::One(One) into One\r\n///\r\n/// let two = Two;\r\n/// let my_enum: MyEnum = two.into(); // Converts Two into MyEnum::Two(Two)\r\n///\r\n/// let one = my_enum.try_into(); // Will fail, as MyEnum::Two(Two) cannot be converted to One\r\n/// assert!(one.is_err());\r\n/// ```\r\n#[proc_macro_derive(EnumFromInner)]\r\npub fn enum_from_inner(input: TokenStream) -> TokenStream {\r\n  enum_from_inner::enum_from_inner(input)\r\n}\r\n"
  },
  {
    "path": "packages/wm-macros/src/subenum/enum_attrs.rs",
    "content": "use crate::{common::branch::Ordered, prelude::*};\r\n\r\nmod kw {\r\n  crate::common::custom_keyword!(defaults);\r\n}\r\n\r\n/// Collects all `#[subenum(...)]` attributes from the given iterator of\r\n/// attributes and returns a list of `Subenum` instances.\r\npub fn collect_sub_enums<'a>(\r\n  attrs: impl Iterator<Item = &'a syn::MetaList>,\r\n) -> syn::Result<Vec<Subenum>> {\r\n  attrs\r\n    .map(|attr| attr.parse_args())\r\n    .collect::<syn::Result<Vec<_>>>()\r\n}\r\n\r\n/// Each subenum can either be a declaration with a name and attribute\r\n/// block, or a defaults block to append to every subenum.\r\npub enum Subenum {\r\n  Defaults(proc_macro2::TokenStream),\r\n  Declaration(SubenumDeclaration),\r\n}\r\n\r\nimpl syn::parse::Parse for Subenum {\r\n  fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {\r\n    type Defaults = Ordered<(kw::defaults, AttrBlock), syn::Token![,]>;\r\n    if input.tpeek::<Defaults>() {\r\n      let Ordered {\r\n        items: (_, AttrBlock { attrs }),\r\n        ..\r\n      } = input.parse::<Defaults>()?;\r\n      return Ok(Self::Defaults(attrs));\r\n    }\r\n\r\n    let declaration: SubenumDeclaration = input.parse()?;\r\n    Ok(Self::Declaration(declaration))\r\n  }\r\n}\r\n\r\n/// Parser for `<name>, {...}` where name is the name of the subenum, and\r\n/// the block contents are passed through as is.\r\npub struct SubenumDeclaration {\r\n  pub name: syn::Ident,\r\n  pub attrs: proc_macro2::TokenStream,\r\n}\r\n\r\nimpl syn::parse::Parse for SubenumDeclaration {\r\n  fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {\r\n    type Declaration = Ordered<(syn::Ident, AttrBlock), syn::Token![,]>;\r\n\r\n    let Ordered {\r\n      items: (name, AttrBlock { attrs }),\r\n      ..\r\n    } = input.parse::<Declaration>()?;\r\n    Ok(Self { name, attrs })\r\n  }\r\n}\r\n\r\n/// Block of arbitray tokens that are contained within { ... }\r\nstruct AttrBlock {\r\n  pub attrs: proc_macro2::TokenStream,\r\n}\r\n\r\nimpl syn::parse::Parse for AttrBlock {\r\n  fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {\r\n    let content;\r\n    let _ = syn::braced!(content in input);\r\n    let attrs: proc_macro2::TokenStream = content.parse()?;\r\n    Ok(Self { attrs })\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-macros/src/subenum/mod.rs",
    "content": "use enum_attrs::{Subenum, SubenumDeclaration};\r\n\r\nuse crate::prelude::*;\r\n\r\nconst SUBENUM_ATTR_NAME: &str = \"subenum\";\r\n\r\nmod enum_attrs;\r\nmod variant_attr;\r\n\r\npub fn sub_enum(\r\n  input: proc_macro::TokenStream,\r\n) -> proc_macro::TokenStream {\r\n  let input = syn::parse_macro_input!(input as syn::DeriveInput);\r\n\r\n  let attrs = &input.attrs;\r\n\r\n  let sub_enums = match enum_attrs::collect_sub_enums(\r\n    attrs.find_list_attrs(SUBENUM_ATTR_NAME),\r\n  ) {\r\n    Ok(sub_enums) => sub_enums,\r\n    Err(err) => return err.to_compile_error().into(),\r\n  };\r\n\r\n  let enum_data = match input.data {\r\n    syn::Data::Enum(data) => data,\r\n    _ => {\r\n      return input\r\n        .ident\r\n        .error(\"This macro can only be used on enums\")\r\n        .to_compile_error()\r\n        .into();\r\n    }\r\n  };\r\n\r\n  let variants = match enum_data\r\n    .variants\r\n    .iter()\r\n    .map(variant_attr::parse_variant)\r\n    .collect::<syn::Result<Vec<_>>>()\r\n  {\r\n    Ok(variants) => variants,\r\n    Err(err) => return err.to_compile_error().into(),\r\n  };\r\n\r\n  // Filter to get the default blocks and combine them into a single token\r\n  // stream.\r\n  let defaults = sub_enums\r\n    .iter()\r\n    .filter_map(|sub| match &sub {\r\n      Subenum::Defaults(attrs) => Some(attrs.clone()),\r\n      _ => None,\r\n    })\r\n    .reduce(|mut acc, el| {\r\n      acc.extend(el);\r\n      acc\r\n    })\r\n    .unwrap_or_default();\r\n\r\n  let sub_enums = sub_enums\r\n    .into_iter()\r\n    .filter_map(|sub| match sub {\r\n      Subenum::Declaration(decl) => Some(decl),\r\n      Subenum::Defaults(_) => None,\r\n    })\r\n    .collect::<Vec<_>>();\r\n\r\n  for variant in &variants {\r\n    for enum_name in &variant.enums {\r\n      if !sub_enums.iter().any(|sub_enum| sub_enum.name == *enum_name) {\r\n        enum_name.emit_warning(\r\n          \"Variant references a subenum that is not defined.\",\r\n        );\r\n      }\r\n    }\r\n  }\r\n\r\n  let sub_enums = combine_variants(sub_enums, variants, &defaults);\r\n\r\n  let sub_enum_to_main_impls = sub_enums\r\n    .iter()\r\n    .map(|sub_enum| from_sub_to_main_impl(&input.ident, sub_enum));\r\n\r\n  let main_to_sub_impls = sub_enums\r\n    .iter()\r\n    .map(|sub| try_from_main_to_sub_impl(&input.ident, sub));\r\n\r\n  struct SharedVariant {\r\n    sub_enum_a: syn::Ident,\r\n    sub_enum_b: syn::Ident,\r\n    variants: Vec<Variant>,\r\n  }\r\n\r\n  // Find all sub enums that have a shared variant, so we can make\r\n  // `TryFrom` impls between them.\r\n  let mut shared_variants: Vec<SharedVariant> = Vec::new();\r\n  for i in 0..sub_enums.len() {\r\n    for j in (i + 1)..sub_enums.len() {\r\n      let sub_enum_a = &sub_enums[i];\r\n      let sub_enum_b = &sub_enums[j];\r\n\r\n      for variant_a in &sub_enum_a.variants {\r\n        if sub_enum_b.variants.iter().any(|v| v.name == variant_a.name) {\r\n          if let Some(shared) = shared_variants.iter_mut().find(|sv| {\r\n            sv.sub_enum_a == sub_enum_a.name\r\n              && sv.sub_enum_b == sub_enum_b.name\r\n          }) {\r\n            shared.variants.push(variant_a.clone());\r\n          } else {\r\n            shared_variants.push(SharedVariant {\r\n              sub_enum_a: sub_enum_a.name.clone(),\r\n              sub_enum_b: sub_enum_b.name.clone(),\r\n              variants: vec![variant_a.clone()],\r\n            });\r\n          }\r\n        }\r\n      }\r\n    }\r\n  }\r\n\r\n  let shared_variants = shared_variants.iter().map(|shared| {\r\n    let a = &shared.sub_enum_a;\r\n    let b = &shared.sub_enum_b;\r\n\r\n    let variants_a_b = shared.variants.iter().map(|v| {\r\n      let var_name = &v.name;\r\n      quote::quote! {\r\n        #a::#var_name(v) => Ok(#b::#var_name(v))\r\n      }\r\n    });\r\n\r\n    let variants_b_a = shared.variants.iter().map(|v| {\r\n      let var_name = &v.name;\r\n      quote::quote! {\r\n        #b::#var_name(v) => Ok(#a::#var_name(v))\r\n      }\r\n    });\r\n\r\n    let eror_a_b = format!(\r\n      \"Cannot convert this variant of sub enum `{a}` to sub enum `{b}`.\"\r\n    );\r\n\r\n    let eror_b_a = format!(\r\n      \"Cannot convert this variant of sub enum `{b}` to sub enum `{a}`.\"\r\n    );\r\n\r\n    quote::quote! {\r\n      impl TryFrom<#a> for #b {\r\n        type Error = &'static str;\r\n\r\n        fn try_from(value: #a) -> Result<Self, Self::Error> {\r\n          match value {\r\n            #(#variants_a_b),*,\r\n            _ => Err(#eror_a_b),\r\n          }\r\n        }\r\n      }\r\n\r\n      impl TryFrom<#b> for #a {\r\n        type Error = &'static str;\r\n\r\n        fn try_from(value: #b) -> Result<Self, Self::Error> {\r\n          match value {\r\n            #(#variants_b_a),*,\r\n            _ => Err(#eror_b_a),\r\n          }\r\n        }\r\n      }\r\n    }\r\n  });\r\n\r\n  quote::quote! {\r\n    #(#sub_enums)*\r\n\r\n    #(#sub_enum_to_main_impls)*\r\n\r\n    #(#main_to_sub_impls)*\r\n\r\n    #(#shared_variants)*\r\n  }\r\n  .into()\r\n}\r\n\r\n/// Holds the final data for creating a sub enum.\r\nstruct SubEnum<'a> {\r\n  pub name: syn::Ident,\r\n  pub defaults: &'a proc_macro2::TokenStream,\r\n  pub attrs: proc_macro2::TokenStream,\r\n  pub variants: Vec<Variant>,\r\n}\r\n\r\n/// Final representation of a sub enum variant, which includes its name and\r\n/// the contained type.\r\n#[derive(Debug, Clone)]\r\nstruct Variant {\r\n  pub name: syn::Ident,\r\n  pub contained: syn::Type,\r\n}\r\n\r\nimpl<'a> quote::ToTokens for SubEnum<'a> {\r\n  fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {\r\n    let name = &self.name;\r\n    let defaults = &self.defaults;\r\n    let attrs = &self.attrs;\r\n    let variants = &self.variants;\r\n\r\n    tokens.extend(quote::quote! {\r\n      #defaults\r\n      #attrs\r\n      pub enum #name {\r\n        #(#variants),*\r\n      }\r\n    });\r\n  }\r\n}\r\n\r\nimpl quote::ToTokens for Variant {\r\n  fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {\r\n    let variant_name = &self.name;\r\n    let contained = &self.contained;\r\n    tokens.extend(quote::quote! {\r\n      #variant_name(#contained)\r\n    });\r\n  }\r\n}\r\n\r\n/// Combine the sub enum declarations with its enum variants\r\nfn combine_variants(\r\n  sub_enums: Vec<SubenumDeclaration>,\r\n  variants: Vec<variant_attr::SubenumVariant>,\r\n  defaults: &proc_macro2::TokenStream,\r\n) -> Vec<SubEnum<'_>> {\r\n  sub_enums\r\n    .into_iter()\r\n    .map(|sub_enum| {\r\n      let mut combined_variants = Vec::new();\r\n      for variant in &variants {\r\n        if variant.enums.contains(&sub_enum.name) {\r\n          combined_variants.push(Variant {\r\n            name: variant.name.clone(),\r\n            contained: variant.contained.clone(),\r\n          });\r\n        }\r\n      }\r\n      SubEnum {\r\n        defaults,\r\n        name: sub_enum.name,\r\n        attrs: sub_enum.attrs,\r\n        variants: combined_variants,\r\n      }\r\n    })\r\n    .collect()\r\n}\r\n\r\n/// Create a `impl From<sub_enum> for name` block\r\nfn from_sub_to_main_impl(\r\n  name: &syn::Ident,\r\n  sub_enum: &SubEnum,\r\n) -> proc_macro2::TokenStream {\r\n  let sub_name = &sub_enum.name;\r\n\r\n  let variants = sub_enum.variants.iter().map(|v| {\r\n    let var_name = &v.name;\r\n    quote::quote! {\r\n      #sub_name::#var_name(v) => #name::#var_name(v)\r\n    }\r\n  });\r\n\r\n  quote::quote! {\r\n    impl From<#sub_name> for #name {\r\n      fn from(value: #sub_name) -> Self {\r\n        match value {\r\n          #(#variants),*\r\n        }\r\n      }\r\n    }\r\n  }\r\n}\r\n\r\n/// Create a `impl TryFrom<name> for sub_enum` block\r\nfn try_from_main_to_sub_impl(\r\n  name: &syn::Ident,\r\n  sub_enum: &SubEnum,\r\n) -> proc_macro2::TokenStream {\r\n  let sub_name = &sub_enum.name;\r\n\r\n  let variants = sub_enum.variants.iter().map(|v| {\r\n    let var_name = &v.name;\r\n    quote::quote! {\r\n      #name::#var_name(v) => Ok(#sub_name::#var_name(v))\r\n    }\r\n  });\r\n\r\n  let error = format!(\r\n    \"Cannot convert this variant of sub enum `{sub_name}` to main enum `{name}`.\"\r\n  );\r\n\r\n  quote::quote! {\r\n    impl TryFrom<#name> for #sub_name {\r\n      type Error = &'static str;\r\n\r\n      fn try_from(value: #name) -> Result<Self, Self::Error> {\r\n        match value {\r\n          #(#variants),*,\r\n          _ => Err(#error),\r\n        }\r\n      }\r\n    }\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-macros/src/subenum/variant_attr.rs",
    "content": "use syn::punctuated::Punctuated;\r\n\r\nuse crate::prelude::*;\r\n\r\n/// Holds the parsed data for a single subenum variant.\r\npub struct SubenumVariant {\r\n  pub name: syn::Ident,\r\n  pub contained: syn::Type,\r\n  pub enums: Vec<syn::Ident>,\r\n}\r\n\r\n/// Parse a single subenum variant from a [syn::Variant].\r\npub fn parse_variant(\r\n  variant: &syn::Variant,\r\n) -> syn::Result<SubenumVariant> {\r\n  let name = variant.ident.clone();\r\n  let mut contained_iter = variant.fields.iter();\r\n  let contained = contained_iter\r\n    .next()\r\n    .map(|field| field.ty.clone())\r\n    .ok_or_else(|| {\r\n      variant.error(\"Subenum variants must have a contained type\")\r\n    })?;\r\n\r\n  contained_iter.next().is_some().then_error(\r\n    variant.error(\"Subenum variants must have exactly one contained type\"),\r\n  )?;\r\n\r\n  let enums = if let Some(enums) = variant\r\n    .attrs\r\n    .find_list_attrs(crate::subenum::SUBENUM_ATTR_NAME)\r\n    .map(|attr| {\r\n      attr.parse_args_with(\r\n        Punctuated::<syn::Ident, syn::Token![,]>::parse_terminated,\r\n      )\r\n    })\r\n    .reduce(|acc, el| {\r\n      acc.and_then(|mut acc| {\r\n        el.map(|el| {\r\n          acc.extend(el);\r\n          acc\r\n        })\r\n      })\r\n    }) {\r\n    enums.map(|list| list.iter().cloned().collect::<Vec<_>>())?\r\n  } else {\r\n    vec![]\r\n  };\r\n\r\n  Ok(SubenumVariant {\r\n    name,\r\n    enums,\r\n    contained,\r\n  })\r\n}\r\n"
  },
  {
    "path": "packages/wm-platform/Cargo.toml",
    "content": "[package]\r\nname = \"wm-platform\"\r\nversion = \"0.0.0\"\r\nedition = \"2021\"\r\n\r\n[lib]\r\npath = \"src/lib.rs\"\r\ntest = false\r\n\r\n[[test]]\r\nname = \"test\"\r\nharness = false\r\npath = \"src/test.rs\"\r\n\r\n[dependencies]\r\nhome = { workspace = true }\r\nthiserror = { workspace = true }\r\ntokio = { workspace = true }\r\ntracing = { workspace = true }\r\nserde = { workspace = true }\r\nregex = { workspace = true }\r\n\r\n[dev-dependencies]\r\nlibtest-mimic-collect = \"0.3.2\"\r\n\r\n[target.'cfg(target_os = \"windows\")'.dependencies]\r\nwindows = { version = \"0.52\", features = [\r\n  \"implement\",\r\n  \"Win32_Devices_HumanInterfaceDevice\",\r\n  \"Win32_Foundation\",\r\n  \"Win32_Graphics_Dwm\",\r\n  \"Win32_Graphics_Gdi\",\r\n  \"Win32_Security\",\r\n  \"Win32_System_Com\",\r\n  \"Win32_System_Environment\",\r\n  \"Win32_System_LibraryLoader\",\r\n  \"Win32_System_Registry\",\r\n  \"Win32_System_RemoteDesktop\",\r\n  \"Win32_System_SystemServices\",\r\n  \"Win32_System_Threading\",\r\n  \"Win32_UI_Accessibility\",\r\n  \"Win32_UI_HiDpi\",\r\n  \"Win32_UI_Input_Ime\",\r\n  \"Win32_UI_Input_KeyboardAndMouse\",\r\n  \"Win32_UI_Shell_Common\",\r\n  \"Win32_UI_TextServices\",\r\n  \"Win32_UI_WindowsAndMessaging\",\r\n] }\r\nwindows-interface = { version = \"0.52\" }\r\n\r\n[target.'cfg(target_os = \"macos\")'.dependencies]\r\nobjc2-core-graphics = \"0.3.2\"\r\nobjc2 = \"0.6.4\"\r\nobjc2-app-kit = { version = \"0.3.2\", default-features = false, features = [\r\n  \"NSAlert\",\r\n  \"NSApplication\",\r\n  \"NSEvent\",\r\n  \"NSGraphics\",\r\n  \"NSRunningApplication\",\r\n  \"NSResponder\",\r\n  \"NSScreen\",\r\n  \"NSWindow\",\r\n  \"NSWorkspace\",\r\n  \"libc\",\r\n  \"objc2-core-foundation\",\r\n] }\r\nobjc2-application-services = \"0.3.2\"\r\nobjc2-core-foundation = { version = \"0.3.2\", features = [\r\n  \"CFCGTypes\",\r\n  \"CFUUID\",\r\n] }\r\nobjc2-foundation = { version = \"0.3.2\", default-features = false, features = [\r\n  \"NSArray\",\r\n  \"NSEnumerator\",\r\n  \"NSNotification\",\r\n  \"NSKeyValueCoding\",\r\n  \"NSString\",\r\n  \"NSThread\",\r\n  \"NSValue\",\r\n] }\r\n"
  },
  {
    "path": "packages/wm-platform/build.rs",
    "content": "fn main() {\r\n  #[cfg(target_os = \"macos\")]\r\n  println!(\r\n    \"cargo:rustc-link-search=framework=/System/Library/PrivateFrameworks\"\r\n  );\r\n}\r\n"
  },
  {
    "path": "packages/wm-platform/src/dispatcher.rs",
    "content": "use std::{\r\n  path::Path,\r\n  sync::{\r\n    atomic::{AtomicBool, Ordering},\r\n    Arc,\r\n  },\r\n  thread::ThreadId,\r\n};\r\n\r\n#[cfg(target_os = \"macos\")]\r\nuse objc2::MainThreadMarker;\r\n#[cfg(target_os = \"macos\")]\r\nuse objc2_app_kit::{NSAlert, NSAlertStyle, NSEvent};\r\n#[cfg(target_os = \"macos\")]\r\nuse objc2_application_services::{\r\n  kAXTrustedCheckOptionPrompt, AXIsProcessTrustedWithOptions,\r\n};\r\n#[cfg(target_os = \"macos\")]\r\nuse objc2_core_foundation::{CFBoolean, CFDictionary, CGPoint};\r\n#[cfg(target_os = \"macos\")]\r\nuse objc2_core_graphics::{CGError, CGEvent, CGWarpMouseCursorPosition};\r\n#[cfg(target_os = \"macos\")]\r\nuse objc2_foundation::NSString;\r\n#[cfg(target_os = \"windows\")]\r\nuse windows::{\r\n  core::PCWSTR,\r\n  Win32::{\r\n    Foundation::POINT,\r\n    System::Environment::ExpandEnvironmentStringsW,\r\n    UI::{\r\n      Input::KeyboardAndMouse::{\r\n        GetAsyncKeyState, VK_LBUTTON, VK_RBUTTON,\r\n      },\r\n      Shell::{\r\n        ShellExecuteExW, SEE_MASK_NOASYNC, SEE_MASK_NOCLOSEPROCESS,\r\n        SHELLEXECUTEINFOW,\r\n      },\r\n      WindowsAndMessaging::{\r\n        GetCursorPos, MessageBoxW, SetCursorPos, SystemParametersInfoW,\r\n        ANIMATIONINFO, MB_ICONERROR, MB_OK, MB_SYSTEMMODAL,\r\n        SPIF_SENDCHANGE, SPIF_UPDATEINIFILE, SPI_GETANIMATION,\r\n        SPI_SETANIMATION, SW_HIDE, SW_NORMAL,\r\n        SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS,\r\n      },\r\n    },\r\n  },\r\n};\r\n\r\n#[cfg(target_os = \"macos\")]\r\nuse crate::platform_impl::Application;\r\nuse crate::{\r\n  platform_impl, Display, DisplayDevice, MouseButton, NativeWindow, Point,\r\n};\r\n\r\n/// Type alias for a closure to be executed by the event loop.\r\npub type DispatchFn = dyn FnOnce() + Send + 'static;\r\n\r\n/// A callback that pre-processes window procedure messages received by the\r\n/// event loop.\r\n///\r\n/// Mirrors the Win32 [`WNDPROC`] signature. Returns `Some(lresult)` if\r\n/// the message was handled, or `None` to pass it along.\r\n///\r\n/// [`WNDPROC`]: https://learn.microsoft.com/en-us/windows/win32/api/winuser/nc-winuser-wndproc\r\npub type WndProcCallback =\r\n  dyn Fn(isize, u32, usize, isize) -> Option<isize> + Send + 'static;\r\n\r\n/// macOS-specific extension trait for [`Dispatcher`].\r\n#[cfg(target_os = \"macos\")]\r\npub trait DispatcherExtMacOs {\r\n  /// Gets all running applications.\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// This method is only available on macOS.\r\n  fn all_applications(&self) -> crate::Result<Vec<Application>>;\r\n\r\n  /// Checks whether accessibility permissions are granted.\r\n  ///\r\n  /// If `prompt` is `true`, a dialog will be shown to the user to request\r\n  /// accessibility permissions.\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// This method is only available on macOS.\r\n  fn has_ax_permission(&self, prompt: bool) -> bool;\r\n}\r\n\r\n#[cfg(target_os = \"macos\")]\r\nimpl DispatcherExtMacOs for Dispatcher {\r\n  fn all_applications(&self) -> crate::Result<Vec<Application>> {\r\n    platform_impl::all_applications(self)\r\n  }\r\n\r\n  fn has_ax_permission(&self, prompt: bool) -> bool {\r\n    let options = CFDictionary::from_slices(\r\n      &[unsafe { kAXTrustedCheckOptionPrompt }],\r\n      &[CFBoolean::new(prompt)],\r\n    );\r\n\r\n    unsafe { AXIsProcessTrustedWithOptions(Some(options.as_ref())) }\r\n  }\r\n}\r\n\r\n/// Windows-specific extensions for `Dispatcher`.\r\n#[cfg(target_os = \"windows\")]\r\npub trait DispatcherExtWindows {\r\n  /// Returns the handle of the event loop's message window.\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// This method is only available on Windows.\r\n  fn message_window_handle(&self) -> isize;\r\n\r\n  /// Registers a callback to pre-process messages in the event loop's\r\n  /// window procedure.\r\n  ///\r\n  /// Returns a unique ID that can be passed to\r\n  /// `deregister_wndproc_callback` to remove the callback.\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// This method is only available on Windows.\r\n  fn register_wndproc_callback(\r\n    &self,\r\n    callback: Box<crate::WndProcCallback>,\r\n  ) -> crate::Result<usize>;\r\n\r\n  /// Removes a previously registered window procedure callback by its ID.\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// This method is only available on Windows.\r\n  fn deregister_wndproc_callback(&self, id: usize) -> crate::Result<()>;\r\n\r\n  /// Gets whether system-wide window transition animations are enabled.\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// This method is only available on Windows.\r\n  fn window_animations_enabled(&self) -> crate::Result<bool>;\r\n\r\n  /// Enables or disables system-wide window transition animations.\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// This method is only available on Windows.\r\n  fn set_window_animations_enabled(\r\n    &self,\r\n    enable: bool,\r\n  ) -> crate::Result<()>;\r\n\r\n  /// Expands `%VAR%` environment variable references in `input`.\r\n  ///\r\n  /// Returns the expanded string.\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// This method is only available on Windows.\r\n  ///\r\n  /// TODO: Remove this. Handle environment variable expansion in a\r\n  /// unified, cross-platform way.\r\n  fn expand_env_strings(&self, input: &str) -> crate::Result<String>;\r\n\r\n  /// Runs the specified program using `ShellExecuteExW`.\r\n  ///\r\n  /// If `hide_window` is `true`, the spawned process window is hidden.\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// This method is only available on Windows.\r\n  ///\r\n  /// TODO: Remove this. Use `shell_util::Shell::spawn` instead.\r\n  fn shell_execute_ex(\r\n    &self,\r\n    program: &str,\r\n    args: &str,\r\n    directory: &Path,\r\n    hide_window: bool,\r\n  ) -> crate::Result<()>;\r\n}\r\n\r\n#[cfg(target_os = \"windows\")]\r\nimpl DispatcherExtWindows for Dispatcher {\r\n  fn message_window_handle(&self) -> isize {\r\n    self.source.as_ref().unwrap().message_window_handle\r\n  }\r\n\r\n  fn register_wndproc_callback(\r\n    &self,\r\n    callback: Box<crate::WndProcCallback>,\r\n  ) -> crate::Result<usize> {\r\n    self\r\n      .source\r\n      .as_ref()\r\n      .unwrap()\r\n      .register_wndproc_callback(callback)\r\n  }\r\n\r\n  fn deregister_wndproc_callback(&self, id: usize) -> crate::Result<()> {\r\n    self\r\n      .source\r\n      .as_ref()\r\n      .unwrap()\r\n      .deregister_wndproc_callback(id)\r\n  }\r\n\r\n  fn window_animations_enabled(&self) -> crate::Result<bool> {\r\n    let mut animation_info = ANIMATIONINFO {\r\n      #[allow(clippy::cast_possible_truncation)]\r\n      cbSize: std::mem::size_of::<ANIMATIONINFO>() as u32,\r\n      iMinAnimate: 0,\r\n    };\r\n\r\n    unsafe {\r\n      SystemParametersInfoW(\r\n        SPI_GETANIMATION,\r\n        animation_info.cbSize,\r\n        Some(std::ptr::from_mut(&mut animation_info).cast()),\r\n        SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS(0),\r\n      )\r\n    }?;\r\n\r\n    Ok(animation_info.iMinAnimate != 0)\r\n  }\r\n\r\n  fn set_window_animations_enabled(\r\n    &self,\r\n    enable: bool,\r\n  ) -> crate::Result<()> {\r\n    let mut animation_info = ANIMATIONINFO {\r\n      #[allow(clippy::cast_possible_truncation)]\r\n      cbSize: std::mem::size_of::<ANIMATIONINFO>() as u32,\r\n      iMinAnimate: i32::from(enable),\r\n    };\r\n\r\n    unsafe {\r\n      SystemParametersInfoW(\r\n        SPI_SETANIMATION,\r\n        animation_info.cbSize,\r\n        Some(std::ptr::from_mut(&mut animation_info).cast()),\r\n        SPIF_UPDATEINIFILE | SPIF_SENDCHANGE,\r\n      )\r\n    }?;\r\n\r\n    Ok(())\r\n  }\r\n\r\n  fn expand_env_strings(&self, input: &str) -> crate::Result<String> {\r\n    let wide_input =\r\n      input.encode_utf16().chain(Some(0)).collect::<Vec<_>>();\r\n\r\n    let size = unsafe {\r\n      ExpandEnvironmentStringsW(PCWSTR(wide_input.as_ptr()), None)\r\n    };\r\n\r\n    if size == 0 {\r\n      return Err(crate::Error::Platform(format!(\r\n        \"Failed to expand environment strings in '{input}'.\",\r\n      )));\r\n    }\r\n\r\n    let mut buffer = vec![0u16; size as usize];\r\n    let size = unsafe {\r\n      ExpandEnvironmentStringsW(\r\n        PCWSTR(wide_input.as_ptr()),\r\n        Some(&mut buffer),\r\n      )\r\n    };\r\n\r\n    // The size includes the null terminator, so subtract one.\r\n    Ok(String::from_utf16_lossy(&buffer[..(size - 1) as usize]))\r\n  }\r\n\r\n  fn shell_execute_ex(\r\n    &self,\r\n    program: &str,\r\n    args: &str,\r\n    directory: &Path,\r\n    hide_window: bool,\r\n  ) -> crate::Result<()> {\r\n    let program_wide =\r\n      program.encode_utf16().chain(Some(0)).collect::<Vec<_>>();\r\n    let args_wide = args.encode_utf16().chain(Some(0)).collect::<Vec<_>>();\r\n    let directory_wide = directory\r\n      .to_string_lossy()\r\n      .encode_utf16()\r\n      .chain(Some(0))\r\n      .collect::<Vec<_>>();\r\n\r\n    let mut exec_info = SHELLEXECUTEINFOW {\r\n      #[allow(clippy::cast_possible_truncation)]\r\n      cbSize: std::mem::size_of::<SHELLEXECUTEINFOW>() as u32,\r\n      lpFile: PCWSTR(program_wide.as_ptr()),\r\n      lpParameters: PCWSTR(args_wide.as_ptr()),\r\n      lpDirectory: PCWSTR(directory_wide.as_ptr()),\r\n      nShow: if hide_window { SW_HIDE } else { SW_NORMAL }.0 as _,\r\n      fMask: SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NOASYNC,\r\n      ..Default::default()\r\n    };\r\n\r\n    unsafe { ShellExecuteExW(&raw mut exec_info) }\r\n      .map_err(crate::Error::from)\r\n  }\r\n}\r\n\r\n/// A thread-safe dispatcher for cross-platform window management\r\n/// operations.\r\n///\r\n/// On macOS, operations are automatically dispatched to the main thread\r\n/// whenever necessary.\r\n///\r\n/// # Thread safety\r\n///\r\n/// This type is `Send + Sync` and can be cheaply cloned and shared across\r\n/// threads.\r\n///\r\n/// # Example usage\r\n///\r\n/// ```rust,no_run\r\n/// use wm_platform::EventLoop;\r\n/// use std::thread;\r\n///\r\n/// # fn main() -> wm_platform::Result<()> {\r\n/// let (event_loop, dispatcher) = EventLoop::new()?;\r\n///\r\n/// // Dispatch from another thread.\r\n/// thread::spawn(move || {\r\n///   dispatcher.dispatch_async(|| {\r\n///     println!(\"This is running on the event loop thread!\");\r\n///   }).unwrap();\r\n/// });\r\n///\r\n/// event_loop.run()\r\n/// # }\r\n/// ```\r\n#[derive(Clone)]\r\npub struct Dispatcher {\r\n  source: Option<platform_impl::EventLoopSource>,\r\n  stopped: Arc<AtomicBool>,\r\n}\r\n\r\nimpl Dispatcher {\r\n  // TODO: Allow for source to be resolved after creation when used via\r\n  // `EventLoopInstaller` (to be added).\r\n  pub(crate) fn new(\r\n    source: Option<platform_impl::EventLoopSource>,\r\n    stopped: Arc<AtomicBool>,\r\n  ) -> Self {\r\n    Self { source, stopped }\r\n  }\r\n\r\n  /// Stops the event loop gracefully from any thread.\r\n  ///\r\n  /// After calling this method, all subsequent calls to `dispatch_async()`\r\n  /// and `dispatch_sync()` will return `Error::EventLoopStopped`.\r\n  pub fn stop_event_loop(&self) -> crate::Result<()> {\r\n    // Set stopped flag to prevent new dispatches.\r\n    self.stopped.store(true, Ordering::SeqCst);\r\n\r\n    // Signal platform-specific event loop to stop.\r\n    if let Some(source) = &self.source {\r\n      source.send_stop()?;\r\n    }\r\n\r\n    Ok(())\r\n  }\r\n\r\n  /// Asynchronously executes a closure on the event loop thread.\r\n  ///\r\n  /// If the current thread is the event loop thread, the function is\r\n  /// executed directly (synchronously).\r\n  ///\r\n  /// Returns `Ok(())` if the closure was successfully queued. No result is\r\n  /// returned.\r\n  pub fn dispatch_async<F>(&self, dispatch_fn: F) -> crate::Result<()>\r\n  where\r\n    F: FnOnce() + Send + 'static,\r\n  {\r\n    // Check if stopped first.\r\n    if self.stopped.load(Ordering::SeqCst) {\r\n      return Err(crate::Error::EventLoopStopped);\r\n    }\r\n\r\n    // Execute the function directly if already on the event loop thread.\r\n    if self.is_event_loop_thread() {\r\n      dispatch_fn();\r\n      return Ok(());\r\n    }\r\n\r\n    if let Some(source) = &self.source {\r\n      // Platform-specific behavior:\r\n      // * On Windows, this uses `PostMessageW` to send callbacks via\r\n      //   window messages.\r\n      // * On macOS, this uses `CFRunLoopSourceSignal` to wake the run loop\r\n      //   and process callbacks.\r\n      source.send_dispatch_async(dispatch_fn)?;\r\n    }\r\n\r\n    Ok(())\r\n  }\r\n\r\n  /// Synchronously executes a closure on the event loop thread.\r\n  ///\r\n  /// If the current thread is the event loop thread, the function is\r\n  /// executed directly.\r\n  ///\r\n  /// Returns a `Result` with the closure's return value.\r\n  #[allow(clippy::missing_panics_doc)]\r\n  pub fn dispatch_sync<F, R>(&self, dispatch_fn: F) -> crate::Result<R>\r\n  where\r\n    F: FnOnce() -> R + Send,\r\n    R: Send,\r\n  {\r\n    // Check if stopped first.\r\n    if self.stopped.load(Ordering::SeqCst) {\r\n      return Err(crate::Error::EventLoopStopped);\r\n    }\r\n\r\n    // Execute the function directly if already on the event loop thread.\r\n    if self.is_event_loop_thread() {\r\n      return Ok(dispatch_fn());\r\n    }\r\n\r\n    let (result_tx, result_rx) = std::sync::mpsc::channel();\r\n\r\n    // TODO: Block until event loop source is set.\r\n    self.source.as_ref().unwrap().send_dispatch_sync(move || {\r\n      let result = dispatch_fn();\r\n\r\n      if result_tx.send(result).is_err() {\r\n        tracing::error!(\"Failed to send closure result.\");\r\n      }\r\n    })?;\r\n\r\n    result_rx\r\n      .recv_timeout(std::time::Duration::from_secs(5))\r\n      .map_err(crate::Error::ChannelRecv)\r\n  }\r\n\r\n  /// Gets the thread ID of the event loop thread.\r\n  #[allow(clippy::missing_panics_doc)]\r\n  #[must_use]\r\n  pub fn thread_id(&self) -> ThreadId {\r\n    // TODO: Block until event loop source is set.\r\n    self.source.as_ref().unwrap().thread_id\r\n  }\r\n\r\n  /// Gets whether the current thread is the event loop thread.\r\n  #[must_use]\r\n  fn is_event_loop_thread(&self) -> bool {\r\n    std::thread::current().id() == self.thread_id()\r\n  }\r\n\r\n  /// Gets all active displays.\r\n  ///\r\n  /// NOTE: Does not guarantee a specific, consistent order.\r\n  ///\r\n  /// Returns all displays that are currently active and available for use.\r\n  pub fn displays(&self) -> crate::Result<Vec<Display>> {\r\n    platform_impl::all_displays(self)\r\n  }\r\n\r\n  /// Gets all active displays sorted left-to-right, top-to-bottom.\r\n  ///\r\n  /// Returns all displays that are currently active and available for\r\n  /// use, sorted by their X coordinate (left edge), with ties broken\r\n  /// by Y coordinate (top edge).\r\n  ///\r\n  /// TODO: Remove this. Instead, call `sort_monitors` after populating WM\r\n  /// state. Need to assign workspaces after sorting monitors because of\r\n  /// `bind_to_monitor`.\r\n  pub fn sorted_displays(&self) -> crate::Result<Vec<Display>> {\r\n    let displays = platform_impl::all_displays(self)?;\r\n\r\n    let mut displays_with_bounds = displays\r\n      .into_iter()\r\n      .map(|display| {\r\n        let bounds = display.bounds()?;\r\n        crate::Result::Ok((display, bounds))\r\n      })\r\n      .try_collect::<Vec<_>>()?;\r\n\r\n    displays_with_bounds.sort_by(|(_, bounds_a), (_, bounds_b)| {\r\n      if bounds_a.x() == bounds_b.x() {\r\n        bounds_a.y().cmp(&bounds_b.y())\r\n      } else {\r\n        bounds_a.x().cmp(&bounds_b.x())\r\n      }\r\n    });\r\n\r\n    Ok(\r\n      displays_with_bounds\r\n        .into_iter()\r\n        .map(|(display, _)| display)\r\n        .collect(),\r\n    )\r\n  }\r\n\r\n  /// Gets all display devices.\r\n  ///\r\n  /// NOTE: Does not guarantee a specific, consistent order.\r\n  ///\r\n  /// Returns all display devices including active, inactive, and\r\n  /// disconnected ones.\r\n  pub fn display_devices(&self) -> crate::Result<Vec<DisplayDevice>> {\r\n    platform_impl::all_display_devices(self)\r\n  }\r\n\r\n  /// Gets the display containing the specified point.\r\n  ///\r\n  /// If no display contains the point, returns the primary display.\r\n  pub fn display_from_point(\r\n    &self,\r\n    point: &Point,\r\n  ) -> crate::Result<Display> {\r\n    platform_impl::display_from_point(point, self)\r\n  }\r\n\r\n  /// Gets the primary display.\r\n  pub fn primary_display(&self) -> crate::Result<Display> {\r\n    platform_impl::primary_display(self)\r\n  }\r\n\r\n  /// Gets the nearest display to a window.\r\n  ///\r\n  /// Returns the display that contains the largest area of the window's\r\n  /// frame. Defaults to the primary display if no overlap is found.\r\n  pub fn nearest_display(\r\n    &self,\r\n    native_window: &NativeWindow,\r\n  ) -> crate::Result<Display> {\r\n    platform_impl::nearest_display(native_window, self)\r\n  }\r\n\r\n  /// Gets all visible windows from all running applications.\r\n  ///\r\n  /// NOTE: Does not guarantee a specific, consistent order.\r\n  ///\r\n  /// Returns a vector of `NativeWindow` instances for windows that are\r\n  /// not hidden and on the current virtual desktop.\r\n  pub fn visible_windows(&self) -> crate::Result<Vec<NativeWindow>> {\r\n    platform_impl::visible_windows(self)\r\n  }\r\n\r\n  /// Gets the currently focused (foreground) window.\r\n  ///\r\n  /// This may be the desktop window if no window has focus.\r\n  pub fn focused_window(&self) -> crate::Result<NativeWindow> {\r\n    platform_impl::focused_window(self)\r\n  }\r\n\r\n  /// Gets the current cursor position.\r\n  pub fn cursor_position(&self) -> crate::Result<Point> {\r\n    #[cfg(target_os = \"macos\")]\r\n    {\r\n      let event = CGEvent::new(None);\r\n      let point = CGEvent::location(event.as_deref());\r\n\r\n      #[allow(clippy::cast_possible_truncation)]\r\n      Ok(Point {\r\n        x: point.x as i32,\r\n        y: point.y as i32,\r\n      })\r\n    }\r\n    #[cfg(target_os = \"windows\")]\r\n    {\r\n      let mut point = POINT { x: 0, y: 0 };\r\n      unsafe { GetCursorPos(&raw mut point) }?;\r\n\r\n      Ok(Point {\r\n        x: point.x,\r\n        y: point.y,\r\n      })\r\n    }\r\n  }\r\n\r\n  /// Gets whether the given mouse button is currently pressed.\r\n  #[must_use]\r\n  pub fn is_mouse_down(&self, button: &MouseButton) -> bool {\r\n    #[cfg(target_os = \"macos\")]\r\n    {\r\n      let bit_index = match button {\r\n        MouseButton::Left => 0usize,\r\n        MouseButton::Right => 1usize,\r\n      };\r\n\r\n      // Check if bit at corresponding index is set in the bitmask.\r\n      let pressed_mask = NSEvent::pressedMouseButtons();\r\n      (pressed_mask & (1usize << bit_index)) != 0\r\n    }\r\n    #[cfg(target_os = \"windows\")]\r\n    {\r\n      // Virtual-key codes for mouse buttons.\r\n      let vk_code = match button {\r\n        MouseButton::Left => VK_LBUTTON.0,\r\n        MouseButton::Right => VK_RBUTTON.0,\r\n      };\r\n\r\n      // High-order bit set indicates the key is currently down.\r\n      let state = unsafe { GetAsyncKeyState(vk_code.into()) };\r\n      (state.cast_unsigned() & 0x8000u16) != 0\r\n    }\r\n  }\r\n\r\n  /// Gets the top-level window at the specified point.\r\n  pub fn window_from_point(\r\n    &self,\r\n    point: &Point,\r\n  ) -> crate::Result<Option<crate::NativeWindow>> {\r\n    platform_impl::window_from_point(point, self)\r\n  }\r\n\r\n  /// Sets the cursor position to the specified coordinates.\r\n  pub fn set_cursor_position(&self, point: &Point) -> crate::Result<()> {\r\n    #[cfg(target_os = \"macos\")]\r\n    {\r\n      let point = CGPoint {\r\n        x: f64::from(point.x),\r\n        y: f64::from(point.y),\r\n      };\r\n\r\n      if CGWarpMouseCursorPosition(point) != CGError::Success {\r\n        return Err(crate::Error::Platform(\r\n          \"Failed to set cursor position.\".to_string(),\r\n        ));\r\n      }\r\n    }\r\n    #[cfg(target_os = \"windows\")]\r\n    {\r\n      unsafe { SetCursorPos(point.x, point.y) }?;\r\n    }\r\n\r\n    Ok(())\r\n  }\r\n\r\n  /// Removes focus from the current window and focuses the desktop.\r\n  pub fn reset_focus(&self) -> crate::Result<()> {\r\n    platform_impl::reset_focus(self)\r\n  }\r\n\r\n  /// Opens the operating system's file explorer at the given path.\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// - **Windows**: Uses `explorer` to open the file explorer.\r\n  /// - **macOS**: Uses `open` to open the file explorer.\r\n  pub fn open_file_explorer(&self, path: &Path) -> crate::Result<()> {\r\n    #[cfg(target_os = \"windows\")]\r\n    {\r\n      let normalized_path = std::fs::canonicalize(path)?;\r\n      std::process::Command::new(\"explorer\")\r\n        .arg(normalized_path)\r\n        .spawn()?;\r\n    }\r\n    #[cfg(target_os = \"macos\")]\r\n    {\r\n      std::process::Command::new(\"open\")\r\n        .arg(path)\r\n        .arg(\"-R\")\r\n        .spawn()?;\r\n    }\r\n\r\n    Ok(())\r\n  }\r\n\r\n  /// Shows a modal error dialog with the given title and message.\r\n  ///\r\n  /// Blocks the current thread until the user dismisses the dialog.\r\n  #[allow(clippy::missing_panics_doc)]\r\n  pub fn show_error_dialog(&self, title: &str, message: &str) {\r\n    #[cfg(target_os = \"windows\")]\r\n    {\r\n      let title_wide =\r\n        title.encode_utf16().chain(Some(0)).collect::<Vec<_>>();\r\n      let message_wide =\r\n        message.encode_utf16().chain(Some(0)).collect::<Vec<_>>();\r\n\r\n      unsafe {\r\n        MessageBoxW(\r\n          None,\r\n          PCWSTR(message_wide.as_ptr()),\r\n          PCWSTR(title_wide.as_ptr()),\r\n          MB_ICONERROR | MB_OK | MB_SYSTEMMODAL,\r\n        );\r\n      }\r\n    }\r\n    #[cfg(target_os = \"macos\")]\r\n    {\r\n      // TODO: This should block indefinitely. Currently, it gets timed out\r\n      // after 5 seconds.\r\n      let _ = self.dispatch_sync(|| {\r\n        let mtm = MainThreadMarker::new().unwrap();\r\n\r\n        let alert = NSAlert::new(mtm);\r\n        alert.setMessageText(&NSString::from_str(title));\r\n        alert.setInformativeText(&NSString::from_str(message));\r\n        alert.setAlertStyle(NSAlertStyle::Critical);\r\n        alert.runModal();\r\n      });\r\n    }\r\n  }\r\n}\r\n\r\nimpl std::fmt::Debug for Dispatcher {\r\n  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\r\n    write!(f, \"EventLoopDispatcher\")\r\n  }\r\n}\r\n\r\n#[cfg(test)]\r\nmod tests {\r\n  use std::sync::{Arc, Mutex};\r\n\r\n  use crate::EventLoop;\r\n\r\n  #[test]\r\n  fn dispatch_after_stop_fails() {\r\n    let (_event_loop, dispatcher) = EventLoop::new().unwrap();\r\n\r\n    dispatcher\r\n      .stop_event_loop()\r\n      .expect(\"Failed to stop dispatcher.\");\r\n\r\n    // Try to dispatch asynchronously - should fail.\r\n    let result = dispatcher.dispatch_sync(|| {});\r\n    assert!(matches!(result, Err(crate::Error::EventLoopStopped)));\r\n\r\n    // Try dispatch synchronously - should fail.\r\n    let sync_result: crate::Result<i32> = dispatcher.dispatch_sync(|| 69);\r\n    assert!(matches!(sync_result, Err(crate::Error::EventLoopStopped)));\r\n  }\r\n\r\n  #[test]\r\n  fn dispatch_sync_executes_in_order() {\r\n    const ITERATIONS: usize = 5000;\r\n\r\n    let (event_loop, dispatcher) = EventLoop::new().unwrap();\r\n\r\n    let order = Arc::new(Mutex::new(Vec::new()));\r\n    let order_clone = order.clone();\r\n\r\n    std::thread::spawn(move || {\r\n      for index in 1..=ITERATIONS {\r\n        dispatcher\r\n          .dispatch_sync(|| {\r\n            order_clone.lock().unwrap().push(index);\r\n          })\r\n          .unwrap();\r\n      }\r\n\r\n      dispatcher.stop_event_loop().unwrap();\r\n    });\r\n\r\n    event_loop.run().unwrap();\r\n    assert_eq!(\r\n      *order.lock().unwrap(),\r\n      (1..=ITERATIONS).collect::<Vec<_>>()\r\n    );\r\n  }\r\n\r\n  #[test]\r\n  fn dispatch_sync_from_different_threads() {\r\n    // Stress test with many threads calling `dispatch_sync`\r\n    // simultaneously. Ensure that dispatching doesn't deadlock.\r\n    const NUM_THREADS: usize = 10;\r\n    const ITERATIONS: usize = 1000;\r\n\r\n    let (event_loop, dispatcher) = EventLoop::new().unwrap();\r\n    let counter = Arc::new(Mutex::new(0));\r\n\r\n    let thread_handles: Vec<_> = (0..NUM_THREADS)\r\n      .map(|_| {\r\n        let counter = counter.clone();\r\n        let dispatcher = dispatcher.clone();\r\n        std::thread::spawn(move || {\r\n          for _ in 0..ITERATIONS {\r\n            dispatcher\r\n              .dispatch_sync(|| {\r\n                let mut count = counter.lock().unwrap();\r\n                *count += 1;\r\n              })\r\n              .unwrap();\r\n          }\r\n        })\r\n      })\r\n      .collect();\r\n\r\n    std::thread::spawn(move || {\r\n      // Wait for all threads to finish.\r\n      for handle in thread_handles {\r\n        handle.join().unwrap();\r\n      }\r\n      dispatcher.stop_event_loop().unwrap();\r\n    });\r\n\r\n    event_loop.run().unwrap();\r\n\r\n    assert_eq!(*counter.lock().unwrap(), NUM_THREADS * ITERATIONS);\r\n  }\r\n\r\n  #[test]\r\n  fn dispatch_sync_with_nested() {\r\n    // Test that calling `dispatch_sync` from within a `dispatch_sync`\r\n    // callback works correctly (should execute directly without blocking).\r\n    let (event_loop, dispatcher) = EventLoop::new().unwrap();\r\n    let result = Arc::new(Mutex::new(Vec::new()));\r\n\r\n    let result_clone = result.clone();\r\n    std::thread::spawn(move || {\r\n      dispatcher\r\n        .dispatch_sync(|| {\r\n          result_clone.lock().unwrap().push(1);\r\n\r\n          // Nested `dispatch_sync` - should execute immediately since it's\r\n          // already on the event loop thread.\r\n          dispatcher\r\n            .dispatch_sync(|| {\r\n              result_clone.lock().unwrap().push(2);\r\n            })\r\n            .unwrap();\r\n\r\n          result_clone.lock().unwrap().push(3);\r\n        })\r\n        .unwrap();\r\n\r\n      dispatcher.stop_event_loop().unwrap();\r\n    });\r\n\r\n    event_loop.run().unwrap();\r\n    assert_eq!(*result.lock().unwrap(), vec![1, 2, 3]);\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-platform/src/display.rs",
    "content": "#[cfg(target_os = \"macos\")]\r\nuse objc2::rc::Retained;\r\n#[cfg(target_os = \"macos\")]\r\nuse objc2_app_kit::NSScreen;\r\n#[cfg(target_os = \"macos\")]\r\nuse objc2_core_graphics::CGDirectDisplayID;\r\n#[cfg(target_os = \"windows\")]\r\nuse windows::Win32::Graphics::Gdi::HMONITOR;\r\n\r\n#[cfg(target_os = \"macos\")]\r\nuse crate::ThreadBound;\r\nuse crate::{platform_impl, Rect};\r\n\r\n/// Unique identifier for a display.\r\n///\r\n/// Can be obtained with `display.id()`.\r\n///\r\n/// # Platform-specific\r\n///\r\n/// - **Windows**: `isize` (`HMONITOR`)\r\n/// - **macOS**: `u32` (`CGDirectDisplayID`)\r\n#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]\r\npub struct DisplayId(\r\n  #[cfg(target_os = \"windows\")] pub isize,\r\n  #[cfg(target_os = \"macos\")] pub u32,\r\n);\r\n\r\n/// Unique identifier for a display device.\r\n///\r\n/// Can be obtained with `display_device.id()`.\r\n///\r\n/// # Platform-specific\r\n///\r\n/// - **Windows**: Hardware ID string with fallback to adapter name.\r\n/// - **macOS**: UUID string from `CGDisplayCreateUUIDFromDisplayID`.\r\n#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]\r\npub struct DisplayDeviceId(pub String);\r\n\r\n/// macOS-specific extension trait for [`Display`].\r\n#[cfg(target_os = \"macos\")]\r\npub trait DisplayExtMacOs {\r\n  /// Gets the Core Graphics display ID.\r\n  fn cg_display_id(&self) -> CGDirectDisplayID;\r\n\r\n  /// Gets the `NSScreen` instance for this display.\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// This method is only available on macOS.\r\n  fn ns_screen(&self) -> &ThreadBound<Retained<NSScreen>>;\r\n}\r\n\r\n#[cfg(target_os = \"macos\")]\r\nimpl DisplayExtMacOs for Display {\r\n  fn cg_display_id(&self) -> CGDirectDisplayID {\r\n    self.inner.cg_display_id()\r\n  }\r\n\r\n  fn ns_screen(&self) -> &ThreadBound<Retained<NSScreen>> {\r\n    self.inner.ns_screen()\r\n  }\r\n}\r\n\r\n/// Windows-specific extensions for [`Display`].\r\n#[cfg(target_os = \"windows\")]\r\npub trait DisplayExtWindows {\r\n  /// Gets the monitor handle.\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// This method is only available on Windows.\r\n  fn hmonitor(&self) -> HMONITOR;\r\n}\r\n\r\n#[cfg(target_os = \"windows\")]\r\nimpl DisplayExtWindows for Display {\r\n  fn hmonitor(&self) -> HMONITOR {\r\n    self.inner.hmonitor()\r\n  }\r\n}\r\n\r\n/// Represents a logical display space where windows can be placed.\r\n///\r\n/// # Platform-specific\r\n///\r\n/// - **Windows**: This corresponds to a Win32 \"display monitor\", each with\r\n///   a monitor handle (`HMONITOR`).\r\n/// - **macOS**: This corresponds to an `NSScreen`.\r\n#[derive(Clone, Debug, PartialEq, Eq)]\r\npub struct Display {\r\n  pub(crate) inner: platform_impl::Display,\r\n}\r\n\r\nimpl Display {\r\n  /// Gets the unique identifier for this display.\r\n  #[must_use]\r\n  pub fn id(&self) -> DisplayId {\r\n    self.inner.id()\r\n  }\r\n\r\n  /// Gets the display name.\r\n  pub fn name(&self) -> crate::Result<String> {\r\n    self.inner.name()\r\n  }\r\n\r\n  /// Gets the full bounds rectangle of the display.\r\n  pub fn bounds(&self) -> crate::Result<Rect> {\r\n    self.inner.bounds()\r\n  }\r\n\r\n  /// Gets the working area rectangle (excluding system UI).\r\n  pub fn working_area(&self) -> crate::Result<Rect> {\r\n    self.inner.working_area()\r\n  }\r\n\r\n  /// Gets the scale factor for the display.\r\n  pub fn scale_factor(&self) -> crate::Result<f32> {\r\n    self.inner.scale_factor()\r\n  }\r\n\r\n  /// Gets the DPI for the display.\r\n  pub fn dpi(&self) -> crate::Result<u32> {\r\n    self.inner.dpi()\r\n  }\r\n\r\n  /// Returns whether this is the primary display.\r\n  pub fn is_primary(&self) -> crate::Result<bool> {\r\n    self.inner.is_primary()\r\n  }\r\n\r\n  /// Gets the display devices for this display.\r\n  ///\r\n  /// A single display can be associated with multiple display devices. For\r\n  /// example, when mirroring a display or combining multiple displays\r\n  /// (e.g. using NVIDIA Surround).\r\n  pub fn devices(&self) -> crate::Result<Vec<DisplayDevice>> {\r\n    self.inner.devices()\r\n  }\r\n\r\n  /// Gets the main device (first non-mirroring device) for this display.\r\n  pub fn main_device(&self) -> crate::Result<DisplayDevice> {\r\n    self.inner.main_device()\r\n  }\r\n}\r\n\r\n/// Connection state of a display device.\r\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\r\npub enum ConnectionState {\r\n  /// Device is connected and part of the desktop coordinate space.\r\n  Active,\r\n\r\n  /// Device is connected but inactive (i.e. on standby or in sleep mode).\r\n  Inactive,\r\n\r\n  /// Device is disconnected.\r\n  Disconnected,\r\n}\r\n\r\n/// Mirroring state of a display device.\r\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\r\npub enum MirroringState {\r\n  /// This device is the source being mirrored.\r\n  Source,\r\n\r\n  /// This device is mirroring another (target).\r\n  Target,\r\n}\r\n\r\n/// Display connection type for physical devices.\r\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\r\npub enum OutputTechnology {\r\n  /// Built-in display (laptop screen).\r\n  Internal,\r\n  /// VGA connection.\r\n  VGA,\r\n  /// DVI connection.\r\n  DVI,\r\n  /// HDMI connection.\r\n  HDMI,\r\n  /// DisplayPort connection.\r\n  DisplayPort,\r\n  /// Thunderbolt connection.\r\n  Thunderbolt,\r\n  /// USB connection.\r\n  USB,\r\n  /// Wireless connection.\r\n  Wireless,\r\n  /// Unknown connection type.\r\n  Unknown,\r\n}\r\n\r\n/// macOS-specific extension trait for [`DisplayDevice`].\r\n#[cfg(target_os = \"macos\")]\r\npub trait DisplayDeviceExtMacOs {\r\n  /// Gets the Core Graphics display ID.\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// This method is only available on macOS.\r\n  fn cg_display_id(&self) -> CGDirectDisplayID;\r\n}\r\n\r\n#[cfg(target_os = \"macos\")]\r\nimpl DisplayDeviceExtMacOs for DisplayDevice {\r\n  fn cg_display_id(&self) -> CGDirectDisplayID {\r\n    self.inner.cg_display_id()\r\n  }\r\n}\r\n\r\n/// Windows-specific extensions for [`DisplayDevice`].\r\n#[cfg(target_os = \"windows\")]\r\npub trait DisplayDeviceExtWindows {\r\n  /// Gets the device path.\r\n  ///\r\n  /// This can be an empty string for virtual display devices.\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// This method is only available on Windows.\r\n  fn device_path(&self) -> Option<String>;\r\n\r\n  /// Gets the hardware ID from the device path.\r\n  ///\r\n  /// # Example usage\r\n  ///\r\n  /// ```rust,no_run\r\n  /// device.device_path(); // \"\\\\?\\DISPLAY#DEL40A3#5&1234abcd&0&UID256#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}\"\r\n  /// device.hardware_id(); // Some(\"DEL40A3\")\r\n  /// ```\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// This method is only available on Windows.\r\n  fn hardware_id(&self) -> Option<String>;\r\n\r\n  /// Gets the output technology.\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// This method is only available on Windows.\r\n  fn output_technology(&self) -> crate::Result<Option<OutputTechnology>>;\r\n}\r\n\r\n#[cfg(target_os = \"windows\")]\r\nimpl DisplayDeviceExtWindows for DisplayDevice {\r\n  fn device_path(&self) -> Option<String> {\r\n    self.inner.device_path.clone()\r\n  }\r\n\r\n  fn hardware_id(&self) -> Option<String> {\r\n    self.inner.hardware_id()\r\n  }\r\n\r\n  fn output_technology(&self) -> crate::Result<Option<OutputTechnology>> {\r\n    self.inner.output_technology()\r\n  }\r\n}\r\n\r\n/// Represents a display device (physical or virtual).\r\n///\r\n/// This is typically a physical display device, such as a monitor or\r\n/// built-in laptop screen.\r\n#[derive(Clone, Debug, PartialEq, Eq)]\r\npub struct DisplayDevice {\r\n  pub(crate) inner: platform_impl::DisplayDevice,\r\n}\r\n\r\nimpl DisplayDevice {\r\n  /// Gets the unique identifier for this display device.\r\n  #[must_use]\r\n  pub fn id(&self) -> DisplayDeviceId {\r\n    self.inner.id()\r\n  }\r\n\r\n  /// Gets the rotation of the device in degrees.\r\n  pub fn rotation(&self) -> crate::Result<f32> {\r\n    self.inner.rotation()\r\n  }\r\n\r\n  /// Gets the refresh rate of the device in Hz.\r\n  pub fn refresh_rate(&self) -> crate::Result<f32> {\r\n    self.inner.refresh_rate()\r\n  }\r\n\r\n  /// Gets whether this is a built-in display.\r\n  ///\r\n  /// Returns `true` for embedded displays (like laptop screens).\r\n  pub fn is_builtin(&self) -> crate::Result<bool> {\r\n    self.inner.is_builtin()\r\n  }\r\n\r\n  /// Gets the connection state of the device.\r\n  pub fn connection_state(&self) -> crate::Result<ConnectionState> {\r\n    self.inner.connection_state()\r\n  }\r\n\r\n  /// Gets the mirroring state of the device.\r\n  pub fn mirroring_state(&self) -> crate::Result<Option<MirroringState>> {\r\n    self.inner.mirroring_state()\r\n  }\r\n}\r\n\r\n#[cfg(test)]\r\nmod tests {\r\n  use super::*;\r\n  use crate::EventLoop;\r\n\r\n  #[test]\r\n  fn test_nearest_display() {\r\n    let (event_loop, dispatcher) = EventLoop::new().unwrap();\r\n\r\n    let thread = std::thread::spawn(move || {\r\n      let display = platform_impl::nearest_display(\r\n        // Assumes that there is at least one window currently visible.\r\n        &dispatcher.visible_windows().unwrap()[0],\r\n        &dispatcher,\r\n      );\r\n      dispatcher.stop_event_loop().unwrap();\r\n      display\r\n    });\r\n\r\n    event_loop.run().unwrap();\r\n    let display = thread.join().unwrap();\r\n    assert!(display.is_ok());\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-platform/src/display_listener.rs",
    "content": "use tokio::sync::mpsc;\r\n\r\nuse crate::{platform_impl, Dispatcher};\r\n\r\n/// A listener for system-wide display setting changes.\r\n///\r\n/// Detects changes to display configuration including resolution changes,\r\n/// display connections/disconnections, and working area changes.\r\npub struct DisplayListener {\r\n  event_rx: mpsc::UnboundedReceiver<()>,\r\n\r\n  /// Inner platform-specific display listener.\r\n  inner: platform_impl::DisplayListener,\r\n}\r\n\r\nimpl DisplayListener {\r\n  /// Creates a new [`DisplayListener`].\r\n  pub fn new(dispatcher: &Dispatcher) -> crate::Result<Self> {\r\n    let (event_tx, event_rx) = mpsc::unbounded_channel();\r\n    let inner = platform_impl::DisplayListener::new(event_tx, dispatcher)?;\r\n    Ok(Self { event_rx, inner })\r\n  }\r\n\r\n  /// Returns when the next display settings change is detected.\r\n  ///\r\n  /// Returns `None` if the channel has been closed.\r\n  pub async fn next_event(&mut self) -> Option<()> {\r\n    self.event_rx.recv().await\r\n  }\r\n\r\n  /// Terminates the display listener.\r\n  pub fn terminate(&mut self) -> crate::Result<()> {\r\n    self.inner.terminate()\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-platform/src/error.rs",
    "content": "#[derive(Debug, thiserror::Error)]\r\npub enum Error {\r\n  #[error(transparent)]\r\n  Io(#[from] std::io::Error),\r\n\r\n  #[error(transparent)]\r\n  #[cfg(target_os = \"windows\")]\r\n  Windows(#[from] windows::core::Error),\r\n\r\n  #[error(\"Accessibility operation failed for attribute {0} with error code: {1}\")]\r\n  #[cfg(target_os = \"macos\")]\r\n  Accessibility(String, i32),\r\n\r\n  #[error(transparent)]\r\n  Parse(#[from] ParseError),\r\n\r\n  #[error(\"Invalid pointer: {0}\")]\r\n  InvalidPointer(String),\r\n\r\n  #[error(\"AXValue creation failed: {0}\")]\r\n  #[cfg(target_os = \"macos\")]\r\n  AXValueCreation(String),\r\n\r\n  #[error(transparent)]\r\n  ChannelRecv(#[from] std::sync::mpsc::RecvTimeoutError),\r\n\r\n  #[error(\"Channel receive error\")]\r\n  OneshotRecv(#[from] tokio::sync::oneshot::error::RecvError),\r\n\r\n  #[error(transparent)]\r\n  IntConversion(#[from] std::num::TryFromIntError),\r\n\r\n  #[error(\"Channel send error\")]\r\n  ChannelSend,\r\n\r\n  #[error(\"Display enumeration failed\")]\r\n  DisplayEnumerationFailed,\r\n\r\n  #[error(\"Display mode not found\")]\r\n  DisplayModeNotFound,\r\n\r\n  #[error(\"Primary display not found\")]\r\n  PrimaryDisplayNotFound,\r\n\r\n  #[error(\"Not main thread\")]\r\n  NotMainThread,\r\n\r\n  #[error(\"Display not found\")]\r\n  DisplayNotFound,\r\n\r\n  #[error(\"Display device not found\")]\r\n  DisplayDeviceNotFound,\r\n\r\n  #[error(\"Hardware enumeration failed\")]\r\n  HardwareEnumerationFailed,\r\n\r\n  #[error(\"Window enumeration failed\")]\r\n  WindowEnumerationFailed,\r\n\r\n  #[error(\"Window not found\")]\r\n  WindowNotFound,\r\n\r\n  #[error(\"Thread error: {0}\")]\r\n  Thread(String),\r\n\r\n  #[error(\"Window message error: {0}\")]\r\n  WindowMessage(String),\r\n\r\n  #[error(\"Platform error: {0}\")]\r\n  Platform(String),\r\n\r\n  #[error(\"Event loop has been stopped\")]\r\n  EventLoopStopped,\r\n\r\n  #[error(\"Keybinding is empty\")]\r\n  InvalidKeybinding,\r\n}\r\n\r\n#[derive(Debug, thiserror::Error)]\r\npub enum ParseError {\r\n  #[error(\r\n    \"Invalid length value '{0}': must be of format '10px' or '10%'.\"\r\n  )]\r\n  Length(String),\r\n\r\n  #[error(\"Invalid keybinding: {0}\")]\r\n  Keybinding(String),\r\n\r\n  #[error(\r\n    \"Invalid opacity value '{0}': must be of format '75%' or '0.75'.\"\r\n  )]\r\n  Opacity(String),\r\n\r\n  #[error(\r\n    \"Invalid color '{0}': must be of format '#RRGGBB' or '#RRGGBBAA'.\"\r\n  )]\r\n  Color(String),\r\n\r\n  #[error(\"Invalid delta value: {0}\")]\r\n  Delta(String),\r\n\r\n  #[error(\"Invalid direction '{0}': must be one of 'left', 'right', 'up', or 'down'.\")]\r\n  Direction(String),\r\n}\r\n\r\npub type Result<T> = std::result::Result<T, Error>;\r\n"
  },
  {
    "path": "packages/wm-platform/src/event_loop.rs",
    "content": "use crate::{platform_impl, Dispatcher};\r\n\r\n/// A cross-platform event loop that allows for remote dispatching via\r\n/// [`Dispatcher`].\r\n///\r\n/// Does not start pumping events until [`EventLoop::run`] is called.\r\n///\r\n/// This type is `!Send` to ensure it stays on the thread where it was\r\n/// created, as the `Drop` implementation relies on thread-specific\r\n/// cleanup.\r\n///\r\n/// # Platform-specific\r\n///\r\n/// - **macOS**: Can be created on any thread. Runs `CFRunLoopRun()`.\r\n/// - **Windows**: Can be created on any thread. Runs a Win32 message loop.\r\npub struct EventLoop {\r\n  inner: platform_impl::EventLoop,\r\n  /// Marker to ensure not `Send`.\r\n  _marker: std::marker::PhantomData<*const ()>,\r\n}\r\n\r\nimpl EventLoop {\r\n  /// Creates a new event loop and dispatcher.\r\n  pub fn new() -> crate::Result<(Self, Dispatcher)> {\r\n    let (event_loop, dispatcher) = platform_impl::EventLoop::new()?;\r\n    Ok((\r\n      Self {\r\n        inner: event_loop,\r\n        _marker: std::marker::PhantomData,\r\n      },\r\n      dispatcher,\r\n    ))\r\n  }\r\n\r\n  /// Runs the event loop, blocking the current thread until shutdown.\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// - **macOS**: Must be called from the main thread. Runs\r\n  ///   `CFRunLoopRun()`.\r\n  /// - **Windows**: Can be called from any thread. Runs Win32 message\r\n  ///   loop.\r\n  pub fn run(self) -> crate::Result<()> {\r\n    self.inner.run()\r\n  }\r\n}\r\n\r\n#[cfg(test)]\r\nmod tests {\r\n  use std::time::Duration;\r\n\r\n  use super::*;\r\n\r\n  #[test]\r\n  fn event_loop_start_stop() {\r\n    let (event_loop, dispatcher) =\r\n      EventLoop::new().expect(\"Failed to create event loop.\");\r\n\r\n    // Stop the event loop after a short delay.\r\n    let handle = std::thread::spawn(move || {\r\n      std::thread::sleep(Duration::from_millis(10));\r\n      dispatcher.stop_event_loop()\r\n    });\r\n\r\n    event_loop.run().expect(\"Failed to run event loop.\");\r\n\r\n    // Ensure the event loop is stopped.\r\n    assert!(handle.join().is_ok());\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-platform/src/keybinding_listener.rs",
    "content": "use std::{\r\n  collections::HashMap,\r\n  sync::{\r\n    atomic::{AtomicBool, Ordering},\r\n    Arc, Mutex,\r\n  },\r\n};\r\n\r\nuse tokio::sync::mpsc;\r\n\r\nuse crate::{\r\n  platform_event::KeybindingEvent, platform_impl, Dispatcher, Key,\r\n};\r\n\r\n/// Modifier key groups, where each entry maps a generic key (e.g.\r\n/// `Key::Shift`) to all its variants (e.g. `Key::LShift`, `Key::RShift`).\r\n///\r\n/// `Cmd` and `Win` are treated as aliases within the same group.\r\nconst MODIFIER_GROUPS: &[(Key, &[Key])] = &[\r\n  (Key::Shift, &[Key::Shift, Key::LShift, Key::RShift]),\r\n  (Key::Ctrl, &[Key::Ctrl, Key::LCtrl, Key::RCtrl]),\r\n  (Key::Alt, &[Key::Alt, Key::LAlt, Key::RAlt]),\r\n  (\r\n    Key::Win,\r\n    &[\r\n      Key::Win,\r\n      Key::LWin,\r\n      Key::RWin,\r\n      Key::Cmd,\r\n      Key::LCmd,\r\n      Key::RCmd,\r\n    ],\r\n  ),\r\n];\r\n\r\n#[derive(Debug, Clone, Eq, PartialEq)]\r\npub struct Keybinding(Vec<Key>);\r\n\r\nimpl Keybinding {\r\n  /// Creates a new keybinding from a vector of keys.\r\n  ///\r\n  /// # Errors\r\n  ///\r\n  /// Returns [`Error::InvalidKeybinding`] if the keybinding is empty.\r\n  pub fn new(keys: Vec<Key>) -> crate::Result<Self> {\r\n    if keys.is_empty() {\r\n      return Err(crate::Error::InvalidKeybinding);\r\n    }\r\n\r\n    Ok(Self(keys))\r\n  }\r\n\r\n  /// Returns the keys in the keybinding.\r\n  #[must_use]\r\n  pub fn keys(&self) -> &[Key] {\r\n    &self.0\r\n  }\r\n\r\n  /// Returns the trigger key in the keybinding.\r\n  #[must_use]\r\n  #[allow(clippy::missing_panics_doc)]\r\n  pub fn trigger_key(&self) -> &Key {\r\n    // SAFETY: Keys vector is verified to be non-empty in\r\n    // `Keybinding::new`.\r\n    self.0.last().unwrap()\r\n  }\r\n}\r\n\r\n/// A listener for system-wide keybindings.\r\n#[derive(Debug)]\r\npub struct KeybindingListener {\r\n  /// A receiver channel for outgoing keybinding events.\r\n  event_rx: mpsc::UnboundedReceiver<KeybindingEvent>,\r\n\r\n  /// A map of keybindings to their trigger key.\r\n  ///\r\n  /// The trigger key is the final key in a keybinding. For example, in\r\n  /// the keybinding `[Key::Cmd, Key::Shift, Key::A]`, `Key::A` is the\r\n  /// trigger key.\r\n  keybinding_map: Arc<Mutex<HashMap<Key, Vec<Keybinding>>>>,\r\n\r\n  /// Whether the listener is currently enabled.\r\n  enabled: Arc<AtomicBool>,\r\n\r\n  /// The underlying keyboard hook used to listen for key events.\r\n  keyboard_hook: platform_impl::KeyboardHook,\r\n}\r\n\r\nimpl KeybindingListener {\r\n  /// Creates an instance of `KeybindingListener`.\r\n  pub fn new(\r\n    keybindings: &[Keybinding],\r\n    dispatcher: &Dispatcher,\r\n  ) -> crate::Result<Self> {\r\n    let (event_tx, event_rx) = mpsc::unbounded_channel();\r\n\r\n    let keybinding_map =\r\n      Arc::new(Mutex::new(Self::create_keybinding_map(keybindings)));\r\n\r\n    let enabled = Arc::new(AtomicBool::new(true));\r\n\r\n    let keyboard_hook = Self::create_keyboard_hook(\r\n      keybinding_map.clone(),\r\n      enabled.clone(),\r\n      event_tx,\r\n      dispatcher,\r\n    )?;\r\n\r\n    Ok(Self {\r\n      event_rx,\r\n      keybinding_map,\r\n      enabled,\r\n      keyboard_hook,\r\n    })\r\n  }\r\n\r\n  /// Returns the next keybinding event from the listener.\r\n  ///\r\n  /// This will block until a keybinding event is available.\r\n  pub async fn next_event(&mut self) -> Option<KeybindingEvent> {\r\n    self.event_rx.recv().await\r\n  }\r\n\r\n  /// Updates the keybindings for the keybinding listener.\r\n  ///\r\n  /// # Panics\r\n  ///\r\n  /// If the internal mutex is poisoned.\r\n  pub fn update(&self, keybindings: &[Keybinding]) {\r\n    *self.keybinding_map.lock().unwrap() =\r\n      Self::create_keybinding_map(keybindings);\r\n  }\r\n\r\n  /// Enables or disables the keybinding listener.\r\n  pub fn enable(&mut self, enabled: bool) {\r\n    self.enabled.store(enabled, Ordering::Relaxed);\r\n  }\r\n\r\n  /// Terminates the keybinding listener.\r\n  pub fn terminate(&mut self) -> crate::Result<()> {\r\n    self.keyboard_hook.terminate()\r\n  }\r\n\r\n  /// Creates and starts the keyboard hook with the given callback.\r\n  fn create_keyboard_hook(\r\n    keybinding_map: Arc<Mutex<HashMap<Key, Vec<Keybinding>>>>,\r\n    enabled: Arc<AtomicBool>,\r\n    event_tx: mpsc::UnboundedSender<KeybindingEvent>,\r\n    dispatcher: &Dispatcher,\r\n  ) -> crate::Result<platform_impl::KeyboardHook> {\r\n    platform_impl::KeyboardHook::new(\r\n      move |event: platform_impl::KeyEvent| -> bool {\r\n        if !enabled.load(Ordering::Relaxed) || !event.is_keypress {\r\n          return false;\r\n        }\r\n\r\n        let Ok(keybinding_map) = keybinding_map.lock() else {\r\n          tracing::error!(\"Failed to acquire lock on keybinding map.\");\r\n          return false;\r\n        };\r\n\r\n        // Find keybinding candidates whose trigger key is the pressed key.\r\n        let Some(candidates) = keybinding_map.get(&event.key) else {\r\n          return false;\r\n        };\r\n\r\n        let mut cached_key_states = HashMap::new();\r\n\r\n        // Find the matching keybindings based on the pressed keys.\r\n        let matched_keybindings = candidates.iter().filter(|keybinding| {\r\n          keybinding.keys().iter().all(|&key| {\r\n            if key == event.key {\r\n              return true;\r\n            }\r\n\r\n            *cached_key_states\r\n              .entry(key)\r\n              .or_insert_with(|| event.is_key_down(key))\r\n          })\r\n        });\r\n\r\n        // Find the longest matching keybinding.\r\n        let Some(longest_keybinding) = matched_keybindings\r\n          .max_by_key(|keybinding| keybinding.keys().len())\r\n        else {\r\n          return false;\r\n        };\r\n\r\n        // Reject if any modifier keys not in the keybinding are held.\r\n        let has_extra_modifiers = MODIFIER_GROUPS\r\n          .iter()\r\n          // Filter out modifier groups that have keys in the keybinding.\r\n          .filter(|(_, group_keys)| {\r\n            !group_keys\r\n              .iter()\r\n              .any(|key| longest_keybinding.keys().contains(key))\r\n          })\r\n          // Use the group's \"generic\" key (e.g. `Key::Shift`) to check if\r\n          // the modifier is held. This avoids lookups for `Key::LShift`\r\n          // and `Key::RShift`.\r\n          .any(|(generic_key, _)| {\r\n            cached_key_states\r\n              .get(generic_key)\r\n              .copied()\r\n              .unwrap_or_else(|| event.is_key_down(*generic_key))\r\n          });\r\n\r\n        if has_extra_modifiers {\r\n          return false;\r\n        }\r\n\r\n        let _ = event_tx.send(KeybindingEvent(longest_keybinding.clone()));\r\n\r\n        true\r\n      },\r\n      dispatcher,\r\n    )\r\n  }\r\n\r\n  /// Builds the keybinding map from configs.\r\n  fn create_keybinding_map(\r\n    keybindings: &[Keybinding],\r\n  ) -> HashMap<Key, Vec<Keybinding>> {\r\n    let mut keybinding_map = HashMap::new();\r\n\r\n    for keybinding in keybindings {\r\n      keybinding_map\r\n        .entry(*keybinding.trigger_key())\r\n        .or_insert_with(Vec::new)\r\n        .push(keybinding.clone());\r\n    }\r\n\r\n    keybinding_map\r\n  }\r\n}\r\n\r\nimpl Drop for KeybindingListener {\r\n  fn drop(&mut self) {\r\n    let _ = self.terminate();\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-platform/src/lib.rs",
    "content": "#![warn(clippy::all, clippy::pedantic)]\r\n#![allow(clippy::missing_errors_doc)]\r\n#![feature(iterator_try_collect)]\r\n\r\nmod dispatcher;\r\nmod display;\r\nmod display_listener;\r\nmod error;\r\nmod event_loop;\r\nmod keybinding_listener;\r\nmod models;\r\nmod mouse_listener;\r\nmod native_window;\r\nmod platform_event;\r\nmod platform_impl;\r\nmod single_instance;\r\nmod thread_bound;\r\nmod window_listener;\r\n\r\npub use dispatcher::*;\r\npub use display::*;\r\npub use display_listener::*;\r\npub use error::*;\r\npub use event_loop::*;\r\npub use keybinding_listener::*;\r\npub use models::*;\r\npub use mouse_listener::*;\r\npub use native_window::*;\r\npub use platform_event::*;\r\npub use single_instance::*;\r\npub use thread_bound::*;\r\npub use window_listener::*;\r\n// TODO: Avoid exposing `windows` crate types in the public API.\r\n#[cfg(target_os = \"windows\")]\r\npub use windows::Win32::UI::WindowsAndMessaging::{\r\n  SET_WINDOW_POS_FLAGS, SWP_ASYNCWINDOWPOS, SWP_FRAMECHANGED,\r\n  SWP_NOACTIVATE, SWP_NOCOPYBITS, SWP_NOSENDCHANGING, WINDOW_EX_STYLE,\r\n  WINDOW_STYLE, WS_CAPTION, WS_CHILD, WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW,\r\n  WS_MAXIMIZEBOX,\r\n};\r\n"
  },
  {
    "path": "packages/wm-platform/src/models/color.rs",
    "content": "use std::str::FromStr;\r\n\r\nuse serde::{Deserialize, Deserializer, Serialize};\r\n\r\n#[derive(Debug, Clone, Serialize)]\r\npub struct Color {\r\n  pub r: u8,\r\n  pub g: u8,\r\n  pub b: u8,\r\n  pub a: u8,\r\n}\r\n\r\nimpl Color {\r\n  #[must_use]\r\n  #[allow(clippy::missing_panics_doc)]\r\n  pub fn to_bgr(&self) -> u32 {\r\n    let bgr = format!(\"{:02x}{:02x}{:02x}\", self.b, self.g, self.r);\r\n    // SAFETY: An invalid hex value is unrepresentable.\r\n    u32::from_str_radix(&bgr, 16).unwrap()\r\n  }\r\n}\r\n\r\nimpl FromStr for Color {\r\n  type Err = crate::ParseError;\r\n\r\n  fn from_str(unparsed: &str) -> Result<Self, crate::ParseError> {\r\n    let mut chars = unparsed.chars();\r\n\r\n    if chars.next() != Some('#') {\r\n      return Err(crate::ParseError::Color(unparsed.to_string()));\r\n    }\r\n\r\n    let parse_hex = |slice: &str| -> Result<u8, crate::ParseError> {\r\n      u8::from_str_radix(slice, 16)\r\n        .map_err(|_| crate::ParseError::Color(unparsed.to_string()))\r\n    };\r\n\r\n    let r = parse_hex(&unparsed[1..3])?;\r\n    let g = parse_hex(&unparsed[3..5])?;\r\n    let b = parse_hex(&unparsed[5..7])?;\r\n\r\n    let a = match unparsed.len() {\r\n      9 => parse_hex(&unparsed[7..9])?,\r\n      7 => 255,\r\n      _ => return Err(crate::ParseError::Color(unparsed.to_string())),\r\n    };\r\n\r\n    Ok(Self { r, g, b, a })\r\n  }\r\n}\r\n\r\n/// Deserialize a `Color` from either a string or a struct.\r\nimpl<'de> Deserialize<'de> for Color {\r\n  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\r\n  where\r\n    D: Deserializer<'de>,\r\n  {\r\n    #[derive(Deserialize)]\r\n    #[serde(untagged)]\r\n    enum ColorDe {\r\n      Struct { r: u8, g: u8, b: u8, a: u8 },\r\n      String(String),\r\n    }\r\n\r\n    match ColorDe::deserialize(deserializer)? {\r\n      ColorDe::Struct { r, g, b, a } => Ok(Self { r, g, b, a }),\r\n      ColorDe::String(str) => {\r\n        Self::from_str(&str).map_err(serde::de::Error::custom)\r\n      }\r\n    }\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-platform/src/models/corner_style.rs",
    "content": "use serde::{Deserialize, Serialize};\r\n\r\n/// Corner style of a window's frame.\r\n///\r\n/// # Platform-specific\r\n///\r\n/// Only has an effect on Windows 11.\r\n#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]\r\n#[serde(rename_all = \"snake_case\")]\r\npub enum CornerStyle {\r\n  #[default]\r\n  Default,\r\n  Square,\r\n  Rounded,\r\n  SmallRounded,\r\n}\r\n"
  },
  {
    "path": "packages/wm-platform/src/models/delta.rs",
    "content": "use std::str::FromStr;\r\n\r\nuse serde::Serialize;\r\n\r\n/// A wrapper that indicates a value should be interpreted as a delta\r\n/// (relative change).\r\n#[derive(Debug, Clone, Copy, PartialEq, Serialize)]\r\npub struct Delta<T> {\r\n  pub inner: T,\r\n  pub is_negative: bool,\r\n}\r\n\r\nimpl<T: FromStr<Err = crate::ParseError>> FromStr for Delta<T> {\r\n  type Err = crate::ParseError;\r\n\r\n  fn from_str(unparsed: &str) -> Result<Self, crate::ParseError> {\r\n    let unparsed = unparsed.trim();\r\n\r\n    let (raw, is_negative) = match unparsed.chars().next() {\r\n      Some('+') => (&unparsed[1..], false),\r\n      Some('-') => (&unparsed[1..], true),\r\n      // No sign is interpreted as positive.\r\n      _ => (unparsed, false),\r\n    };\r\n\r\n    if raw.is_empty() {\r\n      return Err(crate::ParseError::Delta(unparsed.to_string()));\r\n    }\r\n\r\n    let inner = T::from_str(raw)?;\r\n\r\n    Ok(Self { inner, is_negative })\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-platform/src/models/direction.rs",
    "content": "use std::str::FromStr;\r\n\r\nuse serde::Serialize;\r\n\r\n#[derive(Clone, Debug, PartialEq, Serialize)]\r\n#[serde(rename_all = \"snake_case\")]\r\npub enum Direction {\r\n  Left,\r\n  Right,\r\n  Up,\r\n  Down,\r\n}\r\n\r\nimpl Direction {\r\n  /// Gets the inverse of a given direction.\r\n  ///\r\n  /// Example:\r\n  /// ```\r\n  /// # use wm_platform::Direction;\r\n  /// let dir = Direction::Left.inverse();\r\n  /// assert_eq!(dir, Direction::Right);\r\n  /// ```\r\n  #[must_use]\r\n  pub fn inverse(&self) -> Direction {\r\n    match self {\r\n      Direction::Left => Direction::Right,\r\n      Direction::Right => Direction::Left,\r\n      Direction::Up => Direction::Down,\r\n      Direction::Down => Direction::Up,\r\n    }\r\n  }\r\n}\r\n\r\nimpl FromStr for Direction {\r\n  type Err = crate::ParseError;\r\n\r\n  /// Parses a string into a direction.\r\n  ///\r\n  /// Example:\r\n  /// ```\r\n  /// # use wm_platform::Direction;\r\n  /// # use std::str::FromStr;\r\n  /// let dir = Direction::from_str(\"left\");\r\n  /// assert_eq!(dir.unwrap(), Direction::Left);\r\n  /// ```\r\n  fn from_str(unparsed: &str) -> Result<Self, crate::ParseError> {\r\n    match unparsed {\r\n      \"left\" => Ok(Direction::Left),\r\n      \"right\" => Ok(Direction::Right),\r\n      \"up\" => Ok(Direction::Up),\r\n      \"down\" => Ok(Direction::Down),\r\n      _ => Err(crate::ParseError::Direction(unparsed.to_string())),\r\n    }\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-platform/src/models/key.rs",
    "content": "use std::{fmt, str::FromStr};\r\n\r\nuse serde::{Deserialize, Serialize};\r\n\r\n/// Platform-specific keyboard key code.\r\n///\r\n/// Represents the raw key code from the underlying platform's keyboard\r\n/// API.\r\n///\r\n/// # Platform-specific\r\n///\r\n/// - **Windows**: `u16` (Virtual key code from Windows API). See <https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes>\r\n/// - **macOS**: `i64` (Virtual key code from `CGEvent`). See <https://developer.apple.com/documentation/coregraphics/cgeventfield/keyboardeventkeycode>\r\n#[derive(\r\n  Debug,\r\n  Copy,\r\n  Clone,\r\n  PartialEq,\r\n  Eq,\r\n  PartialOrd,\r\n  Ord,\r\n  Hash,\r\n  Serialize,\r\n  Deserialize,\r\n)]\r\npub struct KeyCode(\r\n  #[cfg(target_os = \"windows\")] pub(crate) u16,\r\n  #[cfg(target_os = \"macos\")] pub(crate) i64,\r\n);\r\n\r\nimpl fmt::Display for KeyCode {\r\n  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\r\n    write!(f, \"{}\", self.0)\r\n  }\r\n}\r\n\r\n#[derive(Debug, thiserror::Error)]\r\npub enum KeyParseError {\r\n  #[error(\"Unknown key: {0}\")]\r\n  UnknownKey(String),\r\n}\r\n\r\n/// Cross-platform key representation.\r\n#[allow(clippy::unsafe_derive_deserialize)]\r\n#[derive(\r\n  Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize,\r\n)]\r\n#[serde(rename_all = \"snake_case\")]\r\npub enum Key {\r\n  // Letter keys\r\n  A,\r\n  B,\r\n  C,\r\n  D,\r\n  E,\r\n  F,\r\n  G,\r\n  H,\r\n  I,\r\n  J,\r\n  K,\r\n  L,\r\n  M,\r\n  N,\r\n  O,\r\n  P,\r\n  Q,\r\n  R,\r\n  S,\r\n  T,\r\n  U,\r\n  V,\r\n  W,\r\n  X,\r\n  Y,\r\n  Z,\r\n\r\n  // Number keys\r\n  D0,\r\n  D1,\r\n  D2,\r\n  D3,\r\n  D4,\r\n  D5,\r\n  D6,\r\n  D7,\r\n  D8,\r\n  D9,\r\n\r\n  // Function keys\r\n  F1,\r\n  F2,\r\n  F3,\r\n  F4,\r\n  F5,\r\n  F6,\r\n  F7,\r\n  F8,\r\n  F9,\r\n  F10,\r\n  F11,\r\n  F12,\r\n  F13,\r\n  F14,\r\n  F15,\r\n  F16,\r\n  F17,\r\n  F18,\r\n  F19,\r\n  F20,\r\n  F21,\r\n  F22,\r\n  F23,\r\n  F24,\r\n\r\n  // Modifier keys\r\n  Cmd,\r\n  Ctrl,\r\n  Alt,\r\n  Shift,\r\n  Win,\r\n  LCmd,\r\n  RCmd,\r\n  LCtrl,\r\n  RCtrl,\r\n  LAlt,\r\n  RAlt,\r\n  LShift,\r\n  RShift,\r\n  LWin,\r\n  RWin,\r\n\r\n  // Special keys\r\n  Space,\r\n  Tab,\r\n  Enter,\r\n  Delete,\r\n  Escape,\r\n  Backspace,\r\n\r\n  // Arrow keys\r\n  Left,\r\n  Right,\r\n  Up,\r\n  Down,\r\n\r\n  // Navigation keys\r\n  Home,\r\n  End,\r\n  PageUp,\r\n  PageDown,\r\n  Insert,\r\n\r\n  // Lock keys\r\n  NumLock,\r\n  ScrollLock,\r\n  CapsLock,\r\n\r\n  // Numpad\r\n  Numpad0,\r\n  Numpad1,\r\n  Numpad2,\r\n  Numpad3,\r\n  Numpad4,\r\n  Numpad5,\r\n  Numpad6,\r\n  Numpad7,\r\n  Numpad8,\r\n  Numpad9,\r\n  NumpadAdd,\r\n  NumpadSubtract,\r\n  NumpadMultiply,\r\n  NumpadDivide,\r\n  NumpadDecimal,\r\n\r\n  // Media keys\r\n  VolumeUp,\r\n  VolumeDown,\r\n  VolumeMute,\r\n  MediaNextTrack,\r\n  MediaPrevTrack,\r\n  MediaStop,\r\n  MediaPlayPause,\r\n  PrintScreen,\r\n\r\n  // Language-specific keys\r\n  Muhenkan,\r\n  Henkan,\r\n\r\n  // OEM keys\r\n  OemSemicolon,\r\n  OemQuestion,\r\n  OemTilde,\r\n  OemOpenBrackets,\r\n  OemPipe,\r\n  OemCloseBrackets,\r\n  OemQuotes,\r\n  Oem8,\r\n  Oem102,\r\n  OemPlus,\r\n  OemComma,\r\n  OemMinus,\r\n  OemPeriod,\r\n}\r\n\r\nimpl Key {\r\n  /// Attempts to parse a key from a literal string (e.g. `a`, `;`, `à`).\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// - **macOS**: Not implemented. Returns `KeyParseError::UnknownKey` for\r\n  ///   all keys.\r\n  ///\r\n  /// # Errors\r\n  ///\r\n  /// Returns `KeyParseError::UnknownKey` if the key is not found on the\r\n  /// current keyboard layout.\r\n  pub fn try_from_literal(key_str: &str) -> Result<Self, KeyParseError> {\r\n    #[cfg(target_os = \"macos\")]\r\n    {\r\n      Err(KeyParseError::UnknownKey(key_str.to_string()))\r\n    }\r\n    #[cfg(target_os = \"windows\")]\r\n    {\r\n      use windows::Win32::UI::Input::KeyboardAndMouse::{\r\n        GetKeyboardLayout, VkKeyScanExW,\r\n      };\r\n\r\n      // Check if the key exists on the current keyboard layout.\r\n      let utf16_key = key_str\r\n        .encode_utf16()\r\n        .next()\r\n        .ok_or_else(|| KeyParseError::UnknownKey(key_str.to_string()))?;\r\n\r\n      let layout = unsafe { GetKeyboardLayout(0) };\r\n      let vk_code = unsafe { VkKeyScanExW(utf16_key, layout) };\r\n\r\n      if vk_code == -1 {\r\n        return Err(KeyParseError::UnknownKey(key_str.to_string()));\r\n      }\r\n\r\n      // The low-order byte contains the virtual-key code and the high-\r\n      // order byte contains the shift state.\r\n      let [high_order, low_order] = vk_code.to_be_bytes();\r\n\r\n      // Key is valid if it doesn't require shift or alt to be pressed.\r\n      match high_order {\r\n        0 => Key::try_from(KeyCode(u16::from(low_order)))\r\n          .map_err(|_| KeyParseError::UnknownKey(key_str.to_string())),\r\n        _ => Err(KeyParseError::UnknownKey(key_str.to_string())),\r\n      }\r\n    }\r\n  }\r\n}\r\n\r\n/// Generates `FromStr` and `Display` implementations for the `Key` enum.\r\n///\r\n/// Each variant can have multiple string aliases, with the first used for\r\n/// the `Display` implementation.\r\n///\r\n/// # Example\r\n/// ```no_run,compile_fail\r\n/// impl_key_parsing! {\r\n///   Enter => [\"enter\", \"return\", \"cr\"],\r\n///   Space => [\"space\", \"spacebar\", \" \"],\r\n/// }\r\n/// ```\r\nmacro_rules! impl_key_parsing {\r\n  ($( $variant:ident => [$($str_name:literal),+ $(,)?]),* $(,)?) => {\r\n    impl FromStr for Key {\r\n      type Err = KeyParseError;\r\n\r\n      fn from_str(key_str: &str) -> Result<Self, Self::Err> {\r\n        match key_str.to_ascii_lowercase().as_str() {\r\n          $($($str_name)|+ => Ok(Key::$variant),)*\r\n          _ => Err(KeyParseError::UnknownKey(key_str.to_string())),\r\n        }\r\n      }\r\n    }\r\n\r\n    impl fmt::Display for Key {\r\n      fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\r\n        match self {\r\n          $(Key::$variant => {\r\n            // Return the first string alias as the display name.\r\n            let aliases = &[$($str_name),+];\r\n            write!(f, \"{}\", aliases[0])\r\n          },)*\r\n        }\r\n      }\r\n    }\r\n\r\n    impl Key {\r\n      /// Returns all string aliases for this key variant.\r\n      pub fn all_aliases(&self) -> Option<&'static [&'static str]> {\r\n        match self {\r\n          $(Key::$variant => Some(&[$($str_name),+]),)*\r\n        }\r\n      }\r\n    }\r\n  };\r\n}\r\n\r\nimpl_key_parsing! {\r\n  // Letter keys\r\n  A => [\"a\"], B => [\"b\"], C => [\"c\"], D => [\"d\"], E => [\"e\"], F => [\"f\"],\r\n  G => [\"g\"], H => [\"h\"], I => [\"i\"], J => [\"j\"], K => [\"k\"], L => [\"l\"],\r\n  M => [\"m\"], N => [\"n\"], O => [\"o\"], P => [\"p\"], Q => [\"q\"], R => [\"r\"],\r\n  S => [\"s\"], T => [\"t\"], U => [\"u\"], V => [\"v\"], W => [\"w\"], X => [\"x\"],\r\n  Y => [\"y\"], Z => [\"z\"],\r\n\r\n  // Number keys\r\n  D0 => [\"0\", \"d0\"],\r\n  D1 => [\"1\", \"d1\"],\r\n  D2 => [\"2\", \"d2\"],\r\n  D3 => [\"3\", \"d3\"],\r\n  D4 => [\"4\", \"d4\"],\r\n  D5 => [\"5\", \"d5\"],\r\n  D6 => [\"6\", \"d6\"],\r\n  D7 => [\"7\", \"d7\"],\r\n  D8 => [\"8\", \"d8\"],\r\n  D9 => [\"9\", \"d9\"],\r\n\r\n  // Function keys\r\n  F1 => [\"f1\"], F2 => [\"f2\"], F3 => [\"f3\"], F4 => [\"f4\"], F5 => [\"f5\"],\r\n  F6 => [\"f6\"], F7 => [\"f7\"], F8 => [\"f8\"], F9 => [\"f9\"], F10 => [\"f10\"],\r\n  F11 => [\"f11\"], F12 => [\"f12\"], F13 => [\"f13\"], F14 => [\"f14\"],\r\n  F15 => [\"f15\"], F16 => [\"f16\"], F17 => [\"f17\"], F18 => [\"f18\"],\r\n  F19 => [\"f19\"], F20 => [\"f20\"], F21 => [\"f21\"], F22 => [\"f22\"],\r\n  F23 => [\"f23\"], F24 => [\"f24\"],\r\n\r\n  // Modifier keys\r\n  Cmd => [\"cmd\"],\r\n  Ctrl => [\"ctrl\", \"control\"],\r\n  Alt => [\"alt\", \"menu\"],\r\n  Shift => [\"shift\"],\r\n  Win => [\"win\"],\r\n  LCmd => [\"lcmd\"],\r\n  RCmd => [\"rcmd\"],\r\n  LCtrl => [\"lctrl\"],\r\n  RCtrl => [\"rctrl\"],\r\n  LAlt => [\"lalt\", \"lmenu\"],\r\n  RAlt => [\"ralt\", \"rmenu\"],\r\n  LShift => [\"lshift\"],\r\n  RShift => [\"rshift\"],\r\n  LWin => [\"lwin\"],\r\n  RWin => [\"rwin\"],\r\n\r\n  // Special keys\r\n  Space => [\"space\"],\r\n  Tab => [\"tab\"],\r\n  Enter => [\"enter\", \"return\"],\r\n  Delete => [\"delete\"],\r\n  Escape => [\"escape\"],\r\n  Backspace => [\"backspace\"],\r\n\r\n  // Arrow keys\r\n  Left => [\"left\"],\r\n  Right => [\"right\"],\r\n  Up => [\"up\"],\r\n  Down => [\"down\"],\r\n\r\n  // Navigation keys\r\n  Home => [\"home\"],\r\n  End => [\"end\"],\r\n  PageUp => [\"page_up\"],\r\n  PageDown => [\"page_down\"],\r\n  Insert => [\"insert\"],\r\n\r\n  // Lock keys\r\n  NumLock => [\"num_lock\"],\r\n  ScrollLock => [\"scroll_lock\"],\r\n  CapsLock => [\"caps_lock\"],\r\n\r\n  // Numpad\r\n  Numpad0 => [\"numpad0\"],\r\n  Numpad1 => [\"numpad1\"],\r\n  Numpad2 => [\"numpad2\"],\r\n  Numpad3 => [\"numpad3\"],\r\n  Numpad4 => [\"numpad4\"],\r\n  Numpad5 => [\"numpad5\"],\r\n  Numpad6 => [\"numpad6\"],\r\n  Numpad7 => [\"numpad7\"],\r\n  Numpad8 => [\"numpad8\"],\r\n  Numpad9 => [\"numpad9\"],\r\n  NumpadAdd => [\"numpad_add\", \"add\"],\r\n  NumpadSubtract => [\"numpad_subtract\", \"subtract\"],\r\n  NumpadMultiply => [\"numpad_multiply\", \"multiply\"],\r\n  NumpadDivide => [\"numpad_divide\", \"divide\"],\r\n  NumpadDecimal => [\"numpad_decimal\", \"decimal\"],\r\n\r\n  // Media keys\r\n  VolumeUp => [\"volume_up\"],\r\n  VolumeDown => [\"volume_down\"],\r\n  VolumeMute => [\"volume_mute\"],\r\n  MediaNextTrack => [\"media_next_track\"],\r\n  MediaPrevTrack => [\"media_prev_track\"],\r\n  MediaStop => [\"media_stop\"],\r\n  MediaPlayPause => [\"media_play_pause\"],\r\n  PrintScreen => [\"print_screen\"],\r\n\r\n  // OEM keys\r\n  OemSemicolon => [\"oem_semicolon\"],\r\n  OemQuestion => [\"oem_question\"],\r\n  OemTilde => [\"oem_tilde\"],\r\n  OemOpenBrackets => [\"oem_open_brackets\"],\r\n  OemPipe => [\"oem_pipe\"],\r\n  OemCloseBrackets => [\"oem_close_brackets\"],\r\n  OemQuotes => [\"oem_quotes\"],\r\n  Oem8 => [\"oem_8\"],\r\n  Oem102 => [\"oem_102\"],\r\n  OemPlus => [\"oem_plus\"],\r\n  OemComma => [\"oem_comma\"],\r\n  OemMinus => [\"oem_minus\"],\r\n  OemPeriod => [\"oem_period\"],\r\n\r\n  // Language-specific keys\r\n  Muhenkan => [\"muhenkan\"],\r\n  Henkan => [\"henkan\"],\r\n}\r\n\r\n#[cfg(test)]\r\nmod tests {\r\n  use super::*;\r\n\r\n  #[test]\r\n  fn test_key_parsing() {\r\n    assert_eq!(\"a\".parse::<Key>().unwrap(), Key::A);\r\n    assert_eq!(\"cmd\".parse::<Key>().unwrap(), Key::Cmd);\r\n    assert_eq!(\"f1\".parse::<Key>().unwrap(), Key::F1);\r\n    assert_eq!(\"space\".parse::<Key>().unwrap(), Key::Space);\r\n    assert_eq!(\"enter\".parse::<Key>().unwrap(), Key::Enter);\r\n    assert_eq!(\"return\".parse::<Key>().unwrap(), Key::Enter);\r\n\r\n    // Should be case-insensitive.\r\n    assert_eq!(\"Shift\".parse::<Key>().unwrap(), Key::Shift);\r\n    assert_eq!(\"CTRL\".parse::<Key>().unwrap(), Key::Ctrl);\r\n    assert_eq!(\"F1\".parse::<Key>().unwrap(), Key::F1);\r\n\r\n    // Should fail for unknown keys.\r\n    assert!(\"invalid\".parse::<Key>().is_err());\r\n  }\r\n\r\n  #[test]\r\n  fn test_key_display() {\r\n    assert_eq!(Key::A.to_string(), \"a\");\r\n    assert_eq!(Key::Cmd.to_string(), \"cmd\");\r\n    assert_eq!(Key::F1.to_string(), \"f1\");\r\n    assert_eq!(Key::Space.to_string(), \"space\");\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-platform/src/models/key_code.rs",
    "content": "#[cfg(target_os = \"windows\")]\r\nuse windows::Win32::UI::Input::KeyboardAndMouse::{\r\n  VIRTUAL_KEY, VK_0, VK_1, VK_2, VK_3, VK_4, VK_5, VK_6, VK_7, VK_8, VK_9,\r\n  VK_A, VK_ADD, VK_B, VK_BACK, VK_C, VK_CAPITAL, VK_CONVERT, VK_D,\r\n  VK_DECIMAL, VK_DELETE, VK_DIVIDE, VK_DOWN, VK_E, VK_END, VK_ESCAPE,\r\n  VK_F, VK_F1, VK_F10, VK_F11, VK_F12, VK_F13, VK_F14, VK_F15, VK_F16,\r\n  VK_F17, VK_F18, VK_F19, VK_F2, VK_F20, VK_F21, VK_F22, VK_F23, VK_F24,\r\n  VK_F3, VK_F4, VK_F5, VK_F6, VK_F7, VK_F8, VK_F9, VK_G, VK_H, VK_HOME,\r\n  VK_I, VK_INSERT, VK_J, VK_K, VK_L, VK_LCONTROL, VK_LEFT, VK_LMENU,\r\n  VK_LSHIFT, VK_LWIN, VK_M, VK_MEDIA_NEXT_TRACK, VK_MEDIA_PLAY_PAUSE,\r\n  VK_MEDIA_PREV_TRACK, VK_MEDIA_STOP, VK_MULTIPLY, VK_N, VK_NEXT,\r\n  VK_NONCONVERT, VK_NUMLOCK, VK_NUMPAD0, VK_NUMPAD1, VK_NUMPAD2,\r\n  VK_NUMPAD3, VK_NUMPAD4, VK_NUMPAD5, VK_NUMPAD6, VK_NUMPAD7, VK_NUMPAD8,\r\n  VK_NUMPAD9, VK_O, VK_OEM_1, VK_OEM_102, VK_OEM_2, VK_OEM_3, VK_OEM_4,\r\n  VK_OEM_5, VK_OEM_6, VK_OEM_7, VK_OEM_8, VK_OEM_COMMA, VK_OEM_MINUS,\r\n  VK_OEM_PERIOD, VK_OEM_PLUS, VK_P, VK_PRIOR, VK_Q, VK_R, VK_RCONTROL,\r\n  VK_RETURN, VK_RIGHT, VK_RMENU, VK_RSHIFT, VK_RWIN, VK_S, VK_SCROLL,\r\n  VK_SNAPSHOT, VK_SPACE, VK_SUBTRACT, VK_T, VK_TAB, VK_U, VK_UP, VK_V,\r\n  VK_VOLUME_DOWN, VK_VOLUME_MUTE, VK_VOLUME_UP, VK_W, VK_X, VK_Y, VK_Z,\r\n};\r\n\r\nuse crate::{Key, KeyCode};\r\n\r\n#[derive(Debug, thiserror::Error)]\r\npub enum KeyConversionError {\r\n  #[error(\"Unknown key code: {0}\")]\r\n  UnknownKeyCode(KeyCode),\r\n}\r\n\r\n/// Generates `TryFrom` implementations for converting between `Key` and\r\n/// `KeyCode`.\r\n///\r\n/// For Windows, the key code is assumed to be a `VK_*` constant (accessed\r\n/// via .0).\r\n///\r\n/// # Example\r\n/// ```no_run,compile_fail\r\n/// impl_key_code_conversion! {\r\n///   Enter => { windows: VK_RETURN, macos: 0x24, },\r\n///   Space => { windows: VK_SPACE, macos: 0x31, },\r\n///   PrintScreen => { windows: VK_SNAPSHOT, }, // Only supported on Windows.\r\n/// }\r\n/// ```\r\nmacro_rules! impl_key_code_conversion {\r\n  (\r\n    $(\r\n      $variant:ident => {\r\n        $(windows: $win_code:expr,)?\r\n        $(macos: $mac_code:expr,)?\r\n      }\r\n    ),* $(,)?\r\n  ) => {\r\n    #[cfg(target_os = \"windows\")]\r\n    impl TryFrom<KeyCode> for Key {\r\n      type Error = KeyConversionError;\r\n\r\n      fn try_from(key_code: KeyCode) -> Result<Self, Self::Error> {\r\n        let vk = VIRTUAL_KEY(key_code.0);\r\n        $($(if vk == $win_code { return Ok(Key::$variant); })?)*\r\n        Err(KeyConversionError::UnknownKeyCode(key_code))\r\n      }\r\n    }\r\n\r\n    #[cfg(target_os = \"macos\")]\r\n    impl TryFrom<KeyCode> for Key {\r\n      type Error = KeyConversionError;\r\n\r\n      fn try_from(key_code: KeyCode) -> Result<Self, Self::Error> {\r\n        $($(if key_code.0 == $mac_code { return Ok(Key::$variant); })?)*\r\n        Err(KeyConversionError::UnknownKeyCode(key_code))\r\n      }\r\n    }\r\n\r\n    impl TryFrom<Key> for KeyCode {\r\n      type Error = KeyConversionError;\r\n\r\n      fn try_from(key: Key) -> Result<Self, Self::Error> {\r\n        match key {\r\n          $(Key::$variant => {\r\n            #[cfg(target_os = \"windows\")]\r\n            {\r\n              $(return Ok(KeyCode($win_code.0));)?\r\n              #[allow(unreachable_code)]\r\n              return Err(KeyConversionError::UnknownKeyCode(KeyCode(0)));\r\n            }\r\n            #[cfg(target_os = \"macos\")]\r\n            {\r\n              $(return Ok(KeyCode($mac_code));)?\r\n              #[allow(unreachable_code)]\r\n              return Err(KeyConversionError::UnknownKeyCode(KeyCode(0)));\r\n            }\r\n          }),*\r\n        }\r\n      }\r\n    }\r\n  };\r\n}\r\n\r\nimpl_key_code_conversion! {\r\n  // Letter keys\r\n  A => { windows: VK_A, macos: 0x00, },\r\n  B => { windows: VK_B, macos: 0x0B, },\r\n  C => { windows: VK_C, macos: 0x08, },\r\n  D => { windows: VK_D, macos: 0x02, },\r\n  E => { windows: VK_E, macos: 0x0E, },\r\n  F => { windows: VK_F, macos: 0x03, },\r\n  G => { windows: VK_G, macos: 0x05, },\r\n  H => { windows: VK_H, macos: 0x04, },\r\n  I => { windows: VK_I, macos: 0x22, },\r\n  J => { windows: VK_J, macos: 0x26, },\r\n  K => { windows: VK_K, macos: 0x28, },\r\n  L => { windows: VK_L, macos: 0x25, },\r\n  M => { windows: VK_M, macos: 0x2E, },\r\n  N => { windows: VK_N, macos: 0x2D, },\r\n  O => { windows: VK_O, macos: 0x1F, },\r\n  P => { windows: VK_P, macos: 0x23, },\r\n  Q => { windows: VK_Q, macos: 0x0C, },\r\n  R => { windows: VK_R, macos: 0x0F, },\r\n  S => { windows: VK_S, macos: 0x01, },\r\n  T => { windows: VK_T, macos: 0x11, },\r\n  U => { windows: VK_U, macos: 0x20, },\r\n  V => { windows: VK_V, macos: 0x09, },\r\n  W => { windows: VK_W, macos: 0x0D, },\r\n  X => { windows: VK_X, macos: 0x07, },\r\n  Y => { windows: VK_Y, macos: 0x10, },\r\n  Z => { windows: VK_Z, macos: 0x06, },\r\n  // Number keys\r\n  D0 => { windows: VK_0, macos: 0x1D, },\r\n  D1 => { windows: VK_1, macos: 0x12, },\r\n  D2 => { windows: VK_2, macos: 0x13, },\r\n  D3 => { windows: VK_3, macos: 0x14, },\r\n  D4 => { windows: VK_4, macos: 0x15, },\r\n  D5 => { windows: VK_5, macos: 0x17, },\r\n  D6 => { windows: VK_6, macos: 0x16, },\r\n  D7 => { windows: VK_7, macos: 0x1A, },\r\n  D8 => { windows: VK_8, macos: 0x1C, },\r\n  D9 => { windows: VK_9, macos: 0x19, },\r\n  // Function keys\r\n  F1 => { windows: VK_F1, macos: 0x7A, },\r\n  F2 => { windows: VK_F2, macos: 0x78, },\r\n  F3 => { windows: VK_F3, macos: 0x63, },\r\n  F4 => { windows: VK_F4, macos: 0x76, },\r\n  F5 => { windows: VK_F5, macos: 0x60, },\r\n  F6 => { windows: VK_F6, macos: 0x61, },\r\n  F7 => { windows: VK_F7, macos: 0x62, },\r\n  F8 => { windows: VK_F8, macos: 0x64, },\r\n  F9 => { windows: VK_F9, macos: 0x65, },\r\n  F10 => { windows: VK_F10, macos: 0x6D, },\r\n  F11 => { windows: VK_F11, macos: 0x67, },\r\n  F12 => { windows: VK_F12, macos: 0x6F, },\r\n  F13 => { windows: VK_F13, macos: 0x69, },\r\n  F14 => { windows: VK_F14, macos: 0x6B, },\r\n  F15 => { windows: VK_F15, macos: 0x71, },\r\n  F16 => { windows: VK_F16, macos: 0x6A, },\r\n  F17 => { windows: VK_F17, macos: 0x40, },\r\n  F18 => { windows: VK_F18, macos: 0x4F, },\r\n  F19 => { windows: VK_F19, macos: 0x50, },\r\n  F20 => { windows: VK_F20, macos: 0x5A, },\r\n  // Windows-only function keys; macOS has no F21-F24.\r\n  F21 => { windows: VK_F21, },\r\n  F22 => { windows: VK_F22, },\r\n  F23 => { windows: VK_F23, },\r\n  F24 => { windows: VK_F24, },\r\n  // Modifier keys - use platform-specific primary variants\r\n  LShift => { windows: VK_LSHIFT, macos: 0x38, },\r\n  RShift => { windows: VK_RSHIFT, macos: 0x3C, },\r\n  LCtrl => { windows: VK_LCONTROL, macos: 0x3B, },\r\n  RCtrl => { windows: VK_RCONTROL, macos: 0x3E, },\r\n  LAlt => { windows: VK_LMENU, macos: 0x3A, },\r\n  RAlt => { windows: VK_RMENU, macos: 0x3D, },\r\n  // General modifiers (canonical mapping)\r\n  Shift => { windows: VK_LSHIFT, macos: 0x38, },\r\n  Ctrl => { windows: VK_LCONTROL, macos: 0x3B, },\r\n  Alt => { windows: VK_LMENU, macos: 0x3A, },\r\n  Cmd => { macos: 0x37, },\r\n  Win => { windows: VK_LWIN, },\r\n  // Platform-specific key mappings (aliases)\r\n  LWin => { windows: VK_LWIN, },\r\n  RWin => { windows: VK_RWIN, },\r\n  LCmd => { macos: 0x37, },\r\n  RCmd => { macos: 0x36, },\r\n  // Special keys\r\n  Space => { windows: VK_SPACE, macos: 0x31, },\r\n  Tab => { windows: VK_TAB, macos: 0x30, },\r\n  Enter => { windows: VK_RETURN, macos: 0x24, },\r\n  // macOS: Backspace == 0x33, Forward Delete == 0x75\r\n  Delete => { windows: VK_DELETE, macos: 0x75, },\r\n  Escape => { windows: VK_ESCAPE, macos: 0x35, },\r\n  Backspace => { windows: VK_BACK, macos: 0x33, },\r\n  // Arrow keys\r\n  Left => { windows: VK_LEFT, macos: 0x7B, },\r\n  Right => { windows: VK_RIGHT, macos: 0x7C, },\r\n  Up => { windows: VK_UP, macos: 0x7E, },\r\n  Down => { windows: VK_DOWN, macos: 0x7D, },\r\n  // Navigation keys\r\n  Home => { windows: VK_HOME, macos: 0x73, },\r\n  End => { windows: VK_END, macos: 0x77, },\r\n  PageUp => { windows: VK_PRIOR, macos: 0x74, },\r\n  PageDown => { windows: VK_NEXT, macos: 0x79, },\r\n  Insert => { windows: VK_INSERT, macos: 0x72, }, // Note: macOS 0x72 is Help\r\n  // OEM keys\r\n  OemSemicolon => { windows: VK_OEM_1, macos: 0x29, },\r\n  OemQuestion => { windows: VK_OEM_2, macos: 0x2C, },\r\n  OemTilde => { windows: VK_OEM_3, macos: 0x32, },\r\n  OemOpenBrackets => { windows: VK_OEM_4, macos: 0x21, },\r\n  OemPipe => { windows: VK_OEM_5, macos: 0x2A, },\r\n  OemCloseBrackets => { windows: VK_OEM_6, macos: 0x1E, },\r\n  OemQuotes => { windows: VK_OEM_7, macos: 0x27, },\r\n  Oem8 => { windows: VK_OEM_8, },\r\n  Oem102 => { windows: VK_OEM_102, },\r\n  OemPlus => { windows: VK_OEM_PLUS, macos: 0x18, },\r\n  OemComma => { windows: VK_OEM_COMMA, macos: 0x2B, },\r\n  OemMinus => { windows: VK_OEM_MINUS, macos: 0x1B, },\r\n  OemPeriod => { windows: VK_OEM_PERIOD, macos: 0x2F, },\r\n  // Numpad\r\n  Numpad0 => { windows: VK_NUMPAD0, macos: 0x52, },\r\n  Numpad1 => { windows: VK_NUMPAD1, macos: 0x53, },\r\n  Numpad2 => { windows: VK_NUMPAD2, macos: 0x54, },\r\n  Numpad3 => { windows: VK_NUMPAD3, macos: 0x55, },\r\n  Numpad4 => { windows: VK_NUMPAD4, macos: 0x56, },\r\n  Numpad5 => { windows: VK_NUMPAD5, macos: 0x57, },\r\n  Numpad6 => { windows: VK_NUMPAD6, macos: 0x58, },\r\n  Numpad7 => { windows: VK_NUMPAD7, macos: 0x59, },\r\n  Numpad8 => { windows: VK_NUMPAD8, macos: 0x5B, },\r\n  Numpad9 => { windows: VK_NUMPAD9, macos: 0x5C, },\r\n  NumpadAdd => { windows: VK_ADD, macos: 0x45, },\r\n  NumpadSubtract => { windows: VK_SUBTRACT, macos: 0x4E, },\r\n  NumpadMultiply => { windows: VK_MULTIPLY, macos: 0x43, },\r\n  NumpadDivide => { windows: VK_DIVIDE, macos: 0x4B, },\r\n  NumpadDecimal => { windows: VK_DECIMAL, macos: 0x41, },\r\n  // Lock keys\r\n  NumLock => { windows: VK_NUMLOCK, macos: 0x47, },\r\n  ScrollLock => { windows: VK_SCROLL, macos: 0x6B, },\r\n  CapsLock => { windows: VK_CAPITAL, macos: 0x39, },\r\n  // Media keys\r\n  VolumeUp => { windows: VK_VOLUME_UP, macos: 0x48, },\r\n  VolumeDown => { windows: VK_VOLUME_DOWN, macos: 0x49, },\r\n  VolumeMute => { windows: VK_VOLUME_MUTE, macos: 0x4A, },\r\n  // TODO: Verify these media keys for macOS.\r\n  MediaNextTrack => { windows: VK_MEDIA_NEXT_TRACK, macos: 0x42, },\r\n  MediaPrevTrack => { windows: VK_MEDIA_PREV_TRACK, macos: 0x4D, },\r\n  MediaStop => { windows: VK_MEDIA_STOP, macos: 0x4C, },\r\n  MediaPlayPause => { windows: VK_MEDIA_PLAY_PAUSE, macos: 0x34, },\r\n  PrintScreen => { windows: VK_SNAPSHOT, },\r\n  // Language-specific keys\r\n  Muhenkan => { windows: VK_NONCONVERT, },\r\n  Henkan => { windows: VK_CONVERT, },\r\n}\r\n\r\n#[cfg(test)]\r\nmod tests {\r\n  use super::*;\r\n\r\n  #[test]\r\n  fn test_key_conversion_roundtrip() {\r\n    let test_keys = [\r\n      Key::A,\r\n      Key::S,\r\n      Key::D,\r\n      Key::F,\r\n      Key::Cmd,\r\n      Key::LAlt,\r\n      Key::RCtrl,\r\n      Key::LShift,\r\n      Key::Space,\r\n      Key::Tab,\r\n      Key::Enter,\r\n      Key::F1,\r\n      Key::F12,\r\n      Key::Left,\r\n      Key::Right,\r\n    ];\r\n\r\n    for key in test_keys {\r\n      let code: KeyCode = key.try_into().unwrap();\r\n      let key2: Key = code.try_into().unwrap();\r\n      assert_eq!(key, key2, \"Roundtrip failed for key: {key:?}\");\r\n    }\r\n  }\r\n\r\n  #[test]\r\n  fn test_platform_specific_key_code() {\r\n    #[cfg(target_os = \"windows\")]\r\n    {\r\n      let code = KeyCode::try_from(Key::Win);\r\n      assert!(code.is_ok());\r\n      let code2 = KeyCode::try_from(Key::Cmd);\r\n      assert!(code2.is_err());\r\n    }\r\n\r\n    #[cfg(target_os = \"macos\")]\r\n    {\r\n      let code = KeyCode::try_from(Key::Win);\r\n      assert!(code.is_err());\r\n      let code2 = KeyCode::try_from(Key::Cmd);\r\n      assert!(code2.is_ok());\r\n    }\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-platform/src/models/length_value.rs",
    "content": "use std::str::FromStr;\r\n\r\nuse regex::Regex;\r\nuse serde::{Deserialize, Deserializer, Serialize};\r\n\r\n#[derive(Debug, Clone, PartialEq, Serialize)]\r\npub struct LengthValue {\r\n  pub amount: f32,\r\n  pub unit: LengthUnit,\r\n}\r\n\r\n#[derive(Debug, Deserialize, Clone, PartialEq, Serialize)]\r\n#[serde(rename_all = \"snake_case\")]\r\npub enum LengthUnit {\r\n  Percentage,\r\n  Pixel,\r\n}\r\n\r\nimpl LengthValue {\r\n  #[must_use]\r\n  pub fn from_px(px: i32) -> Self {\r\n    Self {\r\n      #[allow(clippy::cast_precision_loss)]\r\n      amount: px as f32,\r\n      unit: LengthUnit::Pixel,\r\n    }\r\n  }\r\n\r\n  #[must_use]\r\n  pub fn to_px(&self, total_px: i32, scale_factor: Option<f32>) -> i32 {\r\n    let scale_factor = scale_factor.unwrap_or(1.0);\r\n\r\n    #[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]\r\n    match self.unit {\r\n      LengthUnit::Percentage => (self.amount * total_px as f32) as i32,\r\n      LengthUnit::Pixel => (self.amount * scale_factor) as i32,\r\n    }\r\n  }\r\n\r\n  #[must_use]\r\n  pub fn to_percentage(&self, total_px: i32) -> f32 {\r\n    match self.unit {\r\n      LengthUnit::Percentage => self.amount,\r\n      #[allow(clippy::cast_precision_loss)]\r\n      LengthUnit::Pixel => self.amount / total_px as f32,\r\n    }\r\n  }\r\n}\r\n\r\nimpl FromStr for LengthValue {\r\n  type Err = crate::ParseError;\r\n\r\n  /// Parses a string containing a number followed by a unit (`px`, `%`).\r\n  /// Allows for negative numbers.\r\n  ///\r\n  /// Example:\r\n  /// ```\r\n  /// # use wm_platform::{LengthValue, LengthUnit};\r\n  /// # use std::str::FromStr;\r\n  /// let check = LengthValue {\r\n  ///   amount: 100.0,\r\n  ///   unit: LengthUnit::Pixel,\r\n  /// };\r\n  /// let parsed = LengthValue::from_str(\"100px\");\r\n  /// assert_eq!(parsed.unwrap(), check);\r\n  /// ```\r\n  fn from_str(unparsed: &str) -> Result<Self, crate::ParseError> {\r\n    let units_regex =\r\n      Regex::new(r\"([+-]?\\d+)(%|px)?\").expect(\"Invalid regex.\");\r\n\r\n    let captures = units_regex\r\n      .captures(unparsed)\r\n      .ok_or(crate::ParseError::Length(unparsed.to_string()))?;\r\n\r\n    let unit = match captures.get(2).map_or(\"\", |m| m.as_str()) {\r\n      \"px\" | \"\" => LengthUnit::Pixel,\r\n      \"%\" => LengthUnit::Percentage,\r\n      _ => return Err(crate::ParseError::Length(unparsed.to_string())),\r\n    };\r\n\r\n    let amount = captures\r\n      .get(1)\r\n      .and_then(|m| m.as_str().parse::<f32>().ok())\r\n      // Store percentage units as a fraction of 1.\r\n      .map(|amount| {\r\n        if unit == LengthUnit::Percentage {\r\n          amount / 100.0\r\n        } else {\r\n          amount\r\n        }\r\n      })\r\n      .ok_or(crate::ParseError::Length(unparsed.to_string()))?;\r\n\r\n    Ok(LengthValue { amount, unit })\r\n  }\r\n}\r\n\r\n/// Deserialize a `LengthValue` from either a string or a struct.\r\nimpl<'de> Deserialize<'de> for LengthValue {\r\n  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\r\n  where\r\n    D: Deserializer<'de>,\r\n  {\r\n    #[derive(Deserialize)]\r\n    #[serde(untagged)]\r\n    enum LengthValueDe {\r\n      Struct { amount: f32, unit: LengthUnit },\r\n      String(String),\r\n    }\r\n\r\n    match LengthValueDe::deserialize(deserializer)? {\r\n      LengthValueDe::Struct { amount, unit } => Ok(Self { amount, unit }),\r\n      LengthValueDe::String(str) => {\r\n        Self::from_str(&str).map_err(serde::de::Error::custom)\r\n      }\r\n    }\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-platform/src/models/mod.rs",
    "content": "mod color;\r\nmod corner_style;\r\nmod delta;\r\nmod direction;\r\nmod key;\r\nmod key_code;\r\nmod length_value;\r\nmod opacity_value;\r\nmod point;\r\nmod rect;\r\nmod rect_delta;\r\n\r\npub use color::*;\r\npub use corner_style::*;\r\npub use delta::*;\r\npub use direction::*;\r\npub use key::*;\r\npub use key_code::*;\r\npub use length_value::*;\r\npub use opacity_value::*;\r\npub use point::*;\r\npub use rect::*;\r\npub use rect_delta::*;\r\n"
  },
  {
    "path": "packages/wm-platform/src/models/opacity_value.rs",
    "content": "use std::str::FromStr;\r\n\r\nuse serde::{Deserialize, Deserializer, Serialize};\r\n\r\n#[derive(Debug, Clone, PartialEq, Serialize)]\r\npub struct OpacityValue(pub f32);\r\n\r\nimpl OpacityValue {\r\n  #[must_use]\r\n  pub fn to_alpha(&self) -> u8 {\r\n    #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]\r\n    let alpha = (self.0 * 255.0).round() as u8;\r\n    alpha\r\n  }\r\n\r\n  #[must_use]\r\n  pub fn from_alpha(alpha: u8) -> Self {\r\n    Self(f32::from(alpha) / 255.0)\r\n  }\r\n}\r\n\r\nimpl Default for OpacityValue {\r\n  fn default() -> Self {\r\n    Self(1.0)\r\n  }\r\n}\r\n\r\nimpl FromStr for OpacityValue {\r\n  type Err = crate::ParseError;\r\n\r\n  /// Parses a string for an opacity value. The string must be a percentage\r\n  /// or a decimal number.\r\n  ///\r\n  /// Example:\r\n  /// ```\r\n  /// # use wm_platform::{OpacityValue};\r\n  /// # use std::str::FromStr;\r\n  /// let check = OpacityValue(0.75);\r\n  /// let parsed = OpacityValue::from_str(\"75%\");\r\n  /// assert_eq!(parsed.unwrap(), check);\r\n  /// ```\r\n  fn from_str(unparsed: &str) -> Result<Self, crate::ParseError> {\r\n    let unparsed = unparsed.trim();\r\n\r\n    if unparsed.ends_with('%') {\r\n      let percentage = unparsed\r\n        .trim_end_matches('%')\r\n        .parse::<f32>()\r\n        .map_err(|_| crate::ParseError::Opacity(unparsed.to_string()))?;\r\n\r\n      Ok(Self(percentage / 100.0))\r\n    } else {\r\n      unparsed\r\n        .parse::<f32>()\r\n        .map(Self)\r\n        .map_err(|_| crate::ParseError::Opacity(unparsed.to_string()))\r\n    }\r\n  }\r\n}\r\n\r\n/// Deserialize an `OpacityValue` from either a number or a string.\r\nimpl<'de> Deserialize<'de> for OpacityValue {\r\n  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\r\n  where\r\n    D: Deserializer<'de>,\r\n  {\r\n    #[derive(Deserialize)]\r\n    #[serde(untagged, rename_all = \"camelCase\")]\r\n    enum OpacityValueDe {\r\n      Number(f32),\r\n      String(String),\r\n    }\r\n\r\n    match OpacityValueDe::deserialize(deserializer)? {\r\n      OpacityValueDe::Number(num) => Ok(Self(num)),\r\n      OpacityValueDe::String(str) => {\r\n        Self::from_str(&str).map_err(serde::de::Error::custom)\r\n      }\r\n    }\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-platform/src/models/point.rs",
    "content": "/// Represents an x-y coordinate.\r\n#[derive(Debug, Clone)]\r\npub struct Point {\r\n  pub x: i32,\r\n  pub y: i32,\r\n}\r\n\r\nimpl Point {\r\n  /// Calculates the Euclidean distance between this point and another\r\n  /// point.\r\n  #[must_use]\r\n  pub fn distance_between(&self, other: &Point) -> f32 {\r\n    let dx = self.x - other.x;\r\n    let dy = self.y - other.y;\r\n\r\n    #[allow(clippy::cast_precision_loss)]\r\n    ((dx * dx + dy * dy) as f32).sqrt()\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-platform/src/models/rect.rs",
    "content": "use serde::{Deserialize, Serialize};\r\n\r\nuse crate::{Direction, LengthValue, Point, RectDelta};\r\n\r\n#[derive(Debug, Deserialize, Clone, Serialize, Eq, PartialEq)]\r\npub enum Corner {\r\n  TopLeft,\r\n  TopRight,\r\n  BottomLeft,\r\n  BottomRight,\r\n}\r\n\r\n#[derive(Debug, Deserialize, Clone, Serialize, Eq, PartialEq)]\r\npub struct Rect {\r\n  /// X-coordinate of the left edge of the rectangle.\r\n  pub left: i32,\r\n\r\n  /// Y-coordinate of the top edge of the rectangle.\r\n  pub top: i32,\r\n\r\n  /// X-coordinate of the right edge of the rectangle.\r\n  pub right: i32,\r\n\r\n  /// Y-coordinate of the bottom edge of the rectangle.\r\n  pub bottom: i32,\r\n}\r\n\r\nimpl Rect {\r\n  /// Creates a new `Rect` instance from the coordinates of its left, top,\r\n  /// right, and bottom edges.\r\n  #[must_use]\r\n  pub fn from_ltrb(left: i32, top: i32, right: i32, bottom: i32) -> Self {\r\n    Self {\r\n      left,\r\n      top,\r\n      right,\r\n      bottom,\r\n    }\r\n  }\r\n\r\n  /// Creates a new `Rect` instance from its X/Y coordinates and size.\r\n  #[must_use]\r\n  pub fn from_xy(x: i32, y: i32, width: i32, height: i32) -> Self {\r\n    Self {\r\n      left: x,\r\n      top: y,\r\n      right: x + width,\r\n      bottom: y + height,\r\n    }\r\n  }\r\n\r\n  #[must_use]\r\n  pub fn x(&self) -> i32 {\r\n    self.left\r\n  }\r\n\r\n  #[must_use]\r\n  pub fn y(&self) -> i32 {\r\n    self.top\r\n  }\r\n\r\n  #[must_use]\r\n  pub fn width(&self) -> i32 {\r\n    self.right - self.left\r\n  }\r\n\r\n  #[must_use]\r\n  pub fn height(&self) -> i32 {\r\n    self.bottom - self.top\r\n  }\r\n\r\n  #[must_use]\r\n  pub fn translate_to_coordinates(&self, x: i32, y: i32) -> Self {\r\n    Self::from_xy(x, y, self.width(), self.height())\r\n  }\r\n\r\n  #[must_use]\r\n  pub fn translate_to_center(&self, outer_rect: &Rect) -> Self {\r\n    Self::translate_to_coordinates(\r\n      self,\r\n      outer_rect.left + (outer_rect.width() / 2) - (self.width() / 2),\r\n      outer_rect.top + (outer_rect.height() / 2) - (self.height() / 2),\r\n    )\r\n  }\r\n\r\n  #[must_use]\r\n  pub fn translate_in_direction(\r\n    &self,\r\n    direction: &Direction,\r\n    distance: i32,\r\n  ) -> Rect {\r\n    let (delta_x, delta_y) = match direction {\r\n      Direction::Up => (0, -distance),\r\n      Direction::Down => (0, distance),\r\n      Direction::Left => (-distance, 0),\r\n      Direction::Right => (distance, 0),\r\n    };\r\n\r\n    Self::from_xy(\r\n      self.x() + delta_x,\r\n      self.y() + delta_y,\r\n      self.width(),\r\n      self.height(),\r\n    )\r\n  }\r\n\r\n  /// Returns a new `Rect` that is clamped within the bounds of the given\r\n  /// outer rectangle. Attempts to preserve the width and height of the\r\n  /// original rectangle.\r\n  #[must_use]\r\n  pub fn clamp(&self, outer_rect: &Rect) -> Self {\r\n    Self::from_xy(\r\n      self.left.max(outer_rect.left),\r\n      self.top.max(outer_rect.top),\r\n      self.width().min(outer_rect.width()),\r\n      self.height().min(outer_rect.height()),\r\n    )\r\n  }\r\n\r\n  #[must_use]\r\n  pub fn clamp_size(&self, width: i32, height: i32) -> Self {\r\n    Self::from_xy(\r\n      self.x(),\r\n      self.y(),\r\n      self.width().min(width),\r\n      self.height().min(height),\r\n    )\r\n  }\r\n\r\n  #[must_use]\r\n  pub fn center_point(&self) -> Point {\r\n    Point {\r\n      x: self.left + (self.width() / 2),\r\n      y: self.top + (self.height() / 2),\r\n    }\r\n  }\r\n\r\n  #[must_use]\r\n  pub fn corner(&self, corner: &Corner) -> Point {\r\n    match corner {\r\n      Corner::TopLeft => Point {\r\n        x: self.left,\r\n        y: self.top,\r\n      },\r\n      Corner::TopRight => Point {\r\n        x: self.right,\r\n        y: self.top,\r\n      },\r\n      Corner::BottomLeft => Point {\r\n        x: self.left,\r\n        y: self.bottom,\r\n      },\r\n      Corner::BottomRight => Point {\r\n        x: self.right,\r\n        y: self.bottom,\r\n      },\r\n    }\r\n  }\r\n\r\n  /// Gets the delta between this rect and another rect.\r\n  #[must_use]\r\n  pub fn delta(&self, other: &Rect) -> RectDelta {\r\n    RectDelta {\r\n      left: LengthValue::from_px(other.left - self.left),\r\n      top: LengthValue::from_px(other.top - self.top),\r\n      right: LengthValue::from_px(self.right - other.right),\r\n      bottom: LengthValue::from_px(self.bottom - other.bottom),\r\n    }\r\n  }\r\n\r\n  #[must_use]\r\n  pub fn apply_delta(\r\n    &self,\r\n    delta: &RectDelta,\r\n    scale_factor: Option<f32>,\r\n  ) -> Self {\r\n    Self::from_ltrb(\r\n      self.left - delta.left.to_px(self.width(), scale_factor),\r\n      self.top - delta.top.to_px(self.height(), scale_factor),\r\n      self.right + delta.right.to_px(self.width(), scale_factor),\r\n      self.bottom + delta.bottom.to_px(self.height(), scale_factor),\r\n    )\r\n  }\r\n\r\n  // Gets the amount of overlap between the x-coordinates of the two rects.\r\n  #[must_use]\r\n  pub fn x_overlap(&self, other: &Rect) -> i32 {\r\n    self.right.min(other.right) - self.x().max(other.x())\r\n  }\r\n\r\n  // Gets the amount of overlap between the y-coordinates of the two rects.\r\n  #[must_use]\r\n  pub fn y_overlap(&self, other: &Rect) -> i32 {\r\n    self.bottom.min(other.bottom) - self.y().max(other.y())\r\n  }\r\n\r\n  /// Gets the intersection area of this rect and another rect.\r\n  #[must_use]\r\n  pub fn intersection_area(&self, other: &Rect) -> i32 {\r\n    let x_overlap = self.x_overlap(other);\r\n    let y_overlap = self.y_overlap(other);\r\n\r\n    if x_overlap > 0 && y_overlap > 0 {\r\n      x_overlap * y_overlap\r\n    } else {\r\n      0\r\n    }\r\n  }\r\n\r\n  #[must_use]\r\n  pub fn contains_point(&self, point: &Point) -> bool {\r\n    let is_in_x = point.x >= self.left && point.x <= self.right;\r\n    let is_in_y = point.y >= self.top && point.y <= self.bottom;\r\n    is_in_x && is_in_y\r\n  }\r\n\r\n  /// Gets whether this rect fully encloses another rect.\r\n  #[must_use]\r\n  pub fn contains_rect(&self, other: &Rect) -> bool {\r\n    self.left <= other.left\r\n      && self.top <= other.top\r\n      && self.right >= other.right\r\n      && self.bottom >= other.bottom\r\n  }\r\n\r\n  /// Creates a new rect that is inset by the given amount of pixels on all\r\n  /// sides.\r\n  ///\r\n  /// The `inset_px` can be a positive number to create a smaller rect\r\n  /// (inset), or a negative number to create a larger rect (outset).\r\n  #[must_use]\r\n  pub fn inset(&self, inset_px: i32) -> Self {\r\n    Self::from_ltrb(\r\n      self.left + inset_px,\r\n      self.top + inset_px,\r\n      self.right - inset_px,\r\n      self.bottom - inset_px,\r\n    )\r\n  }\r\n\r\n  #[must_use]\r\n  pub fn distance_to_point(&self, point: &Point) -> f32 {\r\n    let dx = (self.x() - point.x)\r\n      .abs()\r\n      .max((self.x() + self.width() - point.x).abs());\r\n\r\n    let dy = (self.y() - point.y)\r\n      .abs()\r\n      .max((self.y() + self.height() - point.y).abs());\r\n\r\n    #[allow(clippy::cast_precision_loss)]\r\n    ((dx * dx + dy * dy) as f32).sqrt()\r\n  }\r\n\r\n  /// Returns the union of this rect and another rect.\r\n  ///\r\n  /// The union is the smallest rect that contains both rects, taking the\r\n  /// minimum left/top and maximum right/bottom coordinates.\r\n  #[must_use]\r\n  pub fn union(&self, other: &Rect) -> Self {\r\n    Self::from_ltrb(\r\n      self.left.min(other.left),\r\n      self.top.min(other.top),\r\n      self.right.max(other.right),\r\n      self.bottom.max(other.bottom),\r\n    )\r\n  }\r\n}\r\n\r\n#[cfg(test)]\r\nmod tests {\r\n  use super::*;\r\n\r\n  #[test]\r\n  fn intersection_area() {\r\n    // Full overlap.\r\n    let r1 = Rect::from_xy(0, 0, 100, 100);\r\n    let r2 = Rect::from_xy(0, 0, 100, 100);\r\n    assert_eq!(r1.intersection_area(&r2), 10000); // 100 * 100\r\n\r\n    // Partial overlap.\r\n    let r1 = Rect::from_xy(0, 0, 100, 100);\r\n    let r2 = Rect::from_xy(50, 50, 100, 100);\r\n    assert_eq!(r1.intersection_area(&r2), 2500); // 50 * 50\r\n\r\n    // No overlap.\r\n    let r1 = Rect::from_xy(0, 0, 100, 100);\r\n    let r2 = Rect::from_xy(200, 200, 100, 100);\r\n    assert_eq!(r1.intersection_area(&r2), 0);\r\n\r\n    // No overlap (edges touching).\r\n    let r1 = Rect::from_xy(0, 0, 100, 100);\r\n    let r2 = Rect::from_xy(100, 0, 100, 100);\r\n    assert_eq!(r1.intersection_area(&r2), 0);\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-platform/src/models/rect_delta.rs",
    "content": "use serde::{Deserialize, Serialize};\r\n\r\nuse super::LengthValue;\r\n\r\n#[derive(Debug, Deserialize, Clone, Serialize)]\r\npub struct RectDelta {\r\n  /// The delta in x-coordinates on the left of the rectangle.\r\n  pub left: LengthValue,\r\n\r\n  /// The delta in y-coordinates on the top of the rectangle.\r\n  pub top: LengthValue,\r\n\r\n  /// The delta in x-coordinates on the right of the rectangle.\r\n  pub right: LengthValue,\r\n\r\n  /// The delta in y-coordinates on the bottom of the rectangle.\r\n  pub bottom: LengthValue,\r\n}\r\n\r\nimpl RectDelta {\r\n  #[must_use]\r\n  pub fn new(\r\n    left: LengthValue,\r\n    top: LengthValue,\r\n    right: LengthValue,\r\n    bottom: LengthValue,\r\n  ) -> Self {\r\n    Self {\r\n      left,\r\n      top,\r\n      right,\r\n      bottom,\r\n    }\r\n  }\r\n\r\n  /// Checks if the rectangle delta has a value greater than 1.0(px/%) for\r\n  /// any of its sides.\r\n  #[must_use]\r\n  pub fn is_significant(&self) -> bool {\r\n    self.bottom.amount > 1.0\r\n      || self.top.amount > 1.0\r\n      || self.left.amount > 1.0\r\n      || self.right.amount > 1.0\r\n  }\r\n\r\n  /// Creates a new `RectDelta` with all sides set to 0px.\r\n  #[must_use]\r\n  pub fn zero() -> Self {\r\n    Self::new(\r\n      LengthValue::from_px(0),\r\n      LengthValue::from_px(0),\r\n      LengthValue::from_px(0),\r\n      LengthValue::from_px(0),\r\n    )\r\n  }\r\n\r\n  /// Gets the inverse of this delta by negating all values.\r\n  ///\r\n  /// Returns a new `RectDelta` instance.\r\n  #[must_use]\r\n  pub fn inverse(&self) -> Self {\r\n    RectDelta::new(\r\n      LengthValue {\r\n        amount: -self.left.amount,\r\n        unit: self.left.unit.clone(),\r\n      },\r\n      LengthValue {\r\n        amount: -self.top.amount,\r\n        unit: self.top.unit.clone(),\r\n      },\r\n      LengthValue {\r\n        amount: -self.right.amount,\r\n        unit: self.right.unit.clone(),\r\n      },\r\n      LengthValue {\r\n        amount: -self.bottom.amount,\r\n        unit: self.bottom.unit.clone(),\r\n      },\r\n    )\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-platform/src/mouse_listener.rs",
    "content": "use tokio::sync::mpsc;\r\n\r\nuse crate::{platform_event::MouseEvent, platform_impl, Dispatcher};\r\n\r\n/// Available mouse events that [`MouseListener`] can listen for.\r\n#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]\r\npub enum MouseEventKind {\r\n  Move,\r\n  LeftButtonDown,\r\n  LeftButtonUp,\r\n  RightButtonDown,\r\n  RightButtonUp,\r\n}\r\n\r\n/// A listener for system-wide mouse events.\r\npub struct MouseListener {\r\n  /// Receiver for outgoing mouse events.\r\n  event_rx: mpsc::UnboundedReceiver<MouseEvent>,\r\n\r\n  /// Inner platform-specific mouse listener.\r\n  inner: platform_impl::MouseListener,\r\n}\r\n\r\nimpl MouseListener {\r\n  /// Creates a new [`MouseListener`] with the specified enabled events.\r\n  pub fn new(\r\n    enabled_events: &[MouseEventKind],\r\n    dispatcher: &Dispatcher,\r\n  ) -> crate::Result<Self> {\r\n    let (event_tx, event_rx) = mpsc::unbounded_channel();\r\n    let inner = platform_impl::MouseListener::new(\r\n      enabled_events,\r\n      event_tx,\r\n      dispatcher,\r\n    )?;\r\n\r\n    Ok(Self { event_rx, inner })\r\n  }\r\n\r\n  /// Returns the next mouse event from the listener.\r\n  ///\r\n  /// This will block until a mouse event is available.\r\n  pub async fn next_event(&mut self) -> Option<MouseEvent> {\r\n    self.event_rx.recv().await\r\n  }\r\n\r\n  /// Enables or disables the underlying mouse listener.\r\n  pub fn enable(&mut self, enabled: bool) -> crate::Result<()> {\r\n    self.inner.enable(enabled)\r\n  }\r\n\r\n  /// Updates the set of enabled mouse events to listen for.\r\n  pub fn set_enabled_events(\r\n    &mut self,\r\n    enabled_events: &[MouseEventKind],\r\n  ) -> crate::Result<()> {\r\n    self.inner.set_enabled_events(enabled_events)\r\n  }\r\n\r\n  /// Terminates the mouse listener.\r\n  pub fn terminate(&mut self) -> crate::Result<()> {\r\n    self.inner.terminate()\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-platform/src/native_window.rs",
    "content": "#[cfg(target_os = \"macos\")]\r\nuse objc2_application_services::AXUIElement;\r\n#[cfg(target_os = \"macos\")]\r\nuse objc2_core_foundation::{CFBoolean, CFRetained, CFString};\r\n#[cfg(target_os = \"windows\")]\r\nuse windows::Win32::{\r\n  Foundation::HWND,\r\n  UI::WindowsAndMessaging::{\r\n    SET_WINDOW_POS_FLAGS, WINDOW_EX_STYLE, WINDOW_STYLE,\r\n  },\r\n};\r\n\r\nuse crate::{platform_impl, Rect};\r\n#[cfg(target_os = \"macos\")]\r\nuse crate::{platform_impl::AXUIElementExt, ThreadBound};\r\n#[cfg(target_os = \"windows\")]\r\nuse crate::{Color, CornerStyle, Delta, OpacityValue, RectDelta};\r\n\r\n/// Unique identifier of a window.\r\n///\r\n/// Can be obtained with `window.id()`.\r\n///\r\n/// # Platform-specific\r\n///\r\n/// - **Windows**: `isize` (`HWND`)\r\n/// - **macOS**: `u32` (`CGWindowID`)\r\n#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]\r\npub struct WindowId(\r\n  #[cfg(target_os = \"windows\")] pub isize,\r\n  #[cfg(target_os = \"macos\")] pub u32,\r\n);\r\n\r\nimpl WindowId {\r\n  #[cfg(target_os = \"macos\")]\r\n  pub(crate) fn from_window_element(el: &CFRetained<AXUIElement>) -> Self {\r\n    let mut window_id = 0;\r\n\r\n    unsafe {\r\n      platform_impl::ffi::_AXUIElementGetWindow(\r\n        CFRetained::as_ptr(el),\r\n        &raw mut window_id,\r\n      )\r\n    };\r\n\r\n    Self(window_id)\r\n  }\r\n}\r\n\r\n#[derive(Clone, Debug, PartialEq, Eq)]\r\npub enum WindowZOrder {\r\n  Normal,\r\n  AfterWindow(WindowId),\r\n  Top,\r\n  TopMost,\r\n}\r\n\r\n/// macOS-specific extension trait for [`NativeWindow`].\r\n#[cfg(target_os = \"macos\")]\r\npub trait NativeWindowExtMacOs {\r\n  /// Gets the `AXUIElement` instance for this window.\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// This method is only available on macOS.\r\n  fn ax_ui_element(&self) -> &ThreadBound<CFRetained<AXUIElement>>;\r\n\r\n  /// Gets the bundle ID of the application that owns the window.\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// This method is only available on macOS.\r\n  fn bundle_id(&self) -> Option<String>;\r\n\r\n  /// Gets the role of the window (e.g. `AXWindow`).\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// This method is only available on macOS.\r\n  fn role(&self) -> crate::Result<String>;\r\n\r\n  /// Gets the sub-role of the window (e.g. `AXStandardWindow`).\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// This method is only available on macOS.\r\n  fn subrole(&self) -> crate::Result<String>;\r\n\r\n  /// Whether the window is modal.\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// This method is only available on macOS.\r\n  fn is_modal(&self) -> crate::Result<bool>;\r\n\r\n  /// Whether the window is the main window for its application.\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// This method is only available on macOS.\r\n  fn is_main(&self) -> crate::Result<bool>;\r\n}\r\n\r\n#[cfg(target_os = \"macos\")]\r\nimpl NativeWindowExtMacOs for NativeWindow {\r\n  fn ax_ui_element(&self) -> &ThreadBound<CFRetained<AXUIElement>> {\r\n    &self.inner.element\r\n  }\r\n\r\n  fn bundle_id(&self) -> Option<String> {\r\n    self.inner.application.bundle_id()\r\n  }\r\n\r\n  fn role(&self) -> crate::Result<String> {\r\n    self.inner.element.with(|el| {\r\n      el.get_attribute::<CFString>(\"AXRole\")\r\n        .map(|cf_string| cf_string.to_string())\r\n    })?\r\n  }\r\n\r\n  fn subrole(&self) -> crate::Result<String> {\r\n    self.inner.element.with(|el| {\r\n      el.get_attribute::<CFString>(\"AXSubrole\")\r\n        .map(|cf_string| cf_string.to_string())\r\n    })?\r\n  }\r\n\r\n  fn is_modal(&self) -> crate::Result<bool> {\r\n    self.inner.element.with(|el| {\r\n      el.get_attribute::<CFBoolean>(\"AXModal\")\r\n        .map(|cf_bool| cf_bool.value())\r\n    })?\r\n  }\r\n\r\n  fn is_main(&self) -> crate::Result<bool> {\r\n    self.inner.element.with(|el| {\r\n      el.get_attribute::<CFBoolean>(\"AXMain\")\r\n        .map(|cf_bool| cf_bool.value())\r\n    })?\r\n  }\r\n}\r\n\r\n/// Windows-specific extensions for [`NativeWindow`].\r\n#[cfg(target_os = \"windows\")]\r\npub trait NativeWindowWindowsExt {\r\n  /// Creates a [`NativeWindow`] from a window handle.\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// This method is only available on Windows.\r\n  fn from_handle(handle: isize) -> NativeWindow;\r\n\r\n  /// Gets the window handle.\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// This method is only available on Windows.\r\n  fn hwnd(&self) -> HWND;\r\n\r\n  /// Gets the class name of the window.\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// This method is only available on Windows.\r\n  fn class_name(&self) -> crate::Result<String>;\r\n\r\n  /// Gets the window's frame, including the window's shadow borders.\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// This method is only available on Windows.\r\n  fn frame_with_shadows(&self) -> crate::Result<Rect>;\r\n\r\n  /// Gets the delta between the window's frame and the window's border.\r\n  /// This represents the size of a window's shadow borders.\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// This method is only available on Windows.\r\n  fn shadow_borders(&self) -> crate::Result<RectDelta>;\r\n\r\n  /// Whether the window has an owner window.\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// This method is only available on Windows.\r\n  fn has_owner_window(&self) -> bool;\r\n\r\n  /// Whether the window has the given window style flag(s) set.\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// This method is only available on Windows.\r\n  fn has_window_style(&self, style: WINDOW_STYLE) -> bool;\r\n\r\n  /// Whether the window has the given extended window style flag(s) set.\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// This method is only available on Windows.\r\n  fn has_window_style_ex(&self, style: WINDOW_EX_STYLE) -> bool;\r\n\r\n  /// Thin wrapper around [`SetWindowPos`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowpos).\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// This method is only available on Windows.\r\n  fn set_window_pos(\r\n    &self,\r\n    z_order: &WindowZOrder,\r\n    rect: &Rect,\r\n    flags: SET_WINDOW_POS_FLAGS,\r\n  ) -> crate::Result<()>;\r\n\r\n  /// Shows the window asynchronously.\r\n  ///\r\n  /// NOTE: Cloaked windows do not get shown until uncloaked.\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// This method is only available on Windows.\r\n  fn show(&self) -> crate::Result<()>;\r\n\r\n  /// Hides the window asynchronously.\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// This method is only available on Windows.\r\n  fn hide(&self) -> crate::Result<()>;\r\n\r\n  /// Restores the window (unminimizes and unmaximizes).\r\n  ///\r\n  /// If `outer_frame` is provided, the window will be restored to the\r\n  /// specified position. This avoids flickering compared to restoring\r\n  /// and then repositioning the window.\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// This method is only available on Windows.\r\n  fn restore(&self, outer_frame: Option<&Rect>) -> crate::Result<()>;\r\n\r\n  /// Cloaks or uncloaks the window.\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// This method is only available on Windows.\r\n  fn set_cloaked(&self, cloaked: bool) -> crate::Result<()>;\r\n\r\n  /// Marks the window as fullscreen.\r\n  ///\r\n  /// Causes the native Windows taskbar to be moved to the bottom of the\r\n  /// z-order when this window is active.\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// This method is only available on Windows.\r\n  fn mark_fullscreen(&self, fullscreen: bool) -> crate::Result<()>;\r\n\r\n  /// Adds or removes the window from the native taskbar.\r\n  ///\r\n  /// Cloaked windows are normally always shown in the taskbar, but can be\r\n  /// manually toggled. Hidden windows (`SW_HIDE`) can never be shown in\r\n  /// the taskbar.\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// This method is only available on Windows.\r\n  fn set_taskbar_visibility(&self, visible: bool) -> crate::Result<()>;\r\n\r\n  /// Adds the given extended window style flag(s) to the window.\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// This method is only available on Windows.\r\n  fn add_window_style_ex(&self, style: WINDOW_EX_STYLE);\r\n\r\n  /// Sets the window's z-order.\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// This method is only available on Windows.\r\n  fn set_z_order(&self, zorder: &WindowZOrder) -> crate::Result<()>;\r\n\r\n  /// Sets the visibility of the window's title bar.\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// This method is only available on Windows.\r\n  fn set_title_bar_visibility(&self, visible: bool) -> crate::Result<()>;\r\n\r\n  /// Sets the color of the window's border.\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// This method is only available on Windows.\r\n  fn set_border_color(&self, color: Option<&Color>) -> crate::Result<()>;\r\n\r\n  /// Sets the corner style of the window.\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// This method is only available on Windows.\r\n  fn set_corner_style(\r\n    &self,\r\n    corner_style: &CornerStyle,\r\n  ) -> crate::Result<()>;\r\n\r\n  /// Sets the transparency of the window.\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// This method is only available on Windows.\r\n  fn set_transparency(\r\n    &self,\r\n    opacity_value: &OpacityValue,\r\n  ) -> crate::Result<()>;\r\n\r\n  /// Adjusts the window's transparency by a relative delta.\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// This method is only available on Windows.\r\n  fn adjust_transparency(\r\n    &self,\r\n    opacity_delta: &Delta<OpacityValue>,\r\n  ) -> crate::Result<()>;\r\n}\r\n\r\n#[cfg(target_os = \"windows\")]\r\nimpl NativeWindowWindowsExt for NativeWindow {\r\n  fn from_handle(handle: isize) -> Self {\r\n    platform_impl::NativeWindow::new(handle).into()\r\n  }\r\n\r\n  fn hwnd(&self) -> HWND {\r\n    self.inner.hwnd()\r\n  }\r\n\r\n  fn class_name(&self) -> crate::Result<String> {\r\n    self.inner.class_name()\r\n  }\r\n\r\n  fn frame_with_shadows(&self) -> crate::Result<Rect> {\r\n    self.inner.frame_with_shadows()\r\n  }\r\n\r\n  fn shadow_borders(&self) -> crate::Result<RectDelta> {\r\n    self.inner.shadow_borders()\r\n  }\r\n\r\n  fn has_owner_window(&self) -> bool {\r\n    self.inner.has_owner_window()\r\n  }\r\n\r\n  fn has_window_style(&self, style: WINDOW_STYLE) -> bool {\r\n    self.inner.has_window_style(style)\r\n  }\r\n\r\n  fn has_window_style_ex(&self, style: WINDOW_EX_STYLE) -> bool {\r\n    self.inner.has_window_style_ex(style)\r\n  }\r\n\r\n  fn set_window_pos(\r\n    &self,\r\n    z_order: &WindowZOrder,\r\n    rect: &Rect,\r\n    flags: SET_WINDOW_POS_FLAGS,\r\n  ) -> crate::Result<()> {\r\n    self.inner.set_window_pos(z_order, rect, flags)\r\n  }\r\n\r\n  fn show(&self) -> crate::Result<()> {\r\n    self.inner.show()\r\n  }\r\n\r\n  fn hide(&self) -> crate::Result<()> {\r\n    self.inner.hide()\r\n  }\r\n\r\n  fn restore(&self, outer_frame: Option<&Rect>) -> crate::Result<()> {\r\n    self.inner.restore(outer_frame)\r\n  }\r\n\r\n  fn set_cloaked(&self, cloaked: bool) -> crate::Result<()> {\r\n    self.inner.set_cloaked(cloaked)\r\n  }\r\n\r\n  fn mark_fullscreen(&self, fullscreen: bool) -> crate::Result<()> {\r\n    self.inner.mark_fullscreen(fullscreen)\r\n  }\r\n\r\n  fn set_taskbar_visibility(&self, visible: bool) -> crate::Result<()> {\r\n    self.inner.set_taskbar_visibility(visible)\r\n  }\r\n\r\n  fn add_window_style_ex(&self, style: WINDOW_EX_STYLE) {\r\n    self.inner.add_window_style_ex(style);\r\n  }\r\n\r\n  fn set_z_order(&self, z_order: &WindowZOrder) -> crate::Result<()> {\r\n    self.inner.set_z_order(z_order)\r\n  }\r\n\r\n  fn set_title_bar_visibility(&self, visible: bool) -> crate::Result<()> {\r\n    self.inner.set_title_bar_visibility(visible)\r\n  }\r\n\r\n  fn set_border_color(&self, color: Option<&Color>) -> crate::Result<()> {\r\n    self.inner.set_border_color(color)\r\n  }\r\n\r\n  fn set_corner_style(\r\n    &self,\r\n    corner_style: &CornerStyle,\r\n  ) -> crate::Result<()> {\r\n    self.inner.set_corner_style(corner_style)\r\n  }\r\n\r\n  fn set_transparency(\r\n    &self,\r\n    opacity_value: &OpacityValue,\r\n  ) -> crate::Result<()> {\r\n    self.inner.set_transparency(opacity_value)\r\n  }\r\n\r\n  fn adjust_transparency(\r\n    &self,\r\n    opacity_delta: &Delta<OpacityValue>,\r\n  ) -> crate::Result<()> {\r\n    self.inner.adjust_transparency(opacity_delta)\r\n  }\r\n}\r\n\r\n#[derive(Clone, Debug)]\r\npub struct NativeWindow {\r\n  pub(crate) inner: platform_impl::NativeWindow,\r\n}\r\n\r\nimpl NativeWindow {\r\n  /// Gets the unique identifier for this window.\r\n  #[must_use]\r\n  pub fn id(&self) -> WindowId {\r\n    self.inner.id()\r\n  }\r\n\r\n  /// Gets the window's title.\r\n  ///\r\n  /// Note that empty strings are valid window titles.\r\n  ///\r\n  /// # Errors\r\n  ///\r\n  /// Returns [`Error::WindowNotFound`] if the window is invalid.\r\n  pub fn title(&self) -> crate::Result<String> {\r\n    self.inner.title()\r\n  }\r\n\r\n  pub fn process_name(&self) -> crate::Result<String> {\r\n    self.inner.process_name()\r\n  }\r\n\r\n  /// Gets a rectangle of the window's size and position.\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// - **Windows**: Includes the window's shadow borders.\r\n  /// - **macOS**: If the window was previously resized to a value outside\r\n  ///   of the window's allowed min/max width & height (e.g. via calling\r\n  ///   `set_frame`), this can return those invalid values and might not\r\n  ///   reflect the actual window size.\r\n  pub fn frame(&self) -> crate::Result<Rect> {\r\n    self.inner.frame()\r\n  }\r\n\r\n  /// Gets the window's position as (x, y) coordinates.\r\n  pub fn position(&self) -> crate::Result<(f64, f64)> {\r\n    self.inner.position()\r\n  }\r\n\r\n  /// Gets the window's size as (width, height).\r\n  pub fn size(&self) -> crate::Result<(f64, f64)> {\r\n    self.inner.size()\r\n  }\r\n\r\n  /// Whether the window is still valid.\r\n  ///\r\n  /// Returns `true` if the underlying window is still alive.\r\n  #[must_use]\r\n  pub fn is_valid(&self) -> bool {\r\n    self.inner.is_valid()\r\n  }\r\n\r\n  /// Whether the window is actually visible.\r\n  pub fn is_visible(&self) -> crate::Result<bool> {\r\n    self.inner.is_visible()\r\n  }\r\n\r\n  /// Whether the window is minimized.\r\n  pub fn is_minimized(&self) -> crate::Result<bool> {\r\n    self.inner.is_minimized()\r\n  }\r\n\r\n  /// Whether the window is maximized.\r\n  pub fn is_maximized(&self) -> crate::Result<bool> {\r\n    self.inner.is_maximized()\r\n  }\r\n\r\n  /// Whether the window can be resized.\r\n  pub fn is_resizable(&self) -> crate::Result<bool> {\r\n    self.inner.is_resizable()\r\n  }\r\n\r\n  /// Whether the window is the OS's desktop window.\r\n  pub fn is_desktop_window(&self) -> crate::Result<bool> {\r\n    self.inner.is_desktop_window()\r\n  }\r\n\r\n  /// Repositions and resizes the window to the specified rectangle.\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// - **Windows**: Automatically adjusts the `rect` prior to calling [`SetWindowPos`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowpos)\r\n  ///   to include the window's shadow borders. To set the window's\r\n  ///   position directly, use [`NativeWindowWindowsExt::set_window_pos`].\r\n  pub fn set_frame(&self, rect: &Rect) -> crate::Result<()> {\r\n    self.inner.set_frame(rect)\r\n  }\r\n\r\n  /// Resizes the window to the specified size.\r\n  pub fn resize(&self, width: i32, height: i32) -> crate::Result<()> {\r\n    self.inner.resize(width, height)\r\n  }\r\n\r\n  /// Repositions the window to the specified position.\r\n  pub fn reposition(&self, x: i32, y: i32) -> crate::Result<()> {\r\n    self.inner.reposition(x, y)\r\n  }\r\n\r\n  pub fn minimize(&self) -> crate::Result<()> {\r\n    self.inner.minimize()\r\n  }\r\n\r\n  pub fn maximize(&self) -> crate::Result<()> {\r\n    self.inner.maximize()\r\n  }\r\n\r\n  /// Sets focus to the window and raises it to the top of the z-order.\r\n  pub fn focus(&self) -> crate::Result<()> {\r\n    self.inner.focus()\r\n  }\r\n\r\n  /// Closes the window.\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// - **Windows**: This sends a `WM_CLOSE` message to the window.\r\n  /// - **macOS**: This simulates pressing the close button on the window's\r\n  ///   title bar.\r\n  pub fn close(&self) -> crate::Result<()> {\r\n    self.inner.close()\r\n  }\r\n}\r\n\r\nimpl PartialEq for NativeWindow {\r\n  fn eq(&self, other: &Self) -> bool {\r\n    self.inner.id() == other.inner.id()\r\n  }\r\n}\r\n\r\nimpl Eq for NativeWindow {}\r\n"
  },
  {
    "path": "packages/wm-platform/src/platform_event.rs",
    "content": "use super::NativeWindow;\r\nuse crate::{\r\n  platform_impl::WindowEventNotificationInner, Keybinding, MouseEventKind,\r\n  Point, WindowId,\r\n};\r\n\r\n#[derive(Clone, Debug)]\r\npub enum PlatformEvent {\r\n  Window(WindowEvent),\r\n  Keybinding(KeybindingEvent),\r\n  Mouse(MouseEvent),\r\n  DisplaySettingsChanged,\r\n}\r\n\r\n#[derive(Clone, Debug)]\r\npub enum WindowEvent {\r\n  /// Window gained focus.\r\n  Focused {\r\n    window: NativeWindow,\r\n    notification: WindowEventNotification,\r\n  },\r\n\r\n  /// Window was hidden.\r\n  Hidden {\r\n    window: NativeWindow,\r\n    notification: WindowEventNotification,\r\n  },\r\n\r\n  /// Size or position of window has changed.\r\n  ///\r\n  /// `is_interactive_start` and `is_interactive_end` indicate whether the\r\n  /// move or resize was initiated via manual interaction with the\r\n  /// window's drag handles.\r\n  ///\r\n  /// # Platform-specific\r\n  ///\r\n  /// - **Windows**: Corresponds to `EVENT_OBJECT_LOCATIONCHANGE`,\r\n  ///   `EVENT_SYSTEM_MOVESIZESTART`, and `EVENT_SYSTEM_MOVESIZEEND`.\r\n  /// - **macOS**: Corresponds to `AXWindowMoved` and `AXWindowResized`.\r\n  ///   The `is_interactive_start` and `is_interactive_end` flags are\r\n  ///   always `false`.\r\n  MovedOrResized {\r\n    window: NativeWindow,\r\n    is_interactive_start: bool,\r\n    is_interactive_end: bool,\r\n    notification: WindowEventNotification,\r\n  },\r\n\r\n  /// Window was minimized.\r\n  Minimized {\r\n    window: NativeWindow,\r\n    notification: WindowEventNotification,\r\n  },\r\n\r\n  /// Window was restored from minimized state.\r\n  MinimizeEnded {\r\n    window: NativeWindow,\r\n    notification: WindowEventNotification,\r\n  },\r\n\r\n  /// Window became visible.\r\n  Shown {\r\n    window: NativeWindow,\r\n    notification: WindowEventNotification,\r\n  },\r\n\r\n  /// Window title changed.\r\n  TitleChanged {\r\n    window: NativeWindow,\r\n    notification: WindowEventNotification,\r\n  },\r\n\r\n  /// Window was destroyed.\r\n  Destroyed {\r\n    window_id: WindowId,\r\n    notification: WindowEventNotification,\r\n  },\r\n}\r\n\r\nimpl WindowEvent {\r\n  /// Get the window handle if available (not available for\r\n  /// `WindowEvent::Destroyed`).\r\n  #[must_use]\r\n  pub fn window(&self) -> Option<&NativeWindow> {\r\n    match self {\r\n      Self::Focused { window, .. }\r\n      | Self::Hidden { window, .. }\r\n      | Self::MovedOrResized { window, .. }\r\n      | Self::Minimized { window, .. }\r\n      | Self::MinimizeEnded { window, .. }\r\n      | Self::Shown { window, .. }\r\n      | Self::TitleChanged { window, .. } => Some(window),\r\n      Self::Destroyed { .. } => None,\r\n    }\r\n  }\r\n\r\n  /// Returns the platform-specific window event notification.\r\n  #[must_use]\r\n  pub fn notification(&self) -> &WindowEventNotification {\r\n    match self {\r\n      Self::Focused { notification, .. }\r\n      | Self::Hidden { notification, .. }\r\n      | Self::MovedOrResized { notification, .. }\r\n      | Self::Minimized { notification, .. }\r\n      | Self::MinimizeEnded { notification, .. }\r\n      | Self::Shown { notification, .. }\r\n      | Self::TitleChanged { notification, .. }\r\n      | Self::Destroyed { notification, .. } => notification,\r\n    }\r\n  }\r\n}\r\n\r\n/// Platform-specific window event notification.\r\n///\r\n/// Some events are \"synthetic\" and do not have a corresponding\r\n/// notification (represented by `None`).\r\n///\r\n/// Synthetic events can occur when:\r\n/// * On macOS, `WindowEvent::Shown` is emitted for new visible windows\r\n///   even if a different notification is received first.\r\n#[derive(Clone, Debug)]\r\npub struct WindowEventNotification(\r\n  pub Option<WindowEventNotificationInner>,\r\n);\r\n\r\n#[derive(Clone, Debug)]\r\npub struct KeybindingEvent(pub Keybinding);\r\n\r\n#[derive(Clone, Debug, Eq, PartialEq)]\r\npub enum MouseButton {\r\n  Left,\r\n  Right,\r\n}\r\n\r\n/// Tracks which mouse buttons are currently pressed.\r\n#[derive(Clone, Copy, Debug, Default)]\r\npub struct PressedButtons {\r\n  left: bool,\r\n  right: bool,\r\n}\r\n\r\nimpl PressedButtons {\r\n  /// Returns whether the given button is currently pressed.\r\n  #[must_use]\r\n  pub fn contains(&self, button: &MouseButton) -> bool {\r\n    match button {\r\n      MouseButton::Left => self.left,\r\n      MouseButton::Right => self.right,\r\n    }\r\n  }\r\n\r\n  /// Updates button state based on a mouse event.\r\n  pub(crate) fn update(&mut self, event: MouseEventKind) {\r\n    match event {\r\n      MouseEventKind::LeftButtonDown => self.left = true,\r\n      MouseEventKind::LeftButtonUp => self.left = false,\r\n      MouseEventKind::RightButtonDown => self.right = true,\r\n      MouseEventKind::RightButtonUp => self.right = false,\r\n      MouseEventKind::Move => {}\r\n    }\r\n  }\r\n}\r\n\r\n#[derive(Clone, Debug)]\r\npub enum MouseEvent {\r\n  /// Mouse cursor moved.\r\n  Move {\r\n    position: Point,\r\n    pressed_buttons: PressedButtons,\r\n    /// Window under cursor.\r\n    ///\r\n    /// # Platform-specific\r\n    ///\r\n    /// - **macOS**: Sourced from the `CGEvent` field. Unreliable; often\r\n    ///   `None`, with the real window ID appearing sporadically.\r\n    /// - **Windows**: Always `None`.\r\n    window_below_cursor: Option<WindowId>,\r\n  },\r\n\r\n  /// A mouse button was pressed.\r\n  ButtonDown {\r\n    position: Point,\r\n    button: MouseButton,\r\n    pressed_buttons: PressedButtons,\r\n  },\r\n\r\n  /// A mouse button was released.\r\n  ButtonUp {\r\n    position: Point,\r\n    button: MouseButton,\r\n    pressed_buttons: PressedButtons,\r\n  },\r\n}\r\n\r\nimpl MouseEvent {\r\n  /// Returns the cursor position at the time of the event.\r\n  ///\r\n  /// `0,0` is the top-left corner of the primary monitor.\r\n  #[must_use]\r\n  pub fn position(&self) -> &Point {\r\n    match self {\r\n      Self::Move { position, .. }\r\n      | Self::ButtonDown { position, .. }\r\n      | Self::ButtonUp { position, .. } => position,\r\n    }\r\n  }\r\n\r\n  /// Returns which mouse buttons were pressed at the time of the event.\r\n  #[must_use]\r\n  pub fn pressed_buttons(&self) -> &PressedButtons {\r\n    match self {\r\n      Self::Move {\r\n        pressed_buttons, ..\r\n      }\r\n      | Self::ButtonDown {\r\n        pressed_buttons, ..\r\n      }\r\n      | Self::ButtonUp {\r\n        pressed_buttons, ..\r\n      } => pressed_buttons,\r\n    }\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-platform/src/platform_impl/macos/application.rs",
    "content": "use std::sync::Arc;\r\n\r\nuse objc2::rc::Retained;\r\nuse objc2_app_kit::{\r\n  NSApplicationActivationPolicy, NSRunningApplication, NSWorkspace,\r\n};\r\nuse objc2_application_services::AXUIElement;\r\nuse objc2_core_foundation::{CFArray, CFRetained};\r\nuse objc2_foundation::NSString;\r\n\r\nuse crate::{\r\n  platform_impl::{ffi, AXUIElementExt, NativeWindow},\r\n  Dispatcher, ThreadBound, WindowId,\r\n};\r\n\r\npub type ProcessId = i32;\r\n\r\n/// Represents a running macOS application.\r\n#[derive(Clone, Debug)]\r\npub struct Application {\r\n  pub(crate) pid: ProcessId,\r\n  pub(crate) dispatcher: Dispatcher,\r\n  pub(crate) ns_app: Retained<NSRunningApplication>,\r\n  pub(crate) ax_element: Arc<ThreadBound<CFRetained<AXUIElement>>>,\r\n}\r\n\r\nimpl Application {\r\n  /// Creates an instance of `Application`.\r\n  pub(crate) fn new(\r\n    ns_app: Retained<NSRunningApplication>,\r\n    dispatcher: Dispatcher,\r\n  ) -> Self {\r\n    let pid = ns_app.processIdentifier();\r\n    let ax_element = Arc::new(ThreadBound::new(\r\n      // Creation of `AXUIElement` for an application does not fail even\r\n      // if the PID is invalid. Instead, subsequent operations on\r\n      // the returned `AXUIElement` will error.\r\n      unsafe { AXUIElement::new_application(pid) },\r\n      dispatcher.clone(),\r\n    ));\r\n\r\n    Self {\r\n      pid,\r\n      dispatcher,\r\n      ns_app,\r\n      ax_element,\r\n    }\r\n  }\r\n\r\n  pub fn focused_window(\r\n    &self,\r\n  ) -> crate::Result<Option<crate::NativeWindow>> {\r\n    self.ax_element.with(|el| {\r\n      let focused_window =\r\n        el.get_attribute::<AXUIElement>(\"AXFocusedWindow\");\r\n\r\n      focused_window.map(|window_el| {\r\n        let window_id = WindowId::from_window_element(&window_el);\r\n        let window_el =\r\n          ThreadBound::new(window_el, self.dispatcher.clone());\r\n        Some(NativeWindow::new(window_id, window_el, self.clone()).into())\r\n      })\r\n    })?\r\n  }\r\n\r\n  pub fn windows(&self) -> crate::Result<Vec<crate::NativeWindow>> {\r\n    self.ax_element.with(|el| {\r\n      let windows = el.get_attribute::<CFArray<AXUIElement>>(\"AXWindows\");\r\n\r\n      windows.map(|windows| {\r\n        windows\r\n          .iter()\r\n          .map(|window_el| {\r\n            let window_id = WindowId::from_window_element(&window_el);\r\n            let window_el =\r\n              ThreadBound::new(window_el, self.dispatcher.clone());\r\n            NativeWindow::new(window_id, window_el, self.clone()).into()\r\n          })\r\n          .collect()\r\n      })\r\n    })?\r\n  }\r\n\r\n  pub fn psn(&self) -> crate::Result<ffi::ProcessSerialNumber> {\r\n    let mut psn = ffi::ProcessSerialNumber::default();\r\n\r\n    if unsafe { ffi::GetProcessForPID(self.pid, &raw mut psn) } != 0 {\r\n      return Err(crate::Error::Platform(\r\n        \"Failed to get process serial number.\".to_string(),\r\n      ));\r\n    }\r\n\r\n    Ok(psn)\r\n  }\r\n\r\n  pub fn bundle_id(&self) -> Option<String> {\r\n    self\r\n      .ns_app\r\n      .bundleIdentifier()\r\n      .map(|ns_string| ns_string.to_string())\r\n  }\r\n\r\n  pub fn process_name(&self) -> Option<String> {\r\n    self\r\n      .ns_app\r\n      .localizedName()\r\n      .map(|ns_string| ns_string.to_string())\r\n  }\r\n\r\n  /// Whether the application is an XPC service.\r\n  ///\r\n  /// Windows from XPC services are non-standard and cannot be managed.\r\n  /// Though XPC services are not intended to accept UI interaction, some\r\n  /// of Apple's own services have windows (e.g. `QuickLookUIService`,\r\n  /// used for Finder previews).\r\n  pub fn is_xpc(&self) -> crate::Result<bool> {\r\n    let psn = self.psn()?;\r\n\r\n    #[allow(clippy::cast_possible_truncation)]\r\n    let mut process_info = {\r\n      let mut info = ffi::ProcessInfo::default();\r\n      info.info_length = std::mem::size_of::<ffi::ProcessInfo>() as u32;\r\n      info\r\n    };\r\n\r\n    if unsafe {\r\n      ffi::GetProcessInformation(&raw const psn, &raw mut process_info)\r\n    } != 0\r\n    {\r\n      return Err(crate::Error::Platform(\r\n        \"Failed to get process information.\".to_string(),\r\n      ));\r\n    }\r\n\r\n    Ok(process_info.r#type.to_be_bytes() == *b\"XPC!\")\r\n  }\r\n\r\n  pub fn activation_policy(&self) -> NSApplicationActivationPolicy {\r\n    self.ns_app.activationPolicy()\r\n  }\r\n\r\n  /// Whether the application should be observed.\r\n  pub(crate) fn should_observe(&self) -> bool {\r\n    if self.activation_policy()\r\n      == NSApplicationActivationPolicy::Prohibited\r\n    {\r\n      return false;\r\n    }\r\n\r\n    !self.is_xpc().unwrap_or(false)\r\n  }\r\n\r\n  pub(crate) fn is_hidden(&self) -> bool {\r\n    self.ns_app.isHidden()\r\n  }\r\n}\r\n\r\npub(crate) fn all_applications(\r\n  dispatcher: &Dispatcher,\r\n) -> crate::Result<Vec<Application>> {\r\n  dispatcher.dispatch_sync(|| {\r\n    let running_apps =\r\n      NSWorkspace::sharedWorkspace().runningApplications();\r\n\r\n    running_apps\r\n      .iter()\r\n      .map(|app| Application::new(app, dispatcher.clone()))\r\n      .collect()\r\n  })\r\n}\r\n\r\npub(crate) fn application_for_bundle_id(\r\n  bundle_id: &str,\r\n  dispatcher: &Dispatcher,\r\n) -> crate::Result<Option<Application>> {\r\n  let bundle_id = bundle_id.to_owned();\r\n  dispatcher.dispatch_sync(|| {\r\n    let apps =\r\n      NSRunningApplication::runningApplicationsWithBundleIdentifier(\r\n        &NSString::from_str(&bundle_id),\r\n      );\r\n\r\n    apps\r\n      .into_iter()\r\n      .next()\r\n      .map(|app| Application::new(app, dispatcher.clone()))\r\n  })\r\n}\r\n"
  },
  {
    "path": "packages/wm-platform/src/platform_impl/macos/application_observer.rs",
    "content": "use std::{\r\n  ptr::NonNull,\r\n  sync::{Arc, Mutex},\r\n};\r\n\r\nuse objc2_application_services::{AXError, AXObserver, AXUIElement};\r\nuse objc2_core_foundation::{\r\n  kCFRunLoopDefaultMode, CFRetained, CFRunLoop, CFRunLoopSource, CFString,\r\n};\r\nuse tokio::sync::mpsc;\r\n\r\nuse crate::{\r\n  platform_impl::{\r\n    Application, NativeWindow, ProcessId, WindowEventNotificationInner,\r\n  },\r\n  NativeWindowExtMacOs, ThreadBound, WindowEvent, WindowId,\r\n};\r\n\r\n/// Notifications to register for the `AXUIElement` of an application.\r\nconst AX_APP_NOTIFICATIONS: &[&str] =\r\n  &[\"AXFocusedWindowChanged\", \"AXWindowCreated\"];\r\n\r\n/// Notifications to register for the `AXUIElement` of a window.\r\nconst AX_WINDOW_NOTIFICATIONS: &[&str] = &[\r\n  \"AXTitleChanged\",\r\n  \"AXUIElementDestroyed\",\r\n  \"AXWindowMoved\",\r\n  \"AXWindowResized\",\r\n  \"AXWindowDeminiaturized\",\r\n  \"AXWindowMiniaturized\",\r\n];\r\n\r\n/// Context passed to the application event callback.\r\n#[derive(Debug)]\r\nstruct ApplicationEventContext {\r\n  application: Application,\r\n  events_tx: mpsc::UnboundedSender<WindowEvent>,\r\n  app_windows: Arc<Mutex<Vec<crate::NativeWindow>>>,\r\n  observer: CFRetained<AXObserver>,\r\n}\r\n\r\n/// Represents an accessibility observer for a specific application.\r\n#[derive(Debug)]\r\npub(crate) struct ApplicationObserver {\r\n  pub(crate) pid: ProcessId,\r\n  app_windows: Arc<Mutex<Vec<crate::NativeWindow>>>,\r\n  events_tx: mpsc::UnboundedSender<WindowEvent>,\r\n  _observer: CFRetained<AXObserver>,\r\n  observer_source: CFRetained<CFRunLoopSource>,\r\n}\r\n\r\n// TODO: Remove this.\r\nunsafe impl Send for ApplicationObserver {}\r\n\r\nimpl ApplicationObserver {\r\n  /// Creates a new `ApplicationObserver` for the given application.\r\n  ///\r\n  /// If `is_startup` is `true`, the observer will not emit\r\n  /// `WindowEvent::Shown` for windows already running on startup.\r\n  pub fn new(\r\n    app: &Application,\r\n    events_tx: mpsc::UnboundedSender<WindowEvent>,\r\n    is_startup: bool,\r\n  ) -> crate::Result<Self> {\r\n    let observer = unsafe {\r\n      let mut observer = std::ptr::null_mut();\r\n\r\n      let result = AXObserver::create(\r\n        app.pid,\r\n        Some(Self::window_event_callback),\r\n        // SAFETY: Stack address of `observer` is guaranteed to be\r\n        // non-null.\r\n        NonNull::new(&raw mut observer).unwrap(),\r\n      );\r\n\r\n      if result != AXError::Success {\r\n        return Err(crate::Error::Accessibility(\r\n          \"AXObserverCreate\".to_string(),\r\n          result.0,\r\n        ));\r\n      }\r\n\r\n      CFRetained::retain(NonNull::new(observer).ok_or_else(|| {\r\n        crate::Error::InvalidPointer(\"AXObserver is null.\".to_string())\r\n      })?)\r\n    };\r\n\r\n    let app_windows = Arc::new(Mutex::new(app.windows()?));\r\n    let context = Box::into_raw(Box::new(ApplicationEventContext {\r\n      application: app.clone(),\r\n      events_tx: events_tx.clone(),\r\n      app_windows: app_windows.clone(),\r\n      observer: observer.clone(),\r\n    }));\r\n\r\n    let runloop =\r\n      CFRunLoop::current().ok_or(crate::Error::EventLoopStopped)?;\r\n\r\n    let observer_source = unsafe { observer.run_loop_source() };\r\n    runloop.add_source(Some(&observer_source), unsafe {\r\n      kCFRunLoopDefaultMode\r\n    });\r\n\r\n    // Register for all window notifications.\r\n    // TODO: Remove from runloop if registration fails.\r\n    Self::register_app_notifications(app, &observer, context)?;\r\n\r\n    // Emit `WindowEvent::Shown` for all existing windows.\r\n    for window in app_windows.lock().unwrap().iter() {\r\n      if let Err(err) =\r\n        Self::register_window_notifications(window, &observer, context)\r\n      {\r\n        tracing::warn!(\r\n          \"Failed to register window notifications for PID {}: {}\",\r\n          app.pid,\r\n          err\r\n        );\r\n      }\r\n\r\n      // Don't emit `WindowEvent::Shown` for windows that are already\r\n      // running on startup.\r\n      if !is_startup {\r\n        if let Err(err) = events_tx.send(WindowEvent::Shown {\r\n          window: window.clone(),\r\n          notification: crate::WindowEventNotification(None),\r\n        }) {\r\n          tracing::warn!(\r\n            \"Failed to send window event for PID {}: {}\",\r\n            app.pid,\r\n            err\r\n          );\r\n        }\r\n      }\r\n    }\r\n\r\n    Ok(Self {\r\n      pid: app.pid,\r\n      app_windows,\r\n      events_tx,\r\n      _observer: observer,\r\n      observer_source,\r\n    })\r\n  }\r\n\r\n  fn register_app_notifications(\r\n    app: &Application,\r\n    observer: &CFRetained<AXObserver>,\r\n    context: *mut ApplicationEventContext,\r\n  ) -> crate::Result<()> {\r\n    for notification in AX_APP_NOTIFICATIONS {\r\n      unsafe {\r\n        let notification_cfstr = CFString::from_static_str(notification);\r\n        let result = observer.add_notification(\r\n          app.ax_element.get_ref()?,\r\n          &notification_cfstr,\r\n          context.cast::<std::ffi::c_void>(),\r\n        );\r\n\r\n        if result != AXError::Success {\r\n          return Err(crate::Error::Platform(format!(\r\n            \"Failed to add notification {} for PID {}: {:?}\",\r\n            notification, app.pid, result\r\n          )));\r\n        }\r\n      }\r\n    }\r\n\r\n    Ok(())\r\n  }\r\n\r\n  fn register_window_notifications(\r\n    window: &crate::NativeWindow,\r\n    observer: &CFRetained<AXObserver>,\r\n    context: *mut ApplicationEventContext,\r\n  ) -> crate::Result<()> {\r\n    for notification in AX_WINDOW_NOTIFICATIONS {\r\n      unsafe {\r\n        let notification_cfstr = CFString::from_static_str(notification);\r\n        let result = observer.add_notification(\r\n          window.ax_ui_element().get_ref()?,\r\n          &notification_cfstr,\r\n          context.cast::<std::ffi::c_void>(),\r\n        );\r\n\r\n        if result != AXError::Success {\r\n          return Err(crate::Error::Platform(format!(\r\n            \"Failed to add notification {} for window {}: {:?}\",\r\n            notification,\r\n            window.id().0,\r\n            result\r\n          )));\r\n        }\r\n      }\r\n    }\r\n\r\n    Ok(())\r\n  }\r\n\r\n  pub(crate) fn emit_all_windows_destroyed(&self) {\r\n    for window in self.app_windows.lock().unwrap().iter() {\r\n      if let Err(err) = self.events_tx.send(WindowEvent::Destroyed {\r\n        window_id: window.id(),\r\n        notification: crate::WindowEventNotification(None),\r\n      }) {\r\n        tracing::warn!(\r\n          \"Failed to send window event for PID {}: {}\",\r\n          self.pid,\r\n          err\r\n        );\r\n      }\r\n    }\r\n  }\r\n\r\n  pub(crate) fn emit_all_windows_hidden(&self) {\r\n    for window in self.app_windows.lock().unwrap().iter() {\r\n      if let Err(err) = self.events_tx.send(WindowEvent::Hidden {\r\n        window: window.clone(),\r\n        notification: crate::WindowEventNotification(None),\r\n      }) {\r\n        tracing::warn!(\r\n          \"Failed to send window event for PID {}: {}\",\r\n          self.pid,\r\n          err\r\n        );\r\n      }\r\n    }\r\n  }\r\n\r\n  pub(crate) fn emit_all_windows_shown(&self) {\r\n    for window in self.app_windows.lock().unwrap().iter() {\r\n      if let Err(err) = self.events_tx.send(WindowEvent::Shown {\r\n        window: window.clone(),\r\n        notification: crate::WindowEventNotification(None),\r\n      }) {\r\n        tracing::warn!(\r\n          \"Failed to send window event for PID {}: {}\",\r\n          self.pid,\r\n          err\r\n        );\r\n      }\r\n    }\r\n  }\r\n\r\n  /// Callback function for accessibility window events.\r\n  #[allow(clippy::too_many_lines)]\r\n  unsafe extern \"C-unwind\" fn window_event_callback(\r\n    _observer: NonNull<AXObserver>,\r\n    element: NonNull<AXUIElement>,\r\n    notification_name: NonNull<CFString>,\r\n    context: *mut std::ffi::c_void,\r\n  ) {\r\n    if context.is_null() {\r\n      tracing::error!(\"Window event callback received null context.\");\r\n      return;\r\n    }\r\n\r\n    let context = &mut *context.cast::<ApplicationEventContext>();\r\n    let ax_element = unsafe { CFRetained::retain(element) };\r\n    let notification = WindowEventNotificationInner {\r\n      name: notification_name.as_ref().to_string(),\r\n      ax_element_ptr: element.as_ptr().cast::<std::ffi::c_void>(),\r\n    };\r\n\r\n    tracing::debug!(\r\n      \"Received window event: {} for PID: {}\",\r\n      notification.name,\r\n      context.application.pid\r\n    );\r\n\r\n    let found_window = {\r\n      let app_windows = context.app_windows.lock().unwrap();\r\n\r\n      app_windows\r\n        .iter()\r\n        .find(|window| {\r\n          window.ax_ui_element().get_ref().ok() == Some(&ax_element)\r\n        })\r\n        .cloned()\r\n    };\r\n\r\n    if notification.name.as_str() == \"AXUIElementDestroyed\" {\r\n      if let Some(window) = &found_window {\r\n        context\r\n          .app_windows\r\n          .lock()\r\n          .unwrap()\r\n          .retain(|w| w.id() != window.id());\r\n\r\n        if let Err(err) = context.events_tx.send(WindowEvent::Destroyed {\r\n          window_id: window.id(),\r\n          notification: crate::WindowEventNotification(Some(notification)),\r\n        }) {\r\n          tracing::warn!(\r\n            \"Failed to send window event for PID {}: {}\",\r\n            context.application.pid,\r\n            err\r\n          );\r\n        }\r\n      }\r\n\r\n      return;\r\n    }\r\n\r\n    let is_new_window = found_window.is_none();\r\n    let window = found_window.unwrap_or_else(|| {\r\n      let window_id = WindowId::from_window_element(&ax_element);\r\n      let ax_element = ThreadBound::new(\r\n        ax_element,\r\n        context.application.dispatcher.clone(),\r\n      );\r\n      NativeWindow::new(window_id, ax_element, context.application.clone())\r\n        .into()\r\n    });\r\n\r\n    if is_new_window {\r\n      context.app_windows.lock().unwrap().push(window.clone());\r\n      let _ = Self::register_window_notifications(\r\n        &window,\r\n        &context.observer.clone(),\r\n        context,\r\n      );\r\n\r\n      if let Err(err) = context.events_tx.send(WindowEvent::Shown {\r\n        window: window.clone(),\r\n        notification: crate::WindowEventNotification(Some(\r\n          notification.clone(),\r\n        )),\r\n      }) {\r\n        tracing::warn!(\r\n          \"Failed to send window event for PID {}: {}\",\r\n          context.application.pid,\r\n          err\r\n        );\r\n      }\r\n    }\r\n\r\n    let window_event = match notification.name.as_str() {\r\n      \"AXFocusedWindowChanged\" => WindowEvent::Focused {\r\n        window,\r\n        notification: crate::WindowEventNotification(Some(notification)),\r\n      },\r\n      \"AXWindowMoved\" | \"AXWindowResized\" => WindowEvent::MovedOrResized {\r\n        window,\r\n        is_interactive_start: false,\r\n        is_interactive_end: false,\r\n        notification: crate::WindowEventNotification(Some(notification)),\r\n      },\r\n      \"AXWindowMiniaturized\" => WindowEvent::Minimized {\r\n        window,\r\n        notification: crate::WindowEventNotification(Some(notification)),\r\n      },\r\n      \"AXWindowDeminiaturized\" => WindowEvent::MinimizeEnded {\r\n        window,\r\n        notification: crate::WindowEventNotification(Some(notification)),\r\n      },\r\n      \"AXTitleChanged\" => WindowEvent::TitleChanged {\r\n        window,\r\n        notification: crate::WindowEventNotification(Some(notification)),\r\n      },\r\n      _ => {\r\n        tracing::debug!(\r\n          \"Unhandled window notification: {} for PID: {}\",\r\n          notification.name,\r\n          context.application.pid\r\n        );\r\n        return;\r\n      }\r\n    };\r\n\r\n    if let Err(err) = context.events_tx.send(window_event) {\r\n      tracing::warn!(\r\n        \"Failed to send window event for PID {}: {}\",\r\n        context.application.pid,\r\n        err\r\n      );\r\n    }\r\n  }\r\n}\r\n\r\nimpl Drop for ApplicationObserver {\r\n  fn drop(&mut self) {\r\n    // Invalidate the runloop source. This is thread-safe and is OK to call\r\n    // after the run loop is stopped.\r\n    self.observer_source.invalidate();\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-platform/src/platform_impl/macos/ax_ui_element.rs",
    "content": "use std::ptr::{self, NonNull};\r\n\r\npub use objc2_application_services::{AXError, AXUIElement};\r\nuse objc2_core_foundation::{CFRetained, CFString, CFType};\r\n\r\nuse crate::Error;\r\n\r\n/// Extension trait for [`AXUIElement`].\r\npub trait AXUIElementExt {\r\n  /// Retrieves the value of an accessibility attribute.\r\n  ///\r\n  /// # Errors\r\n  ///\r\n  /// Returns an error if:\r\n  /// - The accessibility operation fails (e.g. invalid attribute name).\r\n  /// - The attribute value cannot be cast to the requested type.\r\n  fn get_attribute<T: objc2_core_foundation::Type>(\r\n    &self,\r\n    attribute: &str,\r\n  ) -> crate::Result<CFRetained<T>>;\r\n\r\n  /// Sets the value of an accessibility attribute.\r\n  ///\r\n  /// # Errors\r\n  ///\r\n  /// Returns an error if the accessibility operation fails.\r\n  fn set_attribute<T: objc2_core_foundation::Type + AsRef<CFType>>(\r\n    &self,\r\n    attribute: &str,\r\n    value: &CFRetained<T>,\r\n  ) -> crate::Result<()>;\r\n}\r\n\r\nimpl AXUIElementExt for AXUIElement {\r\n  fn get_attribute<T: objc2_core_foundation::Type>(\r\n    &self,\r\n    attribute: &str,\r\n  ) -> crate::Result<CFRetained<T>> {\r\n    let mut value: *const CFType = ptr::null();\r\n\r\n    let result = unsafe {\r\n      self.copy_attribute_value(\r\n        &CFString::from_str(attribute),\r\n        // SAFETY: Stack address of `value` is guaranteed to be\r\n        // non-null.\r\n        NonNull::new(&raw mut value).unwrap(),\r\n      )\r\n    };\r\n\r\n    if result != AXError::Success {\r\n      return Err(Error::Accessibility(attribute.to_string(), result.0));\r\n    }\r\n\r\n    NonNull::new(value.cast_mut())\r\n      .map(|ptr| unsafe { CFRetained::from_raw(ptr.cast()) })\r\n      .ok_or_else(|| {\r\n        Error::InvalidPointer(\r\n          \"copy_attribute_value returned success but null pointer\"\r\n            .to_string(),\r\n        )\r\n      })\r\n  }\r\n\r\n  fn set_attribute<T: objc2_core_foundation::Type + AsRef<CFType>>(\r\n    &self,\r\n    attribute: &str,\r\n    value: &CFRetained<T>,\r\n  ) -> crate::Result<()> {\r\n    let cf_attribute = CFString::from_str(attribute);\r\n    let result =\r\n      unsafe { self.set_attribute_value(&cf_attribute, value.as_ref()) };\r\n\r\n    if result != AXError::Success {\r\n      return Err(Error::Accessibility(attribute.to_string(), result.0));\r\n    }\r\n\r\n    Ok(())\r\n  }\r\n}\r\n\r\n#[cfg(test)]\r\nmod tests {\r\n  use objc2_core_foundation::CFString;\r\n\r\n  use super::*;\r\n\r\n  #[test]\r\n  fn get_attribute_invalid_attribute_is_err() {\r\n    let pid = i32::try_from(std::process::id()).expect(\"pid overflow\");\r\n\r\n    let el = unsafe { AXUIElement::new_application(pid) };\r\n    let result =\r\n      el.get_attribute::<CFString>(\"AXDefinitelyNotARealAttribute\");\r\n\r\n    assert!(result.is_err());\r\n  }\r\n\r\n  #[test]\r\n  fn set_attribute_invalid_attribute_is_err() {\r\n    let pid = i32::try_from(std::process::id()).expect(\"pid overflow\");\r\n\r\n    let el = unsafe { AXUIElement::new_application(pid) };\r\n    let value = CFString::from_str(\"dummy\");\r\n    let result = el.set_attribute(\"AXDefinitelyNotARealAttribute\", &value);\r\n\r\n    assert!(result.is_err());\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-platform/src/platform_impl/macos/ax_value.rs",
    "content": "use std::{ffi::c_void, mem::MaybeUninit, ptr::NonNull};\r\n\r\nuse objc2_application_services::{AXValue, AXValueType};\r\nuse objc2_core_foundation::{\r\n  CFRange, CFRetained, CGPoint, CGRect, CGSize,\r\n};\r\n\r\n/// Trait for types that can be converted to and from `AXValue`.\r\npub trait AXValueTypeMarker: Sized + Copy {\r\n  /// The `AXValueType` constant for this type.\r\n  const AX_TYPE: AXValueType;\r\n}\r\n\r\nimpl AXValueTypeMarker for CGPoint {\r\n  const AX_TYPE: AXValueType = AXValueType::CGPoint;\r\n}\r\n\r\nimpl AXValueTypeMarker for CGSize {\r\n  const AX_TYPE: AXValueType = AXValueType::CGSize;\r\n}\r\n\r\nimpl AXValueTypeMarker for CGRect {\r\n  const AX_TYPE: AXValueType = AXValueType::CGRect;\r\n}\r\n\r\nimpl AXValueTypeMarker for CFRange {\r\n  const AX_TYPE: AXValueType = AXValueType::CFRange;\r\n}\r\n\r\n/// Extension trait for [`AXValue`].\r\npub trait AXValueExt {\r\n  /// Creates a new `AXValue` from the given value.\r\n  ///\r\n  /// This is a wrapper over `AXValue::new` from `objc2`.\r\n  ///\r\n  /// # Errors\r\n  ///\r\n  /// Returns an error if the `AXValue` creation fails.\r\n  fn new_strict<T: AXValueTypeMarker>(\r\n    val: &T,\r\n  ) -> crate::Result<CFRetained<AXValue>>;\r\n\r\n  /// Extracts the value from this `AXValue`.\r\n  ///\r\n  /// This is a wrapper over `AXValue::value` from `objc2`.\r\n  ///\r\n  /// # Errors\r\n  ///\r\n  /// Returns an error if:\r\n  /// - The `AXValue` type doesn't match the requested type `T`.\r\n  /// - The accessibility framework fails to extract the value.\r\n  fn value_strict<T: AXValueTypeMarker>(&self) -> crate::Result<T>;\r\n}\r\n\r\nimpl AXValueExt for AXValue {\r\n  fn new_strict<T: AXValueTypeMarker>(\r\n    val: &T,\r\n  ) -> crate::Result<CFRetained<AXValue>> {\r\n    let ptr = NonNull::new(std::ptr::from_ref::<T>(val) as *mut c_void)\r\n      .ok_or_else(|| {\r\n        crate::Error::InvalidPointer(\"Value pointer is null\".to_string())\r\n      })?;\r\n\r\n    unsafe { AXValue::new(T::AX_TYPE, ptr) }.ok_or_else(|| {\r\n      crate::Error::AXValueCreation(format!(\r\n        \"Failed to create AXValue for type with AX_TYPE {:?}\",\r\n        T::AX_TYPE\r\n      ))\r\n    })\r\n  }\r\n\r\n  fn value_strict<T: AXValueTypeMarker>(&self) -> crate::Result<T> {\r\n    let mut value = MaybeUninit::<T>::uninit();\r\n    let ptr = NonNull::new(value.as_mut_ptr().cast::<c_void>())\r\n      .ok_or_else(|| {\r\n        crate::Error::InvalidPointer(\r\n          \"Value buffer pointer is null\".to_string(),\r\n        )\r\n      })?;\r\n\r\n    let success = unsafe { self.value(T::AX_TYPE, ptr) };\r\n\r\n    if success {\r\n      Ok(unsafe { value.assume_init() })\r\n    } else {\r\n      Err(crate::Error::AXValueCreation(format!(\r\n        \"Failed to extract value from AXValue for type with AX_TYPE {:?}\",\r\n        T::AX_TYPE\r\n      )))\r\n    }\r\n  }\r\n}\r\n\r\n#[cfg(test)]\r\nmod tests {\r\n  use super::*;\r\n\r\n  #[test]\r\n  fn test_ax_value_creation_and_extraction() {\r\n    let point = CGPoint { x: 10.0, y: 20.0 };\r\n    let ax_value =\r\n      AXValue::new_strict(&point).expect(\"Failed to create AXValue.\");\r\n\r\n    let extracted_point: CGPoint = ax_value\r\n      .value_strict()\r\n      .expect(\"Failed to extract value from AXValue.\");\r\n\r\n    assert!((point.x - extracted_point.x).abs() < f64::EPSILON);\r\n    assert!((point.y - extracted_point.y).abs() < f64::EPSILON);\r\n  }\r\n\r\n  #[test]\r\n  fn test_ax_value_wrong_type_extraction() {\r\n    let point = CGPoint { x: 10.0, y: 20.0 };\r\n    let ax_value =\r\n      AXValue::new_strict(&point).expect(\"Failed to create AXValue.\");\r\n\r\n    // Try to extract as `CGSize` instead of `CGPoint`.\r\n    let result = ax_value.value_strict::<CGSize>();\r\n    assert!(result.is_err());\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-platform/src/platform_impl/macos/display.rs",
    "content": "use std::sync::Arc;\r\n\r\nuse objc2::{rc::Retained, MainThreadMarker};\r\nuse objc2_app_kit::NSScreen;\r\nuse objc2_core_foundation::{CFRetained, CFUUID};\r\nuse objc2_core_graphics::{\r\n  CGDirectDisplayID, CGDisplayBounds, CGDisplayCopyDisplayMode,\r\n  CGDisplayMirrorsDisplay, CGDisplayMode, CGDisplayRotation, CGError,\r\n  CGGetActiveDisplayList, CGGetOnlineDisplayList, CGMainDisplayID,\r\n};\r\nuse objc2_foundation::{ns_string, NSNumber, NSRect};\r\n\r\nuse crate::{\r\n  platform_impl::ffi, ConnectionState, Dispatcher, DisplayDeviceId,\r\n  DisplayId, MirroringState, Point, Rect, ThreadBound,\r\n};\r\n\r\n/// Platform-specific implementation of [`Display`].\r\n#[derive(Clone, Debug)]\r\npub(crate) struct Display {\r\n  cg_display_id: CGDirectDisplayID,\r\n  ns_screen: Arc<ThreadBound<Retained<NSScreen>>>,\r\n}\r\n\r\nimpl Display {\r\n  /// Creates an instance of `Display`.\r\n  pub(crate) fn new(\r\n    ns_screen: ThreadBound<Retained<NSScreen>>,\r\n  ) -> crate::Result<Self> {\r\n    let cg_display_id = ns_screen\r\n      .with(|screen| {\r\n        let device_description = screen.deviceDescription();\r\n\r\n        device_description\r\n          .objectForKey(ns_string!(\"NSScreenNumber\"))\r\n          .and_then(|val| {\r\n            val.downcast_ref::<NSNumber>().map(NSNumber::as_u32)\r\n          })\r\n      })?\r\n      .ok_or(crate::Error::DisplayNotFound)?;\r\n\r\n    Ok(Self {\r\n      cg_display_id,\r\n      ns_screen: Arc::new(ns_screen),\r\n    })\r\n  }\r\n\r\n  /// Implements [`Display::id`].\r\n  pub(crate) fn id(&self) -> DisplayId {\r\n    DisplayId(self.cg_display_id)\r\n  }\r\n\r\n  /// Implements [`Display::name`].\r\n  pub(crate) fn name(&self) -> crate::Result<String> {\r\n    self.ns_screen.with(|screen| {\r\n      let name = screen.localizedName();\r\n      Ok(name.to_string())\r\n    })?\r\n  }\r\n\r\n  /// Implements [`Display::bounds`].\r\n  #[allow(clippy::unnecessary_wraps)]\r\n  pub(crate) fn bounds(&self) -> crate::Result<Rect> {\r\n    let cg_rect = CGDisplayBounds(self.cg_display_id);\r\n\r\n    #[allow(clippy::cast_possible_truncation)]\r\n    Ok(Rect::from_xy(\r\n      cg_rect.origin.x as i32,\r\n      cg_rect.origin.y as i32,\r\n      cg_rect.size.width as i32,\r\n      cg_rect.size.height as i32,\r\n    ))\r\n  }\r\n\r\n  /// Implements [`Display::working_area`].\r\n  pub(crate) fn working_area(&self) -> crate::Result<Rect> {\r\n    let primary_display_bounds = {\r\n      let bounds = CGDisplayBounds(CGMainDisplayID());\r\n\r\n      #[allow(clippy::cast_possible_truncation)]\r\n      Rect::from_xy(\r\n        bounds.origin.x as i32,\r\n        bounds.origin.y as i32,\r\n        bounds.size.width as i32,\r\n        bounds.size.height as i32,\r\n      )\r\n    };\r\n\r\n    self.ns_screen.with(|screen| {\r\n      // Convert `NSScreen.visibleFrame` into the same coordinate space as\r\n      // `CGDisplayBounds`.\r\n      Ok(appkit_rect_to_cg_rect(\r\n        screen.visibleFrame(),\r\n        &primary_display_bounds,\r\n      ))\r\n    })?\r\n  }\r\n\r\n  /// Implements [`Display::scale_factor`].\r\n  pub(crate) fn scale_factor(&self) -> crate::Result<f32> {\r\n    #[allow(clippy::cast_possible_truncation)]\r\n    self\r\n      .ns_screen\r\n      .with(|screen| screen.backingScaleFactor() as f32)\r\n  }\r\n\r\n  /// Implements [`Display::dpi`].\r\n  pub(crate) fn dpi(&self) -> crate::Result<u32> {\r\n    let scale_factor = self.scale_factor()?;\r\n\r\n    #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]\r\n    Ok((72.0 * scale_factor) as u32)\r\n  }\r\n\r\n  /// Implements [`Display::is_primary`].\r\n  #[allow(clippy::unnecessary_wraps)]\r\n  pub(crate) fn is_primary(&self) -> crate::Result<bool> {\r\n    let main_display_id = CGMainDisplayID();\r\n    Ok(self.cg_display_id == main_display_id)\r\n  }\r\n\r\n  /// Implements [`Display::devices`].\r\n  pub(crate) fn devices(\r\n    &self,\r\n  ) -> crate::Result<Vec<crate::DisplayDevice>> {\r\n    let main_device = DisplayDevice::new(\r\n      self.cg_display_id,\r\n      cg_display_uuid(self.cg_display_id)?,\r\n    );\r\n\r\n    // TODO: Get devices that are mirroring this display as well.\r\n    Ok(vec![main_device.into()])\r\n  }\r\n\r\n  /// Implements [`Display::main_device`].\r\n  pub(crate) fn main_device(&self) -> crate::Result<crate::DisplayDevice> {\r\n    self\r\n      .devices()?\r\n      .into_iter()\r\n      .find(|device| {\r\n        matches!(\r\n          device.mirroring_state(),\r\n          Ok(None | Some(MirroringState::Source))\r\n        )\r\n      })\r\n      .ok_or(crate::Error::DisplayNotFound)\r\n  }\r\n\r\n  /// Implements [`DisplayExtMacOs::cg_display_id`].\r\n  pub(crate) fn cg_display_id(&self) -> CGDirectDisplayID {\r\n    self.cg_display_id\r\n  }\r\n\r\n  /// Implements [`DisplayExtMacOs::ns_screen`].\r\n  pub(crate) fn ns_screen(&self) -> &ThreadBound<Retained<NSScreen>> {\r\n    &self.ns_screen\r\n  }\r\n}\r\n\r\n/// Transforms an AppKit screen rectangle (e.g. `NSScreen.visibleFrame`)\r\n/// into Core Graphics coordinate space (e.g. `CGDisplayBounds`).\r\n///\r\n/// AppKit has (0,0) at the bottom-left corner of the primary display,\r\n/// whereas Core Graphics has it at the top-left corner. So we can convert\r\n/// between the two by offsetting the Y-axis by the primary display's\r\n/// height.\r\nfn appkit_rect_to_cg_rect(\r\n  appkit_rect: NSRect,\r\n  primary_display_bounds: &Rect,\r\n) -> Rect {\r\n  let adjusted_y = f64::from(primary_display_bounds.height())\r\n    - (appkit_rect.origin.y + appkit_rect.size.height);\r\n\r\n  #[allow(clippy::cast_possible_truncation)]\r\n  Rect::from_xy(\r\n    appkit_rect.origin.x as i32,\r\n    adjusted_y as i32,\r\n    appkit_rect.size.width as i32,\r\n    appkit_rect.size.height as i32,\r\n  )\r\n}\r\n\r\nimpl From<Display> for crate::Display {\r\n  fn from(display: Display) -> Self {\r\n    crate::Display { inner: display }\r\n  }\r\n}\r\n\r\nimpl PartialEq for Display {\r\n  fn eq(&self, other: &Self) -> bool {\r\n    self.id() == other.id()\r\n  }\r\n}\r\n\r\nimpl Eq for Display {}\r\n\r\n/// Platform-specific implementation of [`DisplayDevice`].\r\n#[derive(Clone, Debug, PartialEq, Eq)]\r\npub(crate) struct DisplayDevice {\r\n  cg_display_id: CGDirectDisplayID,\r\n  uuid: CFRetained<CFUUID>,\r\n}\r\n\r\nimpl DisplayDevice {\r\n  /// Creates an instance of `DisplayDevice`.\r\n  #[must_use]\r\n  pub(crate) fn new(\r\n    cg_display_id: CGDirectDisplayID,\r\n    uuid: CFRetained<CFUUID>,\r\n  ) -> Self {\r\n    Self {\r\n      cg_display_id,\r\n      uuid,\r\n    }\r\n  }\r\n\r\n  /// Implements [`DisplayDevice::id`].\r\n  pub(crate) fn id(&self) -> DisplayDeviceId {\r\n    // SAFETY: Can assume that the `CFUUID` is valid regardless of whether\r\n    // the underlying display device is still alive.\r\n    let uuid_string = CFUUID::new_string(None, Some(&self.uuid))\r\n      .unwrap()\r\n      .to_string();\r\n\r\n    DisplayDeviceId(uuid_string)\r\n  }\r\n\r\n  /// Implements [`DisplayDevice::rotation`].\r\n  #[allow(clippy::unnecessary_wraps)]\r\n  pub(crate) fn rotation(&self) -> crate::Result<f32> {\r\n    #[allow(clippy::cast_possible_truncation)]\r\n    Ok(CGDisplayRotation(self.cg_display_id) as f32)\r\n  }\r\n\r\n  /// Implements [`DisplayDevice::refresh_rate`].\r\n  pub(crate) fn refresh_rate(&self) -> crate::Result<f32> {\r\n    // NOTE: Calling `CGDisplayModeRelease` on cleanup is not needed, since\r\n    // it's equivalent to `CFRelease` in this case. Ref: https://developer.apple.com/documentation/coregraphics/cgdisplaymoderelease\r\n    let display_mode = CGDisplayCopyDisplayMode(self.cg_display_id)\r\n      .ok_or(crate::Error::DisplayModeNotFound)?;\r\n\r\n    let refresh_rate = CGDisplayMode::refresh_rate(Some(&display_mode));\r\n\r\n    #[allow(clippy::cast_possible_truncation)]\r\n    Ok(refresh_rate as f32)\r\n  }\r\n\r\n  /// Implements [`DisplayDevice::is_builtin`].\r\n  #[allow(clippy::unnecessary_wraps)]\r\n  pub(crate) fn is_builtin(&self) -> crate::Result<bool> {\r\n    // TODO: Implement this properly.\r\n    let main_display_id = CGMainDisplayID();\r\n    Ok(self.cg_display_id == main_display_id)\r\n  }\r\n\r\n  /// Implements [`DisplayDevice::connection_state`].\r\n  #[allow(clippy::unnecessary_wraps)]\r\n  pub(crate) fn connection_state(&self) -> crate::Result<ConnectionState> {\r\n    let display_mode = CGDisplayCopyDisplayMode(self.cg_display_id);\r\n\r\n    // TODO: Implement this properly.\r\n    if display_mode.is_none() {\r\n      Ok(ConnectionState::Disconnected)\r\n    } else {\r\n      Ok(ConnectionState::Active)\r\n    }\r\n  }\r\n\r\n  /// Implements [`DisplayDevice::mirroring_state`].\r\n  #[allow(clippy::unnecessary_wraps)]\r\n  pub(crate) fn mirroring_state(\r\n    &self,\r\n  ) -> crate::Result<Option<MirroringState>> {\r\n    let mirrored_display = CGDisplayMirrorsDisplay(self.cg_display_id);\r\n\r\n    // TODO: Clean this up.\r\n    if mirrored_display == 0 {\r\n      // This display is not mirroring another display\r\n      // Check if another display is mirroring this one by querying active\r\n      // displays\r\n      let mut displays: Vec<CGDirectDisplayID> = vec![0; 32];\r\n      let mut display_count: u32 = 0;\r\n\r\n      #[allow(clippy::cast_possible_truncation)]\r\n      let result = unsafe {\r\n        CGGetActiveDisplayList(\r\n          displays.len() as u32,\r\n          displays.as_mut_ptr(),\r\n          &raw mut display_count,\r\n        )\r\n      };\r\n\r\n      if result == CGError::Success {\r\n        displays.truncate(display_count as usize);\r\n        for &display_id in &displays {\r\n          if display_id == self.cg_display_id {\r\n            continue; // Skip self\r\n          }\r\n          let other_mirrored = CGDisplayMirrorsDisplay(display_id);\r\n          if other_mirrored == self.cg_display_id {\r\n            // Another display is mirroring this one, so this is the source\r\n            return Ok(Some(MirroringState::Source));\r\n          }\r\n        }\r\n      }\r\n      Ok(None)\r\n    } else {\r\n      // This display is mirroring another display, so it's a target\r\n      Ok(Some(MirroringState::Target))\r\n    }\r\n  }\r\n\r\n  /// Implements [`DisplayDeviceExtMacOs::cg_display_id`].\r\n  pub(crate) fn cg_display_id(&self) -> CGDirectDisplayID {\r\n    self.cg_display_id\r\n  }\r\n}\r\n\r\nimpl From<DisplayDevice> for crate::DisplayDevice {\r\n  fn from(device: DisplayDevice) -> Self {\r\n    crate::DisplayDevice { inner: device }\r\n  }\r\n}\r\n\r\n/// Gets the UUID for a display device from its `CGDirectDisplayID`.\r\n///\r\n/// This UUID is stable across reboots, whereas `CGDirectDisplayID` is not.\r\nfn cg_display_uuid(\r\n  cg_display_id: CGDirectDisplayID,\r\n) -> crate::Result<CFRetained<CFUUID>> {\r\n  let ptr =\r\n    unsafe { ffi::CGDisplayCreateUUIDFromDisplayID(cg_display_id) };\r\n\r\n  ptr.map(|ptr| unsafe { CFRetained::from_raw(ptr) }).ok_or(\r\n    crate::Error::InvalidPointer(\r\n      \"Failed to create UUID for display device\".to_string(),\r\n    ),\r\n  )\r\n}\r\n\r\n/// Implements [`Dispatcher::displays`].\r\npub(crate) fn all_displays(\r\n  dispatcher: &Dispatcher,\r\n) -> crate::Result<Vec<crate::Display>> {\r\n  dispatcher.dispatch_sync(|| {\r\n    let mtm =\r\n      MainThreadMarker::new().ok_or(crate::Error::NotMainThread)?;\r\n\r\n    let mut displays = Vec::new();\r\n\r\n    for screen in NSScreen::screens(mtm) {\r\n      let ns_screen = ThreadBound::new(screen, dispatcher.clone());\r\n      displays.push(Display::new(ns_screen)?.into());\r\n    }\r\n\r\n    Ok(displays)\r\n  })?\r\n}\r\n\r\n/// Implements [`Dispatcher::display_devices`].\r\npub(crate) fn all_display_devices(\r\n  _: &Dispatcher,\r\n) -> crate::Result<Vec<crate::DisplayDevice>> {\r\n  let mut cg_display_ids: Vec<CGDirectDisplayID> = vec![0; 32]; // Max 32 displays\r\n  let mut display_count: u32 = 0;\r\n\r\n  #[allow(clippy::cast_possible_truncation)]\r\n  let result = unsafe {\r\n    CGGetOnlineDisplayList(\r\n      cg_display_ids.len() as u32,\r\n      cg_display_ids.as_mut_ptr(),\r\n      &raw mut display_count,\r\n    )\r\n  };\r\n\r\n  if result != CGError::Success {\r\n    return Err(crate::Error::DisplayEnumerationFailed);\r\n  }\r\n\r\n  cg_display_ids.truncate(display_count as usize);\r\n\r\n  cg_display_ids\r\n    .into_iter()\r\n    .map(|cg_display_id| {\r\n      Ok(\r\n        DisplayDevice::new(cg_display_id, cg_display_uuid(cg_display_id)?)\r\n          .into(),\r\n      )\r\n    })\r\n    .collect()\r\n}\r\n\r\n/// Implements [`Dispatcher::display_from_point`].\r\npub(crate) fn display_from_point(\r\n  point: &Point,\r\n  dispatcher: &Dispatcher,\r\n) -> crate::Result<crate::Display> {\r\n  let displays = all_displays(dispatcher)?;\r\n\r\n  for display in displays {\r\n    let bounds = display.bounds()?;\r\n    if bounds.contains_point(point) {\r\n      return Ok(display);\r\n    }\r\n  }\r\n\r\n  Err(crate::Error::DisplayNotFound)\r\n}\r\n\r\n/// Implements [`Dispatcher::primary_display`].\r\npub(crate) fn primary_display(\r\n  dispatcher: &Dispatcher,\r\n) -> crate::Result<crate::Display> {\r\n  dispatcher.dispatch_sync(|| {\r\n    let mtm =\r\n      MainThreadMarker::new().ok_or(crate::Error::NotMainThread)?;\r\n\r\n    let ns_screen = ThreadBound::new(\r\n      NSScreen::mainScreen(mtm).ok_or(crate::Error::DisplayNotFound)?,\r\n      dispatcher.clone(),\r\n    );\r\n\r\n    Display::new(ns_screen).map(Into::into)\r\n  })?\r\n}\r\n\r\n/// Implements [`Dispatcher::nearest_display`].\r\n///\r\n/// NOTE: This was benchmarked to be 400-600µs on initial retrieval and\r\n/// 150-300µs on subsequent retrievals. Using `CGGetDisplaysWithRect` and\r\n/// getting the corresponding `NSScreen` was found to be slightly slower\r\n/// (700-800µs and then 200-300µs on subsequent retrievals).\r\npub(crate) fn nearest_display(\r\n  native_window: &crate::NativeWindow,\r\n  dispatcher: &Dispatcher,\r\n) -> crate::Result<crate::Display> {\r\n  dispatcher.dispatch_sync(|| {\r\n    // Get the window's frame in screen coordinates.\r\n    let window_frame = native_window.frame()?;\r\n\r\n    let screens = all_displays(dispatcher)?;\r\n    let mut best_screen = None;\r\n    let mut max_intersection_area = 0;\r\n\r\n    // TODO: Clean this up.\r\n    // Iterate through all screens to find the one with the largest\r\n    // intersection with the window.\r\n    for screen in screens {\r\n      let screen_frame = screen.bounds()?;\r\n\r\n      // Calculate intersection area.\r\n      let intersection_x = i32::max(window_frame.x(), screen_frame.x());\r\n      let intersection_y = i32::max(window_frame.y(), screen_frame.y());\r\n      let intersection_width = i32::min(\r\n        window_frame.x() + window_frame.width(),\r\n        screen_frame.x() + screen_frame.width(),\r\n      ) - intersection_x;\r\n      let intersection_height = i32::min(\r\n        window_frame.y() + window_frame.height(),\r\n        screen_frame.y() + screen_frame.height(),\r\n      ) - intersection_y;\r\n\r\n      // If there's a valid intersection, calculate its area.\r\n      if intersection_width > 0 && intersection_height > 0 {\r\n        let area = intersection_width * intersection_height;\r\n        if area > max_intersection_area {\r\n          max_intersection_area = area;\r\n          best_screen = Some(screen);\r\n        }\r\n      }\r\n    }\r\n\r\n    // If we found a screen with intersection, use it. Otherwise, if the\r\n    // window is off-screen, use the main screen.\r\n    best_screen\r\n      .or_else(|| primary_display(dispatcher).ok())\r\n      .ok_or(crate::Error::DisplayNotFound)\r\n  })?\r\n}\r\n"
  },
  {
    "path": "packages/wm-platform/src/platform_impl/macos/display_listener.rs",
    "content": "use std::time::{Duration, Instant};\r\n\r\nuse objc2::rc::Retained;\r\nuse tokio::sync::mpsc;\r\n\r\nuse crate::{\r\n  platform_impl::{\r\n    NotificationCenter, NotificationEvent, NotificationName,\r\n    NotificationObserver,\r\n  },\r\n  Dispatcher, ThreadBound,\r\n};\r\n\r\n/// Platform-specific implementation of [`DisplayListener`].\r\npub(crate) struct DisplayListener {\r\n  /// Notification observer bound to the main thread.\r\n  observer: Option<ThreadBound<Retained<NotificationObserver>>>,\r\n}\r\n\r\nimpl DisplayListener {\r\n  /// Creates an instance of `DisplayListener`.\r\n  pub(crate) fn new(\r\n    event_tx: mpsc::UnboundedSender<()>,\r\n    dispatcher: &Dispatcher,\r\n  ) -> crate::Result<Self> {\r\n    let dispatcher_clone = dispatcher.clone();\r\n    let observer = dispatcher.dispatch_sync(move || {\r\n      Self::add_observers(event_tx, dispatcher_clone)\r\n    })?;\r\n\r\n    Ok(Self {\r\n      observer: Some(observer),\r\n    })\r\n  }\r\n\r\n  /// Implements [`DisplayListener::terminate`].\r\n  #[allow(clippy::unnecessary_wraps)]\r\n  pub(crate) fn terminate(&mut self) -> crate::Result<()> {\r\n    // On macOS 10.11+, observer subscriptions are cleaned up automatically\r\n    // without calling `removeObserver`.\r\n    // Ref: https://developer.apple.com/documentation/foundation/notificationcenter/removeobserver(_:name:object:)\r\n    //\r\n    // Dropping the `NotificationObserver` also drops its channel sender,\r\n    // causing the listener thread to exit.\r\n    self.observer.take();\r\n    Ok(())\r\n  }\r\n\r\n  /// Registers notification observers on the main thread.\r\n  fn add_observers(\r\n    event_tx: mpsc::UnboundedSender<()>,\r\n    dispatcher: Dispatcher,\r\n  ) -> ThreadBound<Retained<NotificationObserver>> {\r\n    let (observer, mut events_rx) = NotificationObserver::new();\r\n    let mut default_center = NotificationCenter::default_center();\r\n    let mut workspace_center = NotificationCenter::workspace_center();\r\n\r\n    // Add observer which will fire when displays are connected and\r\n    // disconnected, resolution changes, or arrangement changes.\r\n    unsafe {\r\n      default_center.add_observer(\r\n        NotificationName::ApplicationDidChangeScreenParameters,\r\n        &observer,\r\n        None,\r\n      );\r\n    }\r\n\r\n    // Add observers for system sleep and wake events.\r\n    unsafe {\r\n      workspace_center.add_observer(\r\n        NotificationName::WorkspaceWillSleep,\r\n        &observer,\r\n        None,\r\n      );\r\n      workspace_center.add_observer(\r\n        NotificationName::WorkspaceDidWake,\r\n        &observer,\r\n        None,\r\n      );\r\n    }\r\n\r\n    std::thread::spawn(move || {\r\n      // Duration to suppress display change events after wake. macOS fires\r\n      // several notifications after waking from sleep, and displays can\r\n      // take 1-2 seconds to be reported as online.\r\n      const WAKE_COALESCE_DURATION: Duration = Duration::from_secs(5);\r\n\r\n      let mut is_asleep = false;\r\n      let mut wake_time: Option<Instant> = None;\r\n\r\n      // Loop exits when the sender is dropped in `Self::terminate`.\r\n      while let Some(event) = events_rx.blocking_recv() {\r\n        match event {\r\n          NotificationEvent::WorkspaceWillSleep => {\r\n            is_asleep = true;\r\n          }\r\n          NotificationEvent::WorkspaceDidWake => {\r\n            is_asleep = false;\r\n            wake_time = Some(Instant::now());\r\n\r\n            // Send a single display change event after the coalesce\r\n            // duration to pick up any changes that occurred during wake.\r\n            let event_tx = event_tx.clone();\r\n            std::thread::spawn(move || {\r\n              std::thread::sleep(WAKE_COALESCE_DURATION);\r\n\r\n              if let Err(err) = event_tx.send(()) {\r\n                tracing::warn!(\r\n                  \"Failed to send display change event: {}\",\r\n                  err\r\n                );\r\n              }\r\n            });\r\n          }\r\n          NotificationEvent::ApplicationDidChangeScreenParameters => {\r\n            // Ignore display change events while asleep or within the\r\n            // coalesce duration after wake.\r\n            if is_asleep\r\n              || wake_time\r\n                .is_some_and(|t| t.elapsed() < WAKE_COALESCE_DURATION)\r\n            {\r\n              continue;\r\n            }\r\n\r\n            // Coalesce duration has passed; clear it.\r\n            if wake_time.is_some() {\r\n              wake_time = None;\r\n            }\r\n\r\n            if let Err(err) = event_tx.send(()) {\r\n              tracing::warn!(\r\n                \"Failed to send display change event: {}\",\r\n                err\r\n              );\r\n              break;\r\n            }\r\n          }\r\n          _ => {}\r\n        }\r\n      }\r\n\r\n      tracing::debug!(\"Display listener thread exited.\");\r\n    });\r\n\r\n    ThreadBound::new(observer, dispatcher)\r\n  }\r\n}\r\n\r\nimpl Drop for DisplayListener {\r\n  fn drop(&mut self) {\r\n    let _ = self.terminate();\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-platform/src/platform_impl/macos/event_loop.rs",
    "content": "use std::sync::{\r\n  atomic::{AtomicBool, Ordering},\r\n  mpsc, Arc,\r\n};\r\n\r\nuse objc2::MainThreadMarker;\r\nuse objc2_app_kit::{NSApplication, NSApplicationActivationPolicy};\r\nuse objc2_core_foundation::{\r\n  kCFRunLoopDefaultMode, CFRetained, CFRunLoop, CFRunLoopSource,\r\n  CFRunLoopSourceContext,\r\n};\r\n\r\nuse crate::{DispatchFn, Dispatcher};\r\n\r\n/// Source for dispatching callbacks onto the event loop thread.\r\n#[derive(Clone)]\r\npub(crate) struct EventLoopSource {\r\n  dispatch_tx: mpsc::Sender<Box<DispatchFn>>,\r\n  source: CFRetained<CFRunLoopSource>,\r\n  run_loop: CFRetained<CFRunLoop>,\r\n  pub(crate) thread_id: std::thread::ThreadId,\r\n}\r\n\r\nimpl EventLoopSource {\r\n  pub(crate) fn send_dispatch_async<F>(\r\n    &self,\r\n    dispatch_fn: F,\r\n  ) -> crate::Result<()>\r\n  where\r\n    F: FnOnce() + Send + 'static,\r\n  {\r\n    // TODO: Avoid duplicate check in `dispatch_sync`.\r\n    if std::thread::current().id() == self.thread_id {\r\n      dispatch_fn();\r\n      return Ok(());\r\n    }\r\n\r\n    self\r\n      .dispatch_tx\r\n      .send(Box::new(dispatch_fn))\r\n      .map_err(|_| crate::Error::ChannelSend)?;\r\n\r\n    // Signal the run loop source, which schedules the `perform` callback\r\n    // to be invoked. If signaled multiple times in a short period, this\r\n    // gets coalesced into a single signal.\r\n    self.source.signal();\r\n\r\n    // Wake up the run loop to process the signal.\r\n    self.run_loop.wake_up();\r\n    Ok(())\r\n  }\r\n\r\n  pub(crate) fn send_dispatch_sync<F>(\r\n    &self,\r\n    dispatch_fn: F,\r\n  ) -> crate::Result<()>\r\n  where\r\n    F: FnOnce() + Send,\r\n  {\r\n    // SAFETY: Usage of this function needs to be in a synchronous\r\n    // context where the dispatch function will be executed before the\r\n    // caller's stack frame is dropped.\r\n    let dispatch_fn_static = unsafe {\r\n      std::mem::transmute::<\r\n        Box<dyn FnOnce() + Send>,\r\n        Box<dyn FnOnce() + Send + 'static>,\r\n      >(Box::new(dispatch_fn))\r\n    };\r\n\r\n    self.send_dispatch_async(dispatch_fn_static)\r\n  }\r\n\r\n  pub(crate) fn send_stop(&self) -> crate::Result<()> {\r\n    let (result_tx, result_rx) = std::sync::mpsc::channel();\r\n\r\n    self.send_dispatch_sync(|| {\r\n      let mtm = unsafe { MainThreadMarker::new_unchecked() };\r\n\r\n      // Call `stop()` to mark the run loop for termination.\r\n      let ns_app = NSApplication::sharedApplication(mtm);\r\n      ns_app.stop(None);\r\n\r\n      // `stop()` only takes effect after processing a subsequent UI event.\r\n      // Post a dummy event so the application actually exits.\r\n      ns_app.abortModal();\r\n\r\n      let _ = result_tx.send(());\r\n    })?;\r\n\r\n    result_rx\r\n      .recv_timeout(std::time::Duration::from_secs(3))\r\n      .map_err(crate::Error::ChannelRecv)\r\n  }\r\n}\r\n\r\n// SAFETY: `CFRunLoop` and `CFRunLoopSource` are thread-safe types. The\r\n// `objc2` bindings don't implement `Send + Sync`.\r\nunsafe impl Send for EventLoopSource {}\r\nunsafe impl Sync for EventLoopSource {}\r\n\r\n/// Platform-specific implementation of [`EventLoop`].\r\npub(crate) struct EventLoop {\r\n  source: EventLoopSource,\r\n  stopped: Arc<AtomicBool>,\r\n}\r\n\r\nimpl EventLoop {\r\n  /// Implements [`EventLoop::new`].\r\n  pub fn new() -> crate::Result<(Self, Dispatcher)> {\r\n    // Add a new run loop source that allows dispatching from any thread.\r\n    let source = Self::add_dispatch_source()?;\r\n\r\n    let stopped = Arc::new(AtomicBool::new(false));\r\n    let dispatcher =\r\n      Dispatcher::new(Some(source.clone()), stopped.clone());\r\n\r\n    Ok((\r\n      Self {\r\n        source: source.clone(),\r\n        stopped,\r\n      },\r\n      dispatcher,\r\n    ))\r\n  }\r\n\r\n  /// Implements [`EventLoop::run`].\r\n  #[allow(clippy::unused_self)]\r\n  pub fn run(self) -> crate::Result<()> {\r\n    let mtm =\r\n      MainThreadMarker::new().ok_or(crate::Error::NotMainThread)?;\r\n\r\n    tracing::info!(\"Starting macOS event loop.\");\r\n    NSApplication::sharedApplication(mtm).run();\r\n\r\n    tracing::info!(\"macOS event loop exiting.\");\r\n    Ok(())\r\n  }\r\n\r\n  /// Adds a source (`CFRunLoopSource`) for allowing dispatches to\r\n  /// the current run loop.\r\n  ///\r\n  /// Can only be called on the main thread.\r\n  pub(crate) fn add_dispatch_source() -> crate::Result<EventLoopSource> {\r\n    let mtm =\r\n      MainThreadMarker::new().ok_or(crate::Error::NotMainThread)?;\r\n\r\n    // Initialize `NSApplication` on the main thread. This is necessary for\r\n    // some AppKit components (e.g. system tray) to be functional.\r\n    // TODO: Skip this if not on the main thread, and instead run a normal\r\n    // run loop.\r\n    let ns_app = NSApplication::sharedApplication(mtm);\r\n    ns_app.setActivationPolicy(NSApplicationActivationPolicy::Accessory);\r\n\r\n    let (dispatch_tx, dispatch_rx) = mpsc::channel();\r\n    let dispatch_rx_ptr =\r\n      Box::into_raw(Box::new(dispatch_rx)).cast::<std::ffi::c_void>();\r\n\r\n    // Create `CFRunLoopSource` context.\r\n    let mut context = CFRunLoopSourceContext {\r\n      version: 0,\r\n      info: dispatch_rx_ptr,\r\n      retain: None,\r\n      release: Some(Self::runloop_source_released_callback),\r\n      copyDescription: None,\r\n      equal: None,\r\n      hash: None,\r\n      schedule: None,\r\n      cancel: None,\r\n      perform: Some(Self::runloop_signaled_callback),\r\n    };\r\n\r\n    // Create the run loop source.\r\n    let source =\r\n      unsafe { CFRunLoopSource::new(None, 0, &raw mut context) }.ok_or(\r\n        crate::Error::Platform(\r\n          \"Failed to create run loop source.\".to_string(),\r\n        ),\r\n      )?;\r\n\r\n    let run_loop =\r\n      CFRunLoop::current().ok_or(crate::Error::EventLoopStopped)?;\r\n\r\n    run_loop.add_source(Some(&source), unsafe { kCFRunLoopDefaultMode });\r\n\r\n    Ok(EventLoopSource {\r\n      dispatch_tx,\r\n      source,\r\n      run_loop,\r\n      thread_id: std::thread::current().id(),\r\n    })\r\n  }\r\n\r\n  // This function is called by the `CFRunLoopSource` when signaled.\r\n  extern \"C-unwind\" fn runloop_signaled_callback(\r\n    info: *mut std::ffi::c_void,\r\n  ) {\r\n    let callbacks =\r\n      unsafe { &*(info as *const mpsc::Receiver<Box<DispatchFn>>) };\r\n\r\n    // Process any pending dispatched callbacks. Multiple run loop signals\r\n    // may be coalesced together (calling `perform` only once), so it's\r\n    // important to drain all pending callbacks.\r\n    for callback in callbacks.try_iter() {\r\n      callback();\r\n    }\r\n  }\r\n\r\n  // This function is called when the `CFRunLoopSource` is released.\r\n  extern \"C-unwind\" fn runloop_source_released_callback(\r\n    info: *const std::ffi::c_void,\r\n  ) {\r\n    // SAFETY: This pointer was created with `Box::into_raw` in\r\n    // `add_dispatch_source`, so it can safely be converted back to a `Box`\r\n    // and dropped.\r\n    let _ = unsafe {\r\n      Box::from_raw(info as *mut mpsc::Receiver<Box<DispatchFn>>)\r\n    };\r\n  }\r\n}\r\n\r\nimpl Drop for EventLoop {\r\n  fn drop(&mut self) {\r\n    tracing::info!(\"Shutting down event loop.\");\r\n\r\n    // Stop the run loop if not already stopped.\r\n    if !self.stopped.load(Ordering::SeqCst) {\r\n      let _ = self.source.send_stop();\r\n    }\r\n\r\n    // Invalidate the runloop source to trigger its release callback. This\r\n    // is thread-safe and is OK to call after the run loop is stopped.\r\n    self.source.source.invalidate();\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-platform/src/platform_impl/macos/ffi.rs",
    "content": "use std::{ffi::c_void, ptr::NonNull};\r\n\r\nuse objc2_application_services::{AXError, AXUIElement};\r\nuse objc2_core_foundation::CFUUID;\r\nuse objc2_core_graphics::{CGDirectDisplayID, CGError, CGWindowID};\r\n\r\nuse crate::platform_impl::ProcessId;\r\n\r\n/// Carbon process serial number (PSN), used to uniquely identify a\r\n/// process.\r\n#[derive(Clone, Debug, Default)]\r\n#[repr(C)]\r\npub struct ProcessSerialNumber {\r\n  high: u32,\r\n  low: u32,\r\n}\r\n\r\n/// Carbon process information, populated by `GetProcessInformation`.\r\n#[derive(Default)]\r\n#[repr(C, packed(2))]\r\npub(crate) struct ProcessInfo {\r\n  pub(crate) info_length: u32,\r\n  name: *const u8,\r\n  psn: ProcessSerialNumber,\r\n  pub(crate) r#type: u32,\r\n  signature: u32,\r\n  mode: u32,\r\n  location: *const u8,\r\n  size: u32,\r\n  free_mem: u32,\r\n  launcher: ProcessSerialNumber,\r\n  launch_date: u32,\r\n  active_time: u32,\r\n  app_ref: *const u8,\r\n}\r\n\r\npub const CPS_USER_GENERATED: u32 = 0x200;\r\n\r\n#[link(name = \"ApplicationServices\", kind = \"framework\")]\r\nunsafe extern \"C\" {\r\n  // Deprecated in macOS 10.9 in late 2014, but still works fine.\r\n  pub(crate) fn GetProcessForPID(\r\n    pid: ProcessId,\r\n    psn: *mut ProcessSerialNumber,\r\n  ) -> u32;\r\n\r\n  // Deprecated in macOS 10.9 in late 2014, but still works fine.\r\n  pub(crate) fn GetProcessInformation(\r\n    psn: *const ProcessSerialNumber,\r\n    process_info: *mut ProcessInfo,\r\n  ) -> u32;\r\n\r\n  // `CGDisplayCreateUUIDFromDisplayID` comes from the `ColorSync`\r\n  // framework, which is a subframework of `ApplicationServices`.\r\n  pub(crate) fn CGDisplayCreateUUIDFromDisplayID(\r\n    display: CGDirectDisplayID,\r\n  ) -> Option<NonNull<CFUUID>>;\r\n}\r\n\r\nunsafe extern \"C\" {\r\n  pub(crate) fn _AXUIElementGetWindow(\r\n    elem: NonNull<AXUIElement>,\r\n    window_id: *mut CGWindowID,\r\n  ) -> AXError;\r\n}\r\n\r\n#[link(name = \"SkyLight\", kind = \"framework\")]\r\nunsafe extern \"C\" {\r\n  pub(crate) fn _SLPSSetFrontProcessWithOptions(\r\n    psn: &ProcessSerialNumber,\r\n    window_id: i32,\r\n    mode: u32,\r\n  ) -> CGError;\r\n\r\n  pub(crate) fn SLPSPostEventRecordTo(\r\n    psn: &ProcessSerialNumber,\r\n    event: *const c_void,\r\n  ) -> CGError;\r\n}\r\n"
  },
  {
    "path": "packages/wm-platform/src/platform_impl/macos/keyboard_hook.rs",
    "content": "use std::{os::raw::c_void, ptr::NonNull};\r\n\r\nuse objc2_core_foundation::{\r\n  kCFRunLoopCommonModes, CFMachPort, CFRetained, CFRunLoop,\r\n};\r\nuse objc2_core_graphics::{\r\n  CGEvent, CGEventField, CGEventFlags, CGEventMask, CGEventTapLocation,\r\n  CGEventTapOptions, CGEventTapPlacement, CGEventTapProxy, CGEventType,\r\n};\r\n\r\nuse crate::{Dispatcher, Error, Key, KeyCode, ThreadBound};\r\n\r\n/// A key event received from the keyboard hook.\r\n#[derive(Clone, Debug)]\r\npub struct KeyEvent {\r\n  /// The key that was pressed or released.\r\n  pub key: Key,\r\n\r\n  /// Key code that generated this event.\r\n  #[allow(dead_code)]\r\n  pub key_code: KeyCode,\r\n\r\n  /// Whether the event is for a key press or release.\r\n  pub is_keypress: bool,\r\n\r\n  /// Modifier key flags at the time of the event.\r\n  event_flags: CGEventFlags,\r\n}\r\n\r\nimpl KeyEvent {\r\n  /// Gets whether the specified key is currently pressed.\r\n  pub fn is_key_down(&self, key: Key) -> bool {\r\n    match key {\r\n      Key::Cmd\r\n      | Key::LCmd\r\n      | Key::RCmd\r\n      | Key::Win\r\n      | Key::LWin\r\n      | Key::RWin => {\r\n        self.event_flags & CGEventFlags::MaskCommand\r\n          != CGEventFlags::empty()\r\n      }\r\n      Key::Alt | Key::LAlt | Key::RAlt => {\r\n        self.event_flags & CGEventFlags::MaskAlternate\r\n          != CGEventFlags::empty()\r\n      }\r\n      Key::Ctrl | Key::LCtrl | Key::RCtrl => {\r\n        self.event_flags & CGEventFlags::MaskControl\r\n          != CGEventFlags::empty()\r\n      }\r\n      Key::Shift | Key::LShift | Key::RShift => {\r\n        self.event_flags & CGEventFlags::MaskShift != CGEventFlags::empty()\r\n      }\r\n      _ => {\r\n        // TODO: For non-modifier keys, check using CGEventSourceStateID.\r\n        false\r\n      }\r\n    }\r\n  }\r\n}\r\n\r\n/// Data shared with the `CGEventTap` callback.\r\nstruct CallbackData {\r\n  callback: Box<dyn Fn(KeyEvent) -> bool + Send + Sync + 'static>,\r\n}\r\n\r\n/// A system-wide low-level keyboard hook.\r\n#[derive(Debug)]\r\npub struct KeyboardHook {\r\n  /// Mach port for the created `CGEventTap`.\r\n  tap_port: Option<ThreadBound<CFRetained<CFMachPort>>>,\r\n\r\n  /// Pointer to [`CallbackData`], used by the `CGEventTap` callback.\r\n  callback_ptr: Option<usize>,\r\n}\r\n\r\nimpl KeyboardHook {\r\n  /// Creates an instance of `KeyboardHook`.\r\n  ///\r\n  /// The callback is called for every keyboard event and returns `true` if\r\n  /// the event should be intercepted.\r\n  pub fn new<F>(\r\n    callback: F,\r\n    dispatcher: &Dispatcher,\r\n  ) -> crate::Result<Self>\r\n  where\r\n    F: Fn(KeyEvent) -> bool + Send + Sync + 'static,\r\n  {\r\n    let callback_ptr = {\r\n      let data = Box::new(CallbackData {\r\n        callback: Box::new(callback),\r\n      });\r\n      Box::into_raw(data) as usize\r\n    };\r\n\r\n    let tap_port = dispatcher\r\n      .dispatch_sync(|| Self::create_event_tap(callback_ptr, dispatcher))\r\n      .flatten()\r\n      .inspect_err(|_| {\r\n        // Clean up the callback data if event tap creation fails.\r\n        let _ =\r\n          unsafe { Box::from_raw(callback_ptr as *mut CallbackData) };\r\n      })?;\r\n\r\n    Ok(Self {\r\n      tap_port: Some(tap_port),\r\n      callback_ptr: Some(callback_ptr),\r\n    })\r\n  }\r\n\r\n  /// Terminates the keyboard hook by invalidating the event tap.\r\n  #[allow(clippy::unnecessary_wraps)]\r\n  pub fn terminate(&mut self) -> crate::Result<()> {\r\n    if let Some(tap) = self.tap_port.take() {\r\n      // Invalidate the event tap to stop it from receiving events. This\r\n      // also invalidates the run loop source.\r\n      // See: https://developer.apple.com/documentation/corefoundation/cfmachportinvalidate(_:)\r\n      let _ = tap.with(|tap| CFMachPort::invalidate(tap));\r\n    }\r\n\r\n    // Clean up the callback data if it exists.\r\n    if let Some(ptr) = self.callback_ptr.take() {\r\n      let _ = unsafe { Box::from_raw(ptr as *mut CallbackData) };\r\n    }\r\n\r\n    Ok(())\r\n  }\r\n\r\n  /// Creates a `CGEventTap` object.\r\n  fn create_event_tap(\r\n    callback_ptr: usize,\r\n    dispatcher: &Dispatcher,\r\n  ) -> crate::Result<ThreadBound<CFRetained<CFMachPort>>> {\r\n    let mask: CGEventMask = (1u64 << u64::from(CGEventType::KeyDown.0))\r\n      | (1u64 << u64::from(CGEventType::KeyUp.0));\r\n\r\n    let tap_port = unsafe {\r\n      CGEvent::tap_create(\r\n        CGEventTapLocation::SessionEventTap,\r\n        CGEventTapPlacement::HeadInsertEventTap,\r\n        CGEventTapOptions::Default,\r\n        mask,\r\n        Some(Self::keyboard_event_callback),\r\n        callback_ptr as *mut c_void,\r\n      )\r\n      .ok_or_else(|| {\r\n        Error::Platform(\r\n          \"Failed to create `CGEventTap`. Accessibility permissions may be required.\"\r\n            .to_string(),\r\n        )\r\n      })\r\n    }?;\r\n\r\n    let loop_source =\r\n      CFMachPort::new_run_loop_source(None, Some(&tap_port), 0)\r\n        .ok_or_else(|| {\r\n          Error::Platform(\"Failed to create loop source\".to_string())\r\n        })?;\r\n\r\n    let current_loop = CFRunLoop::current().ok_or_else(|| {\r\n      Error::Platform(\"Failed to get current run loop\".to_string())\r\n    })?;\r\n\r\n    current_loop\r\n      .add_source(Some(&loop_source), unsafe { kCFRunLoopCommonModes });\r\n\r\n    CGEvent::tap_enable(&tap_port, true);\r\n\r\n    Ok(ThreadBound::new(tap_port, dispatcher.clone()))\r\n  }\r\n\r\n  /// Callback function for keyboard events.\r\n  ///\r\n  /// For use with `CGEventTap`.\r\n  extern \"C-unwind\" fn keyboard_event_callback(\r\n    _proxy: CGEventTapProxy,\r\n    event_type: CGEventType,\r\n    mut event: NonNull<CGEvent>,\r\n    user_info: *mut c_void,\r\n  ) -> *mut CGEvent {\r\n    if user_info.is_null() {\r\n      tracing::error!(\"Null pointer passed to keyboard event callback.\");\r\n      return unsafe { event.as_mut() };\r\n    }\r\n\r\n    // Extract the key code of the pressed/released key.\r\n    let key_code = KeyCode(unsafe {\r\n      CGEvent::integer_value_field(\r\n        Some(event.as_ref()),\r\n        CGEventField::KeyboardEventKeycode,\r\n      )\r\n    });\r\n\r\n    // Try to convert the key code to a known key.\r\n    let Ok(key) = Key::try_from(key_code) else {\r\n      return unsafe { event.as_mut() };\r\n    };\r\n\r\n    let event_flags = unsafe { CGEvent::flags(Some(event.as_ref())) };\r\n    let key_event = KeyEvent {\r\n      key,\r\n      key_code,\r\n      is_keypress: event_type == CGEventType::KeyDown,\r\n      event_flags,\r\n    };\r\n\r\n    // Get callback from user data and invoke it.\r\n    let data = unsafe { &*(user_info as *const CallbackData) };\r\n    let should_intercept = (data.callback)(key_event);\r\n\r\n    if should_intercept {\r\n      std::ptr::null_mut()\r\n    } else {\r\n      unsafe { event.as_mut() }\r\n    }\r\n  }\r\n}\r\n\r\nimpl Drop for KeyboardHook {\r\n  fn drop(&mut self) {\r\n    let _ = self.terminate();\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-platform/src/platform_impl/macos/mod.rs",
    "content": "mod application;\r\nmod application_observer;\r\nmod ax_ui_element;\r\nmod ax_value;\r\nmod display;\r\nmod display_listener;\r\nmod event_loop;\r\npub(crate) mod ffi;\r\nmod keyboard_hook;\r\nmod mouse_listener;\r\nmod native_window;\r\nmod notification_center;\r\nmod single_instance;\r\nmod window_listener;\r\n\r\npub(crate) use application::*;\r\npub(crate) use application_observer::*;\r\npub(crate) use ax_ui_element::*;\r\npub(crate) use ax_value::*;\r\npub(crate) use display::*;\r\npub(crate) use display_listener::*;\r\npub(crate) use event_loop::*;\r\npub(crate) use keyboard_hook::*;\r\npub(crate) use mouse_listener::*;\r\npub(crate) use native_window::*;\r\npub(crate) use notification_center::*;\r\npub(crate) use single_instance::*;\r\npub(crate) use window_listener::*;\r\n"
  },
  {
    "path": "packages/wm-platform/src/platform_impl/macos/mouse_listener.rs",
    "content": "use std::{\r\n  os::raw::c_void,\r\n  ptr::NonNull,\r\n  time::{Duration, Instant},\r\n};\r\n\r\nuse objc2_core_foundation::{\r\n  kCFRunLoopCommonModes, CFMachPort, CFRetained, CFRunLoop,\r\n};\r\nuse objc2_core_graphics::{\r\n  CGEvent, CGEventField, CGEventMask, CGEventTapLocation,\r\n  CGEventTapOptions, CGEventTapPlacement, CGEventTapProxy, CGEventType,\r\n};\r\nuse tokio::sync::mpsc;\r\n\r\nuse crate::{\r\n  mouse_listener::MouseEventKind,\r\n  platform_event::{MouseButton, MouseEvent, PressedButtons},\r\n  Dispatcher, Error, Point, ThreadBound, WindowId,\r\n};\r\n\r\n/// Data shared with the `CGEventTap` callback.\r\nstruct CallbackData {\r\n  event_tx: mpsc::UnboundedSender<MouseEvent>,\r\n\r\n  /// Pressed button state tracked from events.\r\n  pressed_buttons: PressedButtons,\r\n\r\n  /// Timestamp of the last emitted `Move` event for throttling.\r\n  last_move_emission: Option<Instant>,\r\n}\r\n\r\nimpl CallbackData {\r\n  fn new(event_tx: mpsc::UnboundedSender<MouseEvent>) -> Self {\r\n    Self {\r\n      event_tx,\r\n      pressed_buttons: PressedButtons::default(),\r\n      last_move_emission: None,\r\n    }\r\n  }\r\n}\r\n\r\n/// Platform-specific implementation of [`MouseListener`].\r\n#[derive(Debug)]\r\npub(crate) struct MouseListener {\r\n  dispatcher: Dispatcher,\r\n  event_tx: mpsc::UnboundedSender<MouseEvent>,\r\n\r\n  /// Mach port for the created `CGEventTap`.\r\n  tap_port: Option<ThreadBound<CFRetained<CFMachPort>>>,\r\n\r\n  /// Pointer to [`CallbackData`], used by the `CGEventTap` callback.\r\n  callback_data_ptr: Option<usize>,\r\n}\r\n\r\nimpl MouseListener {\r\n  /// Implements [`MouseListener::new`].\r\n  pub(crate) fn new(\r\n    enabled_events: &[MouseEventKind],\r\n    event_tx: mpsc::UnboundedSender<MouseEvent>,\r\n    dispatcher: &Dispatcher,\r\n  ) -> crate::Result<Self> {\r\n    let callback_data_ptr = {\r\n      let data = Box::new(CallbackData::new(event_tx.clone()));\r\n      Box::into_raw(data) as usize\r\n    };\r\n\r\n    let tap_port = dispatcher\r\n      .dispatch_sync(|| {\r\n        Self::create_event_tap(\r\n          enabled_events,\r\n          callback_data_ptr,\r\n          dispatcher,\r\n        )\r\n      })\r\n      .flatten()\r\n      .inspect_err(|_| {\r\n        // Clean up the callback data if event tap creation fails.\r\n        let _ =\r\n          unsafe { Box::from_raw(callback_data_ptr as *mut CallbackData) };\r\n      })?;\r\n\r\n    Ok(Self {\r\n      tap_port: Some(tap_port),\r\n      callback_data_ptr: Some(callback_data_ptr),\r\n      dispatcher: dispatcher.clone(),\r\n      event_tx,\r\n    })\r\n  }\r\n\r\n  /// Implements [`MouseListener::enable`].\r\n  pub(crate) fn enable(&mut self, enabled: bool) -> crate::Result<()> {\r\n    if let Some(tap_port) = &self.tap_port {\r\n      tap_port.with(|tap| CGEvent::tap_enable(tap, enabled))?;\r\n    }\r\n\r\n    Ok(())\r\n  }\r\n\r\n  /// Implements [`MouseListener::set_enabled_events`].\r\n  pub(crate) fn set_enabled_events(\r\n    &mut self,\r\n    enabled_events: &[MouseEventKind],\r\n  ) -> crate::Result<()> {\r\n    let _ = self.terminate();\r\n\r\n    let callback_data_ptr = {\r\n      let data = Box::new(CallbackData::new(self.event_tx.clone()));\r\n      Box::into_raw(data) as usize\r\n    };\r\n\r\n    let tap_port = self\r\n      .dispatcher\r\n      .dispatch_sync(|| {\r\n        Self::create_event_tap(\r\n          enabled_events,\r\n          callback_data_ptr,\r\n          &self.dispatcher,\r\n        )\r\n      })\r\n      .flatten()\r\n      .inspect_err(|_| {\r\n        // Clean up the callback data if event tap creation fails.\r\n        let _ =\r\n          unsafe { Box::from_raw(callback_data_ptr as *mut CallbackData) };\r\n      })?;\r\n\r\n    self.callback_data_ptr = Some(callback_data_ptr);\r\n    self.tap_port = Some(tap_port);\r\n\r\n    Ok(())\r\n  }\r\n\r\n  /// Implements [`MouseListener::terminate`].\r\n  pub(crate) fn terminate(&mut self) -> crate::Result<()> {\r\n    if let Some(tap) = self.tap_port.take() {\r\n      // Invalidate the tap to stop it from receiving events. This also\r\n      // invalidates the run loop source.\r\n      // See: https://developer.apple.com/documentation/corefoundation/cfmachportinvalidate(_:)\r\n      tap.with(|tap| CFMachPort::invalidate(tap))?;\r\n    }\r\n\r\n    // Clean up the callback data if it exists.\r\n    if let Some(ptr) = self.callback_data_ptr.take() {\r\n      let _ = unsafe { Box::from_raw(ptr as *mut CallbackData) };\r\n    }\r\n\r\n    Ok(())\r\n  }\r\n\r\n  /// Creates and registers a `CGEventTap` for mouse events.\r\n  fn create_event_tap(\r\n    enabled_events: &[MouseEventKind],\r\n    callback_data_ptr: usize,\r\n    dispatcher: &Dispatcher,\r\n  ) -> crate::Result<ThreadBound<CFRetained<CFMachPort>>> {\r\n    let mask = Self::event_mask_from_enabled(enabled_events);\r\n\r\n    let tap_port = unsafe {\r\n      CGEvent::tap_create(\r\n        CGEventTapLocation::AnnotatedSessionEventTap,\r\n        CGEventTapPlacement::HeadInsertEventTap,\r\n        CGEventTapOptions::Default,\r\n        mask,\r\n        Some(Self::mouse_event_callback),\r\n        callback_data_ptr as *mut c_void,\r\n      )\r\n      .ok_or_else(|| {\r\n        Error::Platform(\r\n          \"Failed to create `CGEventTap`. Accessibility permissions may be required.\".to_string(),\r\n        )\r\n      })\r\n    }?;\r\n\r\n    let loop_source =\r\n      CFMachPort::new_run_loop_source(None, Some(&tap_port), 0)\r\n        .ok_or_else(|| {\r\n          Error::Platform(\"Failed to create loop source\".to_string())\r\n        })?;\r\n\r\n    let current_loop = CFRunLoop::current().ok_or_else(|| {\r\n      Error::Platform(\"Failed to get current run loop\".to_string())\r\n    })?;\r\n\r\n    current_loop\r\n      .add_source(Some(&loop_source), unsafe { kCFRunLoopCommonModes });\r\n\r\n    CGEvent::tap_enable(&tap_port, true);\r\n\r\n    Ok(ThreadBound::new(tap_port, dispatcher.clone()))\r\n  }\r\n\r\n  /// Gets the `CGEvent` mask for the enabled mouse events.\r\n  fn event_mask_from_enabled(\r\n    enabled_events: &[MouseEventKind],\r\n  ) -> CGEventMask {\r\n    let mut mask = 0u64;\r\n\r\n    for event in enabled_events {\r\n      match event {\r\n        MouseEventKind::Move => {\r\n          // NOTE: `MouseMoved` doesn't get triggered when clicking and\r\n          // dragging. Therefore, we also listen for `LeftMouseDragged`\r\n          // and `RightMouseDragged` events.\r\n          mask |= 1u64 << u64::from(CGEventType::MouseMoved.0);\r\n          mask |= 1u64 << u64::from(CGEventType::LeftMouseDragged.0);\r\n          mask |= 1u64 << u64::from(CGEventType::RightMouseDragged.0);\r\n        }\r\n        MouseEventKind::LeftButtonDown => {\r\n          mask |= 1u64 << u64::from(CGEventType::LeftMouseDown.0);\r\n        }\r\n        MouseEventKind::RightButtonDown => {\r\n          mask |= 1u64 << u64::from(CGEventType::RightMouseDown.0);\r\n        }\r\n        MouseEventKind::LeftButtonUp => {\r\n          mask |= 1u64 << u64::from(CGEventType::LeftMouseUp.0);\r\n        }\r\n        MouseEventKind::RightButtonUp => {\r\n          mask |= 1u64 << u64::from(CGEventType::RightMouseUp.0);\r\n        }\r\n      }\r\n    }\r\n\r\n    mask\r\n  }\r\n\r\n  /// Callback for the `CGEventTap`.\r\n  extern \"C-unwind\" fn mouse_event_callback(\r\n    _: CGEventTapProxy,\r\n    cg_event_type: CGEventType,\r\n    mut cg_event: NonNull<CGEvent>,\r\n    user_info: *mut c_void,\r\n  ) -> *mut CGEvent {\r\n    if user_info.is_null() {\r\n      tracing::error!(\"Null pointer passed to mouse event callback.\");\r\n      return unsafe { cg_event.as_mut() };\r\n    }\r\n\r\n    let data = unsafe { &mut *user_info.cast::<CallbackData>() };\r\n\r\n    // Map a `CGEventType` to a `MouseEventKind`.\r\n    let event_kind = match cg_event_type {\r\n      CGEventType::LeftMouseDown => MouseEventKind::LeftButtonDown,\r\n      CGEventType::LeftMouseUp => MouseEventKind::LeftButtonUp,\r\n      CGEventType::RightMouseDown => MouseEventKind::RightButtonDown,\r\n      CGEventType::RightMouseUp => MouseEventKind::RightButtonUp,\r\n      _ => MouseEventKind::Move,\r\n    };\r\n\r\n    // Extract the cursor position from the `CGEvent`.\r\n    let cg_event_ref = unsafe { cg_event.as_ref() };\r\n    let position = {\r\n      let cg_point = CGEvent::location(Some(cg_event_ref));\r\n\r\n      #[allow(clippy::cast_possible_truncation)]\r\n      Point {\r\n        x: cg_point.x as i32,\r\n        y: cg_point.y as i32,\r\n      }\r\n    };\r\n\r\n    // NOTE: Unfortunately quite unreliable. Returns 0 in most cases with\r\n    // the real window ID interspersed every so often. Often a 100–200ms\r\n    // delay before the real window ID is returned.\r\n    let window_below_cursor = {\r\n      let window_id = CGEvent::integer_value_field(\r\n        Some(cg_event_ref),\r\n        CGEventField::MouseEventWindowUnderMousePointer,\r\n      );\r\n\r\n      if window_id == 0 {\r\n        None\r\n      } else {\r\n        #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]\r\n        Some(WindowId(window_id as u32))\r\n      }\r\n    };\r\n\r\n    data.pressed_buttons.update(event_kind);\r\n\r\n    // Throttle mouse move events so that there's a minimum of 50ms between\r\n    // each emission. State change events (button down/up) always get\r\n    // emitted.\r\n    let should_emit = match event_kind {\r\n      MouseEventKind::Move => {\r\n        let has_elapsed_throttle =\r\n          data.last_move_emission.is_none_or(|timestamp| {\r\n            timestamp.elapsed() >= Duration::from_millis(50)\r\n          });\r\n\r\n        // TODO: This is a hack to let through mouse move events when\r\n        // they contain a window ID. macOS sporadically includes the\r\n        // window ID on mouse events.\r\n        has_elapsed_throttle || window_below_cursor.is_some()\r\n      }\r\n      _ => true,\r\n    };\r\n\r\n    let mouse_event = match event_kind {\r\n      MouseEventKind::LeftButtonDown => MouseEvent::ButtonDown {\r\n        position,\r\n        button: MouseButton::Left,\r\n        pressed_buttons: data.pressed_buttons,\r\n      },\r\n      MouseEventKind::LeftButtonUp => MouseEvent::ButtonUp {\r\n        position,\r\n        button: MouseButton::Left,\r\n        pressed_buttons: data.pressed_buttons,\r\n      },\r\n      MouseEventKind::RightButtonDown => MouseEvent::ButtonDown {\r\n        position,\r\n        button: MouseButton::Right,\r\n        pressed_buttons: data.pressed_buttons,\r\n      },\r\n      MouseEventKind::RightButtonUp => MouseEvent::ButtonUp {\r\n        position,\r\n        button: MouseButton::Right,\r\n        pressed_buttons: data.pressed_buttons,\r\n      },\r\n      MouseEventKind::Move => MouseEvent::Move {\r\n        position,\r\n        pressed_buttons: data.pressed_buttons,\r\n        window_below_cursor,\r\n      },\r\n    };\r\n\r\n    if should_emit {\r\n      let _ = data.event_tx.send(mouse_event);\r\n\r\n      if event_kind == MouseEventKind::Move {\r\n        data.last_move_emission = Some(Instant::now());\r\n      }\r\n    }\r\n\r\n    unsafe { cg_event.as_mut() }\r\n  }\r\n}\r\n\r\nimpl Drop for MouseListener {\r\n  fn drop(&mut self) {\r\n    if let Err(err) = self.terminate() {\r\n      tracing::warn!(\"Failed to terminate mouse listener: {}\", err);\r\n    }\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-platform/src/platform_impl/macos/native_window.rs",
    "content": "use std::sync::Arc;\r\n\r\nuse objc2::MainThreadMarker;\r\nuse objc2_app_kit::{\r\n  NSApplicationActivationOptions, NSWindow, NSWorkspace,\r\n};\r\nuse objc2_application_services::{AXError, AXValue};\r\nuse objc2_core_foundation::{\r\n  CFBoolean, CFRetained, CFString, CGPoint, CGSize,\r\n};\r\nuse objc2_core_graphics::{CGDisplayIsAsleep, CGError};\r\n\r\nuse crate::{\r\n  platform_impl::{\r\n    self, ffi, AXUIElement, AXUIElementExt, AXValueExt, Application,\r\n  },\r\n  Dispatcher, Point, Rect, ThreadBound, WindowId,\r\n};\r\n\r\n/// Platform-specific implementation of [`NativeWindow`].\r\n#[derive(Clone, Debug)]\r\npub(crate) struct NativeWindow {\r\n  pub(crate) id: WindowId,\r\n  pub(crate) element: Arc<ThreadBound<CFRetained<AXUIElement>>>,\r\n  pub(crate) application: Application,\r\n}\r\n\r\nimpl NativeWindow {\r\n  /// Creates an instance of `NativeWindow`.\r\n  #[must_use]\r\n  pub(crate) fn new(\r\n    id: WindowId,\r\n    element: ThreadBound<CFRetained<AXUIElement>>,\r\n    application: Application,\r\n  ) -> Self {\r\n    Self {\r\n      element: Arc::new(element),\r\n      id,\r\n      application,\r\n    }\r\n  }\r\n\r\n  /// Implements [`NativeWindow::id`].\r\n  pub(crate) fn id(&self) -> WindowId {\r\n    self.id\r\n  }\r\n\r\n  /// Implements [`NativeWindow::title`].\r\n  pub(crate) fn title(&self) -> crate::Result<String> {\r\n    self.element.with(|el| {\r\n      el.get_attribute::<CFString>(\"AXTitle\")\r\n        .map(|cf_string| cf_string.to_string())\r\n    })?\r\n  }\r\n\r\n  /// Implements [`NativeWindow::process_name`].\r\n  pub(crate) fn process_name(&self) -> crate::Result<String> {\r\n    self\r\n      .application\r\n      .process_name()\r\n      .ok_or(crate::Error::Platform(\r\n        \"Failed to get application process name.\".to_string(),\r\n      ))\r\n  }\r\n\r\n  /// Implements [`NativeWindow::frame`].\r\n  pub(crate) fn frame(&self) -> crate::Result<Rect> {\r\n    // TODO: Consider refactoring this to use a single dispatch.\r\n    // TODO: Would `AXFrame` work instead?\r\n    let size = self.size()?;\r\n    let position = self.position()?;\r\n    #[allow(clippy::cast_possible_truncation)]\r\n    Ok(Rect::from_xy(\r\n      position.0 as i32,\r\n      position.1 as i32,\r\n      size.0 as i32,\r\n      size.1 as i32,\r\n    ))\r\n  }\r\n\r\n  /// Implements [`NativeWindow::position`].\r\n  pub(crate) fn position(&self) -> crate::Result<(f64, f64)> {\r\n    self.element.with(move |el| {\r\n      el.get_attribute::<AXValue>(\"AXPosition\")\r\n        .and_then(|ax_value| ax_value.value_strict::<CGPoint>())\r\n        .map(|point| (point.x, point.y))\r\n    })?\r\n  }\r\n\r\n  /// Implements [`NativeWindow::size`].\r\n  pub(crate) fn size(&self) -> crate::Result<(f64, f64)> {\r\n    self.element.with(move |el| {\r\n      el.get_attribute::<AXValue>(\"AXSize\")\r\n        .and_then(|ax_value| ax_value.value_strict::<CGSize>())\r\n        .map(|size| (size.width, size.height))\r\n    })?\r\n  }\r\n\r\n  /// Implements [`NativeWindow::is_valid`].\r\n  pub(crate) fn is_valid(&self) -> bool {\r\n    // Query `AXRole`, which is present on all valid `AXUIElement`s.\r\n    self\r\n      .element\r\n      .with(|el| match el.get_attribute::<CFString>(\"AXRole\") {\r\n        Err(crate::Error::Accessibility(_, code))\r\n          if code == AXError::InvalidUIElement.0 =>\r\n        {\r\n          let has_login_window = NSWorkspace::sharedWorkspace()\r\n            .frontmostApplication()\r\n            .and_then(|app| app.bundleIdentifier())\r\n            .is_some_and(|id| id.to_string() == \"com.apple.loginwindow\");\r\n\r\n          // AX calls transiently fail with `InvalidUIElement` during\r\n          // sleep/wake. The window should still be considered valid.\r\n          //\r\n          // Events during sleep:\r\n          //   1. Display goes asleep.\r\n          //   2. AX calls fail with `InvalidUIElement`.\r\n          //   3. Login window activates.\r\n          //\r\n          // Events during wake:\r\n          //   1. Display wakes up.\r\n          //   2. Login window deactivates and AX calls succeed again.\r\n          //\r\n          // Perf: `CGDisplayIsAsleep` ~1-5µs, login window check ~1-2ms.\r\n          CGDisplayIsAsleep(0) || has_login_window\r\n        }\r\n        _ => true,\r\n      })\r\n      .unwrap_or(false)\r\n  }\r\n\r\n  /// Implements [`NativeWindow::is_visible`].\r\n  #[allow(clippy::unnecessary_wraps)]\r\n  pub(crate) fn is_visible(&self) -> crate::Result<bool> {\r\n    Ok(!self.application.is_hidden())\r\n  }\r\n\r\n  /// Implements [`NativeWindow::is_minimized`].\r\n  pub(crate) fn is_minimized(&self) -> crate::Result<bool> {\r\n    self.element.with(|el| {\r\n      el.get_attribute::<CFBoolean>(\"AXMinimized\")\r\n        .map(|cf_bool| cf_bool.value())\r\n    })?\r\n  }\r\n\r\n  /// Implements [`NativeWindow::is_maximized`].\r\n  pub(crate) fn is_maximized(&self) -> crate::Result<bool> {\r\n    self.element.with(|el| {\r\n      el.get_attribute::<CFBoolean>(\"AXFullScreen\")\r\n        .map(|cf_bool| cf_bool.value())\r\n    })?\r\n  }\r\n\r\n  /// Implements [`NativeWindow::is_resizable`].\r\n  #[allow(clippy::unnecessary_wraps, clippy::unused_self)]\r\n  pub(crate) fn is_resizable(&self) -> crate::Result<bool> {\r\n    // TODO: Not sure if this is even available via the AX API.\r\n    Ok(true)\r\n  }\r\n\r\n  /// Implements [`NativeWindow::is_desktop_window`].\r\n  #[allow(clippy::unnecessary_wraps)]\r\n  pub(crate) fn is_desktop_window(&self) -> crate::Result<bool> {\r\n    Ok(\r\n      self.application.bundle_id() == Some(\"com.apple.finder\".to_string()),\r\n    )\r\n  }\r\n\r\n  /// Implements [`NativeWindow::set_frame`].\r\n  pub(crate) fn set_frame(&self, rect: &Rect) -> crate::Result<()> {\r\n    // TODO: Consider adding a separate `set_frame_async` method which\r\n    // spawns a thread. Calling blocking AXUIElement methods from different\r\n    // threads supposedly works fine.\r\n    // TODO: Refactor the repeated `set_attribute` calls.\r\n    let rect = rect.clone();\r\n    self.with_enhanced_ui_disabled(move |el| -> crate::Result<()> {\r\n      let ax_size = CGSize::new(rect.width().into(), rect.height().into());\r\n      let ax_value = AXValue::new_strict(&ax_size)?;\r\n      el.set_attribute(\"AXSize\", &ax_value)?;\r\n      let ax_point = CGPoint::new(rect.x().into(), rect.y().into());\r\n      let ax_value = AXValue::new_strict(&ax_point)?;\r\n      el.set_attribute(\"AXPosition\", &ax_value)?;\r\n      let ax_size = CGSize::new(rect.width().into(), rect.height().into());\r\n      let ax_value = AXValue::new_strict(&ax_size)?;\r\n      el.set_attribute(\"AXSize\", &ax_value)\r\n    })\r\n  }\r\n\r\n  /// Implements [`NativeWindow::resize`].\r\n  pub(crate) fn resize(\r\n    &self,\r\n    width: i32,\r\n    height: i32,\r\n  ) -> crate::Result<()> {\r\n    self.with_enhanced_ui_disabled(move |el| -> crate::Result<()> {\r\n      let ax_size = CGSize::new(width.into(), height.into());\r\n      let ax_value = AXValue::new_strict(&ax_size)?;\r\n      el.set_attribute(\"AXSize\", &ax_value)\r\n    })\r\n  }\r\n\r\n  /// Implements [`NativeWindow::reposition`].\r\n  pub(crate) fn reposition(&self, x: i32, y: i32) -> crate::Result<()> {\r\n    self.with_enhanced_ui_disabled(move |el| -> crate::Result<()> {\r\n      let ax_point = CGPoint::new(x.into(), y.into());\r\n      let ax_value = AXValue::new_strict(&ax_point)?;\r\n      el.set_attribute(\"AXPosition\", &ax_value)\r\n    })\r\n  }\r\n\r\n  /// Implements [`NativeWindow::minimize`].\r\n  pub(crate) fn minimize(&self) -> crate::Result<()> {\r\n    self.element.with(move |el| -> crate::Result<()> {\r\n      let ax_bool = CFBoolean::new(true);\r\n      el.set_attribute::<CFBoolean>(\"AXMinimized\", &ax_bool.into())\r\n    })?\r\n  }\r\n\r\n  /// Implements [`NativeWindow::maximize`].\r\n  pub(crate) fn maximize(&self) -> crate::Result<()> {\r\n    self.element.with(move |el| -> crate::Result<()> {\r\n      let ax_bool = CFBoolean::new(true);\r\n      el.set_attribute::<CFBoolean>(\"AXFullScreen\", &ax_bool.into())\r\n    })?\r\n  }\r\n\r\n  /// Implements [`NativeWindow::focus`].\r\n  pub(crate) fn focus(&self) -> crate::Result<()> {\r\n    let psn = self.application.psn()?;\r\n    self.set_front_process(&psn)?;\r\n    self.set_key_window(&psn)?;\r\n    self.raise()\r\n  }\r\n\r\n  /// Implements [`NativeWindow::close`].\r\n  pub(crate) fn close(&self) -> crate::Result<()> {\r\n    self.element.with(|el| -> crate::Result<()> {\r\n      let close_button =\r\n        el.get_attribute::<AXUIElement>(\"AXCloseButton\")?;\r\n\r\n      // Simulate pressing the window's close button.\r\n      let result = unsafe {\r\n        close_button.perform_action(&CFString::from_str(\"AXPress\"))\r\n      };\r\n\r\n      if result != AXError::Success {\r\n        return Err(crate::Error::Accessibility(\r\n          \"AXPress\".to_string(),\r\n          result.0,\r\n        ));\r\n      }\r\n\r\n      Ok(())\r\n    })?\r\n  }\r\n\r\n  /// Executes a callback with the `AXEnhancedUserInterface` attribute\r\n  /// temporarily disabled on the application `AXUIElement`.\r\n  ///\r\n  /// This is to prevent inconsistent window resizing and repositioning\r\n  /// for certain applications (e.g. Firefox).\r\n  ///\r\n  /// References:\r\n  /// - <https://github.com/koekeishiya/yabai/commit/3fe4c77b001e1a4f613c26f01ea68c0f09327f3a>\r\n  /// - <https://github.com/rxhanson/Rectangle/pull/285>\r\n  fn with_enhanced_ui_disabled<F, R>(\r\n    &self,\r\n    callback: F,\r\n  ) -> crate::Result<R>\r\n  where\r\n    F: FnOnce(&CFRetained<AXUIElement>) -> crate::Result<R> + Send,\r\n    R: Send,\r\n  {\r\n    self.application.ax_element.with(|app_el| {\r\n      // Get whether enhanced UI is currently enabled.\r\n      let was_enabled = app_el\r\n        .get_attribute::<CFBoolean>(\"AXEnhancedUserInterface\")\r\n        .is_ok_and(|cf_bool| cf_bool.value());\r\n\r\n      // Disable enhanced UI if it was enabled.\r\n      if was_enabled {\r\n        let ax_bool = CFBoolean::new(false);\r\n        let _ = app_el.set_attribute::<CFBoolean>(\r\n          \"AXEnhancedUserInterface\",\r\n          &ax_bool.into(),\r\n        );\r\n      }\r\n\r\n      // Execute the callback with the window element.\r\n      let result = self.element.with(callback);\r\n\r\n      // Restore enhanced UI if it was originally enabled.\r\n      if was_enabled {\r\n        let ax_bool = CFBoolean::new(true);\r\n        let _ = app_el.set_attribute::<CFBoolean>(\r\n          \"AXEnhancedUserInterface\",\r\n          &ax_bool.into(),\r\n        );\r\n      }\r\n\r\n      result\r\n    })??\r\n  }\r\n\r\n  fn raise(&self) -> crate::Result<()> {\r\n    self.element.with(move |el| -> crate::Result<()> {\r\n      // This has a couple of caveats:\r\n      // - Some windows do not get raised without first calling\r\n      //   `_SLPSSetFrontProcessWithOptions`.\r\n      // - This changes focus if raising a window of the frontmost (active)\r\n      //   application. For example, if 2 Chrome windows are open and one\r\n      //   is focused, raising the other will change focus to the other\r\n      //   window.\r\n      //\r\n      // Because of these caveats, this method is not exposed as a public\r\n      // API. It's also the reason why the GlazeWM feature of bringing all\r\n      // tiling/floating windows to the front on focus change is not\r\n      // implemented for macOS.\r\n      let result =\r\n        unsafe { el.perform_action(&CFString::from_str(\"AXRaise\")) };\r\n\r\n      if result != AXError::Success {\r\n        return Err(crate::Error::Accessibility(\r\n          \"AXRaise\".to_string(),\r\n          result.0,\r\n        ));\r\n      }\r\n\r\n      Ok(())\r\n    })?\r\n  }\r\n\r\n  fn set_front_process(\r\n    &self,\r\n    psn: &ffi::ProcessSerialNumber,\r\n  ) -> crate::Result<()> {\r\n    let result = unsafe {\r\n      #[allow(clippy::cast_possible_wrap)]\r\n      ffi::_SLPSSetFrontProcessWithOptions(\r\n        psn,\r\n        self.id.0 as i32,\r\n        ffi::CPS_USER_GENERATED,\r\n      )\r\n    };\r\n\r\n    if result != CGError::Success {\r\n      return Err(crate::Error::Platform(\r\n        \"Failed to set front process.\".to_string(),\r\n      ));\r\n    }\r\n\r\n    Ok(())\r\n  }\r\n\r\n  fn set_key_window(\r\n    &self,\r\n    psn: &ffi::ProcessSerialNumber,\r\n  ) -> crate::Result<()> {\r\n    // Ref: https://github.com/Hammerspoon/hammerspoon/issues/370#issuecomment-545545468\r\n    let window_id = self.id.0.to_ne_bytes();\r\n    let mut event1 = [0u8; 0xf8];\r\n    event1[0x04] = 0xf8;\r\n    event1[0x08] = 0x01;\r\n    event1[0x3a] = 0x10;\r\n    event1[0x3c..(0x3c + window_id.len())].copy_from_slice(&window_id);\r\n    event1[0x20..(0x20 + 0x10)].fill(0xff);\r\n\r\n    let mut event2 = event1;\r\n    event2[0x08] = 0x02;\r\n\r\n    for event in [event1, event2] {\r\n      let result =\r\n        unsafe { ffi::SLPSPostEventRecordTo(psn, event.as_ptr().cast()) };\r\n\r\n      if result != CGError::Success {\r\n        return Err(crate::Error::Platform(\r\n          \"Failed to set key window.\".to_string(),\r\n        ));\r\n      }\r\n    }\r\n\r\n    Ok(())\r\n  }\r\n}\r\n\r\nimpl From<NativeWindow> for crate::NativeWindow {\r\n  fn from(window: NativeWindow) -> Self {\r\n    crate::NativeWindow { inner: window }\r\n  }\r\n}\r\n\r\n/// Implements [`Dispatcher::visible_windows`].\r\npub(crate) fn visible_windows(\r\n  dispatcher: &Dispatcher,\r\n) -> crate::Result<Vec<crate::NativeWindow>> {\r\n  Ok(\r\n    platform_impl::all_applications(dispatcher)?\r\n      .iter()\r\n      .filter_map(|app| app.windows().ok())\r\n      .flat_map(std::iter::IntoIterator::into_iter)\r\n      .collect(),\r\n  )\r\n}\r\n\r\n/// Implements [`Dispatcher::window_by_id`].\r\npub(crate) fn window_by_id(\r\n  id: WindowId,\r\n  dispatcher: &Dispatcher,\r\n) -> crate::Result<Option<crate::NativeWindow>> {\r\n  // TODO: The performance of this is terrible. A better solution would be\r\n  // to have a cache of window ID <-> `NativeWindow` instances.\r\n  for app in platform_impl::all_applications(dispatcher)? {\r\n    if let Ok(windows) = app.windows() {\r\n      if let Some(win) = windows.into_iter().find(|w| w.id() == id) {\r\n        return Ok(Some(win));\r\n      }\r\n    }\r\n  }\r\n\r\n  Ok(None)\r\n}\r\n\r\n/// Implements [`Dispatcher::window_from_point`].\r\npub(crate) fn window_from_point(\r\n  point: &Point,\r\n  dispatcher: &Dispatcher,\r\n) -> crate::Result<Option<crate::NativeWindow>> {\r\n  // Get the top-most window ID at the given point.\r\n  let window_id = dispatcher.dispatch_sync(|| {\r\n    let cg_point = CGPoint {\r\n      x: f64::from(point.x),\r\n      y: f64::from(point.y),\r\n    };\r\n\r\n    let window_id = unsafe {\r\n      NSWindow::windowNumberAtPoint_belowWindowWithWindowNumber(\r\n        cg_point,\r\n        // 0 for all windows.\r\n        0,\r\n        MainThreadMarker::new_unchecked(),\r\n      )\r\n    };\r\n\r\n    #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]\r\n    WindowId(window_id as u32)\r\n  })?;\r\n\r\n  // No window found at the given point.\r\n  if window_id.0 == 0 {\r\n    return Ok(None);\r\n  }\r\n\r\n  window_by_id(window_id, dispatcher)\r\n    .map_err(|_| crate::Error::WindowNotFound)\r\n}\r\n\r\n/// Implements [`Dispatcher::focused_window`].\r\npub(crate) fn focused_window(\r\n  dispatcher: &Dispatcher,\r\n) -> crate::Result<crate::NativeWindow> {\r\n  dispatcher\r\n    .dispatch_sync(|| {\r\n      // Get the frontmost (active) application.\r\n      let frontmost_app = NSWorkspace::sharedWorkspace()\r\n        .frontmostApplication()\r\n        .map(|app| Application::new(app, dispatcher.clone()));\r\n\r\n      // Get the focused window of the frontmost application.\r\n      frontmost_app.and_then(|app| app.focused_window().ok().flatten())\r\n    })?\r\n    .ok_or(crate::Error::WindowNotFound)\r\n}\r\n\r\n/// Implements [`Dispatcher::reset_focus`].\r\n// TODO: Move this to a better-suited module.\r\npub(crate) fn reset_focus(dispatcher: &Dispatcher) -> crate::Result<()> {\r\n  let Some(application) = platform_impl::application_for_bundle_id(\r\n    \"com.apple.finder\",\r\n    dispatcher,\r\n  )?\r\n  else {\r\n    return Err(crate::Error::Platform(\r\n      \"Failed to get desktop application.\".to_string(),\r\n    ));\r\n  };\r\n\r\n  let success = application.ns_app.activateWithOptions(\r\n    NSApplicationActivationOptions::ActivateAllWindows,\r\n  );\r\n\r\n  if !success {\r\n    return Err(crate::Error::Platform(\r\n      \"Failed to activate desktop application.\".to_string(),\r\n    ));\r\n  }\r\n\r\n  Ok(())\r\n}\r\n"
  },
  {
    "path": "packages/wm-platform/src/platform_impl/macos/notification_center.rs",
    "content": "use objc2::{\r\n  define_class, msg_send, rc::Retained, runtime::AnyObject, sel,\r\n  AnyThread, DefinedClass,\r\n};\r\nuse objc2_app_kit::{\r\n  NSApplicationDidChangeScreenParametersNotification,\r\n  NSRunningApplication, NSWorkspace,\r\n  NSWorkspaceActiveSpaceDidChangeNotification,\r\n  NSWorkspaceDidActivateApplicationNotification,\r\n  NSWorkspaceDidHideApplicationNotification,\r\n  NSWorkspaceDidLaunchApplicationNotification,\r\n  NSWorkspaceDidTerminateApplicationNotification,\r\n  NSWorkspaceDidUnhideApplicationNotification,\r\n  NSWorkspaceDidWakeNotification, NSWorkspaceWillSleepNotification,\r\n};\r\nuse objc2_foundation::{\r\n  ns_string, NSNotification, NSNotificationCenter, NSNotificationName,\r\n  NSObject, NSString,\r\n};\r\nuse tokio::sync::mpsc;\r\n\r\n/// Notification names for observing macOS workspace and screen events.\r\n#[derive(Debug)]\r\npub(crate) enum NotificationName {\r\n  WorkspaceActiveSpaceDidChange,\r\n  WorkspaceDidActivateApplication,\r\n  WorkspaceDidLaunchApplication,\r\n  WorkspaceDidTerminateApplication,\r\n  WorkspaceDidHideApplication,\r\n  WorkspaceDidUnhideApplication,\r\n  WorkspaceDidWake,\r\n  WorkspaceWillSleep,\r\n  ApplicationDidChangeScreenParameters,\r\n}\r\n\r\nimpl From<&NSNotificationName> for NotificationName {\r\n  fn from(name: &NSNotificationName) -> Self {\r\n    if name == unsafe { NSWorkspaceDidLaunchApplicationNotification } {\r\n      Self::WorkspaceDidLaunchApplication\r\n    } else if name\r\n      == unsafe { NSWorkspaceDidActivateApplicationNotification }\r\n    {\r\n      Self::WorkspaceDidActivateApplication\r\n    } else if name\r\n      == unsafe { NSWorkspaceDidTerminateApplicationNotification }\r\n    {\r\n      Self::WorkspaceDidTerminateApplication\r\n    } else if name\r\n      == unsafe { NSWorkspaceActiveSpaceDidChangeNotification }\r\n    {\r\n      Self::WorkspaceActiveSpaceDidChange\r\n    } else if name == unsafe { NSWorkspaceDidHideApplicationNotification }\r\n    {\r\n      Self::WorkspaceDidHideApplication\r\n    } else if name\r\n      == unsafe { NSWorkspaceDidUnhideApplicationNotification }\r\n    {\r\n      Self::WorkspaceDidUnhideApplication\r\n    } else if name == unsafe { NSWorkspaceDidWakeNotification } {\r\n      Self::WorkspaceDidWake\r\n    } else if name == unsafe { NSWorkspaceWillSleepNotification } {\r\n      Self::WorkspaceWillSleep\r\n    } else if name\r\n      == unsafe { NSApplicationDidChangeScreenParametersNotification }\r\n    {\r\n      Self::ApplicationDidChangeScreenParameters\r\n    } else {\r\n      panic!(\"Unknown notification name: {name}\");\r\n    }\r\n  }\r\n}\r\n\r\nimpl From<NotificationName> for &NSString {\r\n  fn from(name: NotificationName) -> Self {\r\n    match name {\r\n      NotificationName::WorkspaceActiveSpaceDidChange => unsafe {\r\n        NSWorkspaceActiveSpaceDidChangeNotification\r\n      },\r\n      NotificationName::WorkspaceDidActivateApplication => unsafe {\r\n        NSWorkspaceDidActivateApplicationNotification\r\n      },\r\n      NotificationName::WorkspaceDidLaunchApplication => unsafe {\r\n        NSWorkspaceDidLaunchApplicationNotification\r\n      },\r\n      NotificationName::WorkspaceDidTerminateApplication => unsafe {\r\n        NSWorkspaceDidTerminateApplicationNotification\r\n      },\r\n      NotificationName::WorkspaceDidHideApplication => unsafe {\r\n        NSWorkspaceDidHideApplicationNotification\r\n      },\r\n      NotificationName::WorkspaceDidUnhideApplication => unsafe {\r\n        NSWorkspaceDidUnhideApplicationNotification\r\n      },\r\n      NotificationName::WorkspaceDidWake => unsafe {\r\n        NSWorkspaceDidWakeNotification\r\n      },\r\n      NotificationName::WorkspaceWillSleep => unsafe {\r\n        NSWorkspaceWillSleepNotification\r\n      },\r\n      NotificationName::ApplicationDidChangeScreenParameters => unsafe {\r\n        NSApplicationDidChangeScreenParametersNotification\r\n      },\r\n    }\r\n  }\r\n}\r\n\r\n/// Events received from macOS notification center observers.\r\n#[derive(Debug)]\r\npub(crate) enum NotificationEvent {\r\n  WorkspaceActiveSpaceDidChange,\r\n  WorkspaceDidActivateApplication(Retained<NSRunningApplication>),\r\n  WorkspaceDidLaunchApplication(Retained<NSRunningApplication>),\r\n  WorkspaceDidTerminateApplication(Retained<NSRunningApplication>),\r\n  WorkspaceDidHideApplication(Retained<NSRunningApplication>),\r\n  WorkspaceDidUnhideApplication(Retained<NSRunningApplication>),\r\n  WorkspaceWillSleep,\r\n  WorkspaceDidWake,\r\n  ApplicationDidChangeScreenParameters,\r\n}\r\n\r\n/// Instance variables for `NotificationObserver`.\r\n#[repr(C)]\r\npub(crate) struct NotificationObserverIvars {\r\n  events_tx: mpsc::UnboundedSender<NotificationEvent>,\r\n}\r\n\r\ndefine_class! {\r\n  // SAFETY:\r\n  // - The superclass `NSObject` does not have any subclassing requirements.\r\n  // - `NotificationObserver` does not implement `Drop`.\r\n  #[unsafe(super(NSObject))]\r\n  #[ivars = Box<NotificationObserverIvars>]\r\n  pub(crate) struct NotificationObserver;\r\n\r\n  // SAFETY: Each of these method signatures must match their invocations.\r\n  impl NotificationObserver {\r\n    #[unsafe(method(onEvent:))]\r\n    fn on_event(&self, notif: &NSNotification) {\r\n      self.handle_event(notif);\r\n    }\r\n  }\r\n}\r\n\r\nimpl NotificationObserver {\r\n  pub fn new(\r\n  ) -> (Retained<Self>, mpsc::UnboundedReceiver<NotificationEvent>) {\r\n    let (events_tx, events_rx) = mpsc::unbounded_channel();\r\n\r\n    let instance = Self::alloc()\r\n      .set_ivars(Box::new(NotificationObserverIvars { events_tx }));\r\n\r\n    // SAFETY: The signature of `NSObject`'s `init` method is correct.\r\n    (unsafe { msg_send![super(instance), init] }, events_rx)\r\n  }\r\n\r\n  fn handle_event(&self, notif: &NSNotification) {\r\n    tracing::debug!(\"Received notification: {notif:#?}\");\r\n\r\n    match NotificationName::from(&*notif.name()) {\r\n      NotificationName::WorkspaceActiveSpaceDidChange => {\r\n        self.emit_event(NotificationEvent::WorkspaceActiveSpaceDidChange);\r\n      }\r\n      NotificationName::WorkspaceDidActivateApplication => {\r\n        if let Some(app) = unsafe { app_from_notification(notif) } {\r\n          self.emit_event(\r\n            NotificationEvent::WorkspaceDidActivateApplication(app),\r\n          );\r\n        } else {\r\n          tracing::warn!(\r\n            \"Failed to extract application from activate notification\"\r\n          );\r\n        }\r\n      }\r\n      NotificationName::WorkspaceDidLaunchApplication => {\r\n        if let Some(app) = unsafe { app_from_notification(notif) } {\r\n          self.emit_event(\r\n            NotificationEvent::WorkspaceDidLaunchApplication(app),\r\n          );\r\n        } else {\r\n          tracing::warn!(\r\n            \"Failed to extract application from launch notification\"\r\n          );\r\n        }\r\n      }\r\n      NotificationName::WorkspaceDidTerminateApplication => {\r\n        if let Some(app) = unsafe { app_from_notification(notif) } {\r\n          self.emit_event(\r\n            NotificationEvent::WorkspaceDidTerminateApplication(app),\r\n          );\r\n        } else {\r\n          tracing::warn!(\r\n            \"Failed to extract application from terminate notification\"\r\n          );\r\n        }\r\n      }\r\n      NotificationName::WorkspaceDidHideApplication => {\r\n        if let Some(app) = unsafe { app_from_notification(notif) } {\r\n          self.emit_event(NotificationEvent::WorkspaceDidHideApplication(\r\n            app,\r\n          ));\r\n        }\r\n      }\r\n      NotificationName::WorkspaceDidUnhideApplication => {\r\n        if let Some(app) = unsafe { app_from_notification(notif) } {\r\n          self.emit_event(\r\n            NotificationEvent::WorkspaceDidUnhideApplication(app),\r\n          );\r\n        }\r\n      }\r\n      NotificationName::WorkspaceDidWake => {\r\n        self.emit_event(NotificationEvent::WorkspaceDidWake);\r\n      }\r\n      NotificationName::WorkspaceWillSleep => {\r\n        self.emit_event(NotificationEvent::WorkspaceWillSleep);\r\n      }\r\n      NotificationName::ApplicationDidChangeScreenParameters => {\r\n        self.emit_event(\r\n          NotificationEvent::ApplicationDidChangeScreenParameters,\r\n        );\r\n      }\r\n    }\r\n  }\r\n\r\n  fn emit_event(&self, event: NotificationEvent) {\r\n    if let Err(err) = self.ivars().events_tx.send(event) {\r\n      tracing::warn!(\"Failed to send event: {err}\");\r\n    }\r\n  }\r\n}\r\n\r\n/// Wrapper around `NSNotificationCenter` for registering event observers.\r\n#[derive(Debug)]\r\npub(crate) struct NotificationCenter {\r\n  inner: Retained<NSNotificationCenter>,\r\n}\r\n\r\nimpl NotificationCenter {\r\n  pub fn workspace_center() -> Self {\r\n    let center = NSWorkspace::sharedWorkspace().notificationCenter();\r\n\r\n    Self { inner: center }\r\n  }\r\n\r\n  pub fn default_center() -> Self {\r\n    let center = NSNotificationCenter::defaultCenter();\r\n\r\n    Self { inner: center }\r\n  }\r\n\r\n  pub unsafe fn add_observer(\r\n    &mut self,\r\n    notification_name: NotificationName,\r\n    observer: &NotificationObserver,\r\n    object: Option<&AnyObject>,\r\n  ) {\r\n    tracing::info!(\"Adding observer for {notification_name:?}.\");\r\n\r\n    self.inner.addObserver_selector_name_object(\r\n      observer,\r\n      sel!(onEvent:),\r\n      Some(notification_name.into()),\r\n      object,\r\n    );\r\n  }\r\n}\r\n\r\npub unsafe fn app_from_notification(\r\n  notification: &NSNotification,\r\n) -> Option<Retained<NSRunningApplication>> {\r\n  notification\r\n    .userInfo()?\r\n    .objectForKey(ns_string!(\"NSWorkspaceApplicationKey\"))\r\n    .map(|app| Retained::<AnyObject>::cast_unchecked(app))\r\n}\r\n"
  },
  {
    "path": "packages/wm-platform/src/platform_impl/macos/single_instance.rs",
    "content": "use std::{\r\n  fs::{self, File, TryLockError},\r\n  path::PathBuf,\r\n};\r\n\r\n/// Platform-specific implementation of [`SingleInstance`].\r\npub(crate) struct SingleInstance {\r\n  /// File that holds the lock.\r\n  ///\r\n  /// The lock is automatically released when the [`File`] is dropped.\r\n  _file: File,\r\n}\r\n\r\nimpl SingleInstance {\r\n  /// Implements [`SingleInstance::new`].\r\n  pub(crate) fn new() -> crate::Result<Self> {\r\n    let path = Self::lock_file_path()?;\r\n\r\n    if let Some(parent) = path.parent() {\r\n      fs::create_dir_all(parent).map_err(crate::Error::Io)?;\r\n    }\r\n\r\n    let file = File::create(&path).map_err(crate::Error::Io)?;\r\n\r\n    // Acquire exclusive file lock.\r\n    file.try_lock().map_err(|err| match err {\r\n      TryLockError::WouldBlock => crate::Error::Platform(\r\n        \"Another instance of the application is already running.\"\r\n          .to_string(),\r\n      ),\r\n      TryLockError::Error(io_err) => crate::Error::Io(io_err),\r\n    })?;\r\n\r\n    Ok(Self { _file: file })\r\n  }\r\n\r\n  /// Implements [`SingleInstance::is_running`].\r\n  #[must_use]\r\n  pub(crate) fn is_running() -> bool {\r\n    let Ok(file) = Self::lock_file_path()\r\n      .and_then(|path| File::open(&path).map_err(crate::Error::Io))\r\n    else {\r\n      return false;\r\n    };\r\n\r\n    // If `try_lock` fails with `WouldBlock`, the lock is held by another\r\n    // process.\r\n    file\r\n      .try_lock()\r\n      .is_err_and(|err| matches!(err, TryLockError::WouldBlock))\r\n  }\r\n\r\n  /// Returns the path to the lock file:\r\n  /// `~/Library/Application Support/glazewm/.lock`.\r\n  fn lock_file_path() -> crate::Result<PathBuf> {\r\n    let home = home::home_dir().ok_or_else(|| {\r\n      crate::Error::Platform(\r\n        \"Unable to determine home directory.\".to_string(),\r\n      )\r\n    })?;\r\n\r\n    Ok(home.join(\"Library/Application Support/glazewm/.lock\"))\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-platform/src/platform_impl/macos/window_listener.rs",
    "content": "use std::collections::HashMap;\r\n\r\nuse objc2::rc::Retained;\r\nuse objc2_app_kit::NSWorkspace;\r\nuse tokio::sync::mpsc;\r\n\r\nuse crate::{\r\n  platform_impl::{\r\n    self, Application, ApplicationObserver, NotificationCenter,\r\n    NotificationEvent, NotificationName, NotificationObserver, ProcessId,\r\n  },\r\n  Dispatcher, ThreadBound, WindowEvent,\r\n};\r\n\r\n/// Platform-specific implementation of [`WindowEventNotification`].\r\n#[derive(Clone, Debug)]\r\npub struct WindowEventNotificationInner {\r\n  /// Name of the notification (e.g. `AXWindowMoved`).\r\n  pub name: String,\r\n\r\n  /// Pointer to the `AXUIElement` that triggered the notification.\r\n  pub ax_element_ptr: *mut std::ffi::c_void,\r\n}\r\n\r\nunsafe impl Send for WindowEventNotificationInner {}\r\n\r\n/// Platform-specific implementation of [`WindowListener`].\r\n#[derive(Debug)]\r\npub(crate) struct WindowListener {\r\n  /// Workspace notification observer, bound to the main thread.\r\n  observer: Option<ThreadBound<Retained<NotificationObserver>>>,\r\n}\r\n\r\nimpl WindowListener {\r\n  /// Implements [`WindowListener::new`].\r\n  pub(crate) fn new(\r\n    events_tx: mpsc::UnboundedSender<WindowEvent>,\r\n    dispatcher: &Dispatcher,\r\n  ) -> crate::Result<Self> {\r\n    let observer = dispatcher\r\n      .dispatch_sync(|| Self::init(events_tx, dispatcher.clone()))??;\r\n\r\n    Ok(Self {\r\n      observer: Some(observer),\r\n    })\r\n  }\r\n\r\n  /// Implements [`WindowListener::terminate`].\r\n  pub(crate) fn terminate(&mut self) {\r\n    // On macOS 10.11+, observer subscriptions are cleaned up automatically\r\n    // without calling `removeObserver`.\r\n    // Ref: https://developer.apple.com/documentation/foundation/notificationcenter/removeobserver(_:name:object:)\r\n    //\r\n    // Dropping the `NotificationObserver` also drops its channel sender,\r\n    // causing the listener thread to exit.\r\n    self.observer.take();\r\n  }\r\n\r\n  fn init(\r\n    events_tx: mpsc::UnboundedSender<WindowEvent>,\r\n    dispatcher: Dispatcher,\r\n  ) -> crate::Result<ThreadBound<Retained<NotificationObserver>>> {\r\n    let (observer, events_rx) = NotificationObserver::new();\r\n\r\n    let workspace = NSWorkspace::sharedWorkspace();\r\n    let mut workspace_center = NotificationCenter::workspace_center();\r\n\r\n    for notification in [\r\n      NotificationName::WorkspaceActiveSpaceDidChange,\r\n      NotificationName::WorkspaceDidLaunchApplication,\r\n      NotificationName::WorkspaceDidActivateApplication,\r\n      NotificationName::WorkspaceDidTerminateApplication,\r\n      NotificationName::WorkspaceDidHideApplication,\r\n      NotificationName::WorkspaceDidUnhideApplication,\r\n    ] {\r\n      unsafe {\r\n        workspace_center.add_observer(\r\n          notification,\r\n          &observer,\r\n          Some(&workspace),\r\n        );\r\n      }\r\n    }\r\n\r\n    let running_apps = platform_impl::all_applications(&dispatcher)?;\r\n\r\n    // Create observers for all running applications.\r\n    let app_observers = running_apps\r\n      .into_iter()\r\n      .filter_map(|app| {\r\n        Self::create_app_observer(&app, events_tx.clone()).ok()\r\n      })\r\n      .collect::<Vec<_>>();\r\n\r\n    tracing::info!(\r\n      \"Registered observers for {} existing applications.\",\r\n      app_observers.len()\r\n    );\r\n\r\n    let dispatcher_clone = dispatcher.clone();\r\n    std::thread::spawn(move || {\r\n      Self::listen_workspace_events(\r\n        app_observers,\r\n        events_rx,\r\n        &events_tx,\r\n        &dispatcher_clone,\r\n      );\r\n    });\r\n\r\n    Ok(ThreadBound::new(observer, dispatcher))\r\n  }\r\n\r\n  fn listen_workspace_events(\r\n    app_observers: Vec<ApplicationObserver>,\r\n    mut events_rx: mpsc::UnboundedReceiver<NotificationEvent>,\r\n    events_tx: &mpsc::UnboundedSender<WindowEvent>,\r\n    dispatcher: &Dispatcher,\r\n  ) {\r\n    // Track window observers for each application by PID.\r\n    let mut app_observers: HashMap<ProcessId, ApplicationObserver> =\r\n      app_observers\r\n        .into_iter()\r\n        .map(|observer| (observer.pid, observer))\r\n        .collect();\r\n\r\n    // Loop exits when the sender is dropped in `Self::terminate`.\r\n    while let Some(event) = events_rx.blocking_recv() {\r\n      tracing::debug!(\"Received workspace event: {event:?}\");\r\n\r\n      match event {\r\n        NotificationEvent::WorkspaceDidLaunchApplication(running_app) => {\r\n          let events_tx = events_tx.clone();\r\n\r\n          let Ok(Ok(app_observer)) = dispatcher.dispatch_sync(|| {\r\n            let app = Application::new(running_app, dispatcher.clone());\r\n            if !app.should_observe() {\r\n              return Err(crate::Error::Platform(format!(\r\n                \"Skipped observer registration for PID {} (should ignore).\",\r\n                app.pid,\r\n              )));\r\n            }\r\n\r\n            ApplicationObserver::new(&app, events_tx.clone(), false)\r\n          }) else {\r\n            continue;\r\n          };\r\n\r\n          if app_observers.contains_key(&app_observer.pid) {\r\n            tracing::debug!(\r\n              \"Observer already exists for PID {}.\",\r\n              app_observer.pid\r\n            );\r\n            continue;\r\n          }\r\n\r\n          app_observers.insert(app_observer.pid, app_observer);\r\n        }\r\n        NotificationEvent::WorkspaceDidTerminateApplication(\r\n          running_app,\r\n        ) => {\r\n          let pid = running_app.processIdentifier();\r\n\r\n          if let Some(observer) = app_observers.remove(&pid) {\r\n            tracing::info!(\r\n              \"Removed window observer for terminated PID: {}\",\r\n              pid\r\n            );\r\n\r\n            observer.emit_all_windows_destroyed();\r\n          }\r\n        }\r\n        NotificationEvent::WorkspaceDidActivateApplication(\r\n          running_app,\r\n        ) => {\r\n          let Ok(Ok(Some(focused_window))) =\r\n            dispatcher.dispatch_sync(|| {\r\n              let app = Application::new(running_app, dispatcher.clone());\r\n              app.focused_window()\r\n            })\r\n          else {\r\n            continue;\r\n          };\r\n\r\n          let _ = events_tx.send(WindowEvent::Focused {\r\n            window: focused_window,\r\n            notification: crate::WindowEventNotification(None),\r\n          });\r\n        }\r\n        NotificationEvent::WorkspaceDidHideApplication(running_app) => {\r\n          if let Some(app_observer) =\r\n            app_observers.get(&running_app.processIdentifier())\r\n          {\r\n            app_observer.emit_all_windows_hidden();\r\n          }\r\n        }\r\n        NotificationEvent::WorkspaceDidUnhideApplication(running_app) => {\r\n          if let Some(app_observer) =\r\n            app_observers.get(&running_app.processIdentifier())\r\n          {\r\n            app_observer.emit_all_windows_shown();\r\n          }\r\n        }\r\n        _ => {}\r\n      }\r\n    }\r\n\r\n    tracing::debug!(\"Window listener thread exited.\");\r\n  }\r\n\r\n  fn create_app_observer(\r\n    app: &Application,\r\n    events_tx: mpsc::UnboundedSender<WindowEvent>,\r\n  ) -> crate::Result<ApplicationObserver> {\r\n    if !app.should_observe() {\r\n      return Err(crate::Error::Platform(format!(\r\n        \"Skipped observer registration for PID {} (should ignore).\",\r\n        app.pid,\r\n      )));\r\n    }\r\n\r\n    let app_observer_res = ApplicationObserver::new(app, events_tx, true);\r\n\r\n    if let Err(err) = &app_observer_res {\r\n      tracing::debug!(\r\n        \"Skipped observer registration for PID {}: {}\",\r\n        app.pid,\r\n        err\r\n      );\r\n    }\r\n\r\n    app_observer_res\r\n  }\r\n}\r\n\r\nimpl Drop for WindowListener {\r\n  fn drop(&mut self) {\r\n    self.terminate();\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-platform/src/platform_impl/mod.rs",
    "content": "#[cfg(target_os = \"windows\")]\r\n#[path = \"windows/mod.rs\"]\r\nmod platform;\r\n#[cfg(target_os = \"macos\")]\r\n#[path = \"macos/mod.rs\"]\r\nmod platform;\r\n\r\npub(crate) use platform::*;\r\n\r\n#[cfg(all(not(target_os = \"windows\"), not(target_os = \"macos\"),))]\r\ncompile_error!(\"The platform you're compiling for is not supported.\");\r\n"
  },
  {
    "path": "packages/wm-platform/src/platform_impl/windows/com.rs",
    "content": "use std::cell::RefCell;\r\n\r\nuse windows::{\r\n  core::{ComInterface, IUnknown, IUnknown_Vtbl, GUID, HRESULT},\r\n  Win32::{\r\n    System::Com::{\r\n      CoCreateInstance, CoInitializeEx, CoUninitialize, IServiceProvider,\r\n      CLSCTX_ALL, CLSCTX_SERVER, COINIT_APARTMENTTHREADED,\r\n    },\r\n    UI::Shell::{ITaskbarList2, TaskbarList},\r\n  },\r\n};\r\n\r\n/// COM class identifier (CLSID) for the Windows Shell that implements the\r\n/// `IServiceProvider` interface.\r\nconst CLSID_IMMERSIVE_SHELL: GUID =\r\n  GUID::from_u128(0xC2F03A33_21F5_47FA_B4BB_156362A2F239);\r\n\r\nthread_local! {\r\n  /// Manages per-thread COM initialization. COM must be initialized on each\r\n  /// thread that uses it, so we store this in thread-local storage to handle\r\n  /// the setup and cleanup automatically.\r\n  ///\r\n  /// Wrapped in `RefCell` to allow mutation via `COM_INIT.borrow_mut()`.\r\n  pub(crate) static COM_INIT: RefCell<ComInit> = RefCell::new(ComInit::new());\r\n}\r\n\r\npub(crate) struct ComInit {\r\n  service_provider: Option<IServiceProvider>,\r\n  application_view_collection: Option<IApplicationViewCollection>,\r\n  taskbar_list: Option<ITaskbarList2>,\r\n}\r\n\r\nimpl ComInit {\r\n  /// Initializes COM on the current thread with apartment threading model.\r\n  /// `COINIT_APARTMENTTHREADED` is required for shell COM objects.\r\n  ///\r\n  /// # Panics\r\n  ///\r\n  /// Panics if COM initialization fails. This is typically only possible\r\n  /// if COM is already initialized with an incompatible threading model.\r\n  #[must_use]\r\n  pub(crate) fn new() -> Self {\r\n    unsafe { CoInitializeEx(None, COINIT_APARTMENTTHREADED) }\r\n      .expect(\"Unable to initialize COM.\");\r\n\r\n    let service_provider = unsafe {\r\n      CoCreateInstance(&CLSID_IMMERSIVE_SHELL, None, CLSCTX_ALL)\r\n    }\r\n    .ok();\r\n\r\n    let application_view_collection = service_provider.as_ref().and_then(\r\n      |provider: &IServiceProvider| unsafe {\r\n        provider.QueryService(&IApplicationViewCollection::IID).ok()\r\n      },\r\n    );\r\n\r\n    let taskbar_list =\r\n      unsafe { CoCreateInstance(&TaskbarList, None, CLSCTX_SERVER) }.ok();\r\n\r\n    Self {\r\n      service_provider,\r\n      application_view_collection,\r\n      taskbar_list,\r\n    }\r\n  }\r\n\r\n  /// Returns an instance of `IApplicationViewCollection`.\r\n  pub(crate) fn application_view_collection(\r\n    &self,\r\n  ) -> crate::Result<&IApplicationViewCollection> {\r\n    self.application_view_collection.as_ref().ok_or_else(|| {\r\n      crate::Error::Platform(\r\n        \"Failed to query for `IApplicationViewCollection` instance.\"\r\n          .to_string(),\r\n      )\r\n    })\r\n  }\r\n\r\n  /// Returns an instance of `ITaskbarList2`.\r\n  pub(crate) fn taskbar_list(&self) -> crate::Result<&ITaskbarList2> {\r\n    self.taskbar_list.as_ref().ok_or_else(|| {\r\n      crate::Error::Platform(\r\n        \"Unable to create `ITaskbarList2` instance.\".to_string(),\r\n      )\r\n    })\r\n  }\r\n\r\n  /// Refreshes cached COM interfaces.\r\n  ///\r\n  /// Called automatically by `with_retry` when COM operations fail due to\r\n  /// stale interface pointers (e.g. after Explorer restarts).\r\n  pub(crate) fn refresh(&mut self) {\r\n    // Re-create the service provider.\r\n    self.service_provider = unsafe {\r\n      CoCreateInstance(&CLSID_IMMERSIVE_SHELL, None, CLSCTX_ALL)\r\n    }\r\n    .ok();\r\n\r\n    // Re-create the application view collection.\r\n    self.application_view_collection = self\r\n      .service_provider\r\n      .as_ref()\r\n      .and_then(|provider: &IServiceProvider| unsafe {\r\n        provider.QueryService(&IApplicationViewCollection::IID).ok()\r\n      });\r\n\r\n    // Re-create the taskbar list.\r\n    self.taskbar_list =\r\n      unsafe { CoCreateInstance(&TaskbarList, None, CLSCTX_SERVER) }.ok();\r\n  }\r\n\r\n  /// Executes a COM operation, refreshing interfaces on failure and\r\n  /// retrying once. Use this for operations that may fail due to stale\r\n  /// COM interfaces.\r\n  pub fn with_retry<T, F>(&mut self, op: F) -> crate::Result<T>\r\n  where\r\n    F: Fn(&Self) -> crate::Result<T>,\r\n  {\r\n    if let Ok(result) = op(self) {\r\n      Ok(result)\r\n    } else {\r\n      self.refresh();\r\n      op(self)\r\n    }\r\n  }\r\n}\r\n\r\nimpl Default for ComInit {\r\n  fn default() -> Self {\r\n    Self::new()\r\n  }\r\n}\r\n\r\nimpl Drop for ComInit {\r\n  fn drop(&mut self) {\r\n    // Explicitly drop COM interfaces first.\r\n    drop(self.taskbar_list.take());\r\n    drop(self.application_view_collection.take());\r\n    drop(self.service_provider.take());\r\n\r\n    unsafe { CoUninitialize() };\r\n  }\r\n}\r\n\r\n/// Undocumented COM interface for Windows shell functionality.\r\n///\r\n/// Note that filler methods are added to match the vtable layout.\r\n#[windows_interface::interface(\"1841c6d7-4f9d-42c0-af41-8747538f10e5\")]\r\npub unsafe trait IApplicationViewCollection: IUnknown {\r\n  pub unsafe fn m1(&self);\r\n  pub unsafe fn m2(&self);\r\n  pub unsafe fn m3(&self);\r\n  pub unsafe fn get_view_for_hwnd(\r\n    &self,\r\n    window: isize,\r\n    application_view: *mut Option<IApplicationView>,\r\n  ) -> HRESULT;\r\n}\r\n\r\n/// Undocumented COM interface for managing views in the Windows shell.\r\n///\r\n/// Note that filler methods are added to match the vtable layout.\r\n#[windows_interface::interface(\"372E1D3B-38D3-42E4-A15B-8AB2B178F513\")]\r\npub unsafe trait IApplicationView: IUnknown {\r\n  pub unsafe fn m1(&self);\r\n  pub unsafe fn m2(&self);\r\n  pub unsafe fn m3(&self);\r\n  pub unsafe fn m4(&self);\r\n  pub unsafe fn m5(&self);\r\n  pub unsafe fn m6(&self);\r\n  pub unsafe fn m7(&self);\r\n  pub unsafe fn m8(&self);\r\n  pub unsafe fn m9(&self);\r\n  pub unsafe fn set_cloak(\r\n    &self,\r\n    cloak_type: u32,\r\n    cloak_flag: i32,\r\n  ) -> HRESULT;\r\n}\r\n"
  },
  {
    "path": "packages/wm-platform/src/platform_impl/windows/display.rs",
    "content": "use windows::{\r\n  core::PCWSTR,\r\n  Win32::{\r\n    Foundation::{BOOL, LPARAM, POINT, RECT},\r\n    Graphics::Gdi::{\r\n      EnumDisplayDevicesW, EnumDisplayMonitors, EnumDisplaySettingsW,\r\n      GetMonitorInfoW, MonitorFromPoint, MonitorFromWindow, DEVMODEW,\r\n      DISPLAY_DEVICEW, DISPLAY_DEVICE_ACTIVE, ENUM_CURRENT_SETTINGS, HDC,\r\n      HMONITOR, MONITORINFO, MONITORINFOEXW, MONITOR_DEFAULTTONEAREST,\r\n      MONITOR_DEFAULTTOPRIMARY,\r\n    },\r\n    UI::{\r\n      HiDpi::{GetDpiForMonitor, MDT_EFFECTIVE_DPI},\r\n      WindowsAndMessaging::EDD_GET_DEVICE_INTERFACE_NAME,\r\n    },\r\n  },\r\n};\r\n\r\nuse crate::{\r\n  display::{\r\n    ConnectionState, DisplayDeviceId, DisplayId, MirroringState,\r\n    OutputTechnology,\r\n  },\r\n  Dispatcher, NativeWindow, Point, Rect,\r\n};\r\n\r\n/// Platform-specific implementation of [`Display`].\r\n#[derive(Clone, Debug)]\r\npub(crate) struct Display {\r\n  pub(crate) monitor_handle: isize,\r\n}\r\n\r\nimpl Display {\r\n  /// Creates an instance of `Display`.\r\n  #[must_use]\r\n  pub(crate) fn new(monitor_handle: isize) -> Self {\r\n    Self { monitor_handle }\r\n  }\r\n\r\n  /// Implements [`Display::id`].\r\n  pub(crate) fn id(&self) -> DisplayId {\r\n    DisplayId(self.monitor_handle)\r\n  }\r\n\r\n  /// Implements [`Display::name`].\r\n  pub(crate) fn name(&self) -> crate::Result<String> {\r\n    Ok(\r\n      String::from_utf16_lossy(&self.monitor_info_ex()?.szDevice)\r\n        .trim_end_matches('\\0')\r\n        .to_string(),\r\n    )\r\n  }\r\n\r\n  /// Implements [`Display::bounds`].\r\n  pub(crate) fn bounds(&self) -> crate::Result<Rect> {\r\n    let rc = self.monitor_info_ex()?.monitorInfo.rcMonitor;\r\n    Ok(Rect::from_ltrb(rc.left, rc.top, rc.right, rc.bottom))\r\n  }\r\n\r\n  /// Implements [`Display::working_area`].\r\n  pub(crate) fn working_area(&self) -> crate::Result<Rect> {\r\n    let rc = self.monitor_info_ex()?.monitorInfo.rcWork;\r\n    Ok(Rect::from_ltrb(rc.left, rc.top, rc.right, rc.bottom))\r\n  }\r\n\r\n  /// Implements [`Display::scale_factor`].\r\n  pub(crate) fn scale_factor(&self) -> crate::Result<f32> {\r\n    let dpi = self.dpi()?;\r\n    #[allow(clippy::cast_precision_loss)]\r\n    Ok(dpi as f32 / 96.0)\r\n  }\r\n\r\n  /// Implements [`Display::dpi`].\r\n  pub(crate) fn dpi(&self) -> crate::Result<u32> {\r\n    let mut dpi_x = u32::default();\r\n    let mut dpi_y = u32::default();\r\n\r\n    unsafe {\r\n      GetDpiForMonitor(\r\n        HMONITOR(self.monitor_handle),\r\n        MDT_EFFECTIVE_DPI,\r\n        &raw mut dpi_x,\r\n        &raw mut dpi_y,\r\n      )\r\n    }?;\r\n\r\n    // Arbitrarily choose the Y DPI.\r\n    Ok(dpi_y)\r\n  }\r\n\r\n  /// Implements [`Display::is_primary`].\r\n  pub(crate) fn is_primary(&self) -> crate::Result<bool> {\r\n    // Check for `MONITORINFOF_PRIMARY` flag (`0x1`).\r\n    Ok(self.monitor_info_ex()?.monitorInfo.dwFlags & 0x1 != 0)\r\n  }\r\n\r\n  /// Implements [`Display::devices`].\r\n  pub(crate) fn devices(\r\n    &self,\r\n  ) -> crate::Result<Vec<crate::DisplayDevice>> {\r\n    let monitor_info = self.monitor_info_ex()?;\r\n\r\n    let adapter_name = String::from_utf16_lossy(&monitor_info.szDevice)\r\n      .trim_end_matches('\\0')\r\n      .to_string();\r\n\r\n    // Get the display devices associated with the display's adapter.\r\n    let devices = (0u32..)\r\n      .map_while(|index| {\r\n        #[allow(clippy::cast_possible_truncation)]\r\n        let mut device = DISPLAY_DEVICEW {\r\n          cb: std::mem::size_of::<DISPLAY_DEVICEW>() as u32,\r\n          ..Default::default()\r\n        };\r\n\r\n        // When passing the `EDD_GET_DEVICE_INTERFACE_NAME` flag, the\r\n        // returned `DISPLAY_DEVICEW` will contain the device path in the\r\n        // `DeviceID` field.\r\n        unsafe {\r\n          EnumDisplayDevicesW(\r\n            PCWSTR(monitor_info.szDevice.as_ptr()),\r\n            index,\r\n            &raw mut device,\r\n            EDD_GET_DEVICE_INTERFACE_NAME,\r\n          )\r\n        }\r\n        .as_bool()\r\n        .then_some(device)\r\n      })\r\n      // Filter out any devices that are not active.\r\n      .filter(|device| device.StateFlags & DISPLAY_DEVICE_ACTIVE != 0)\r\n      .map(|device| {\r\n        DisplayDevice::new(adapter_name.clone(), &device.DeviceID).into()\r\n      })\r\n      .collect();\r\n\r\n    Ok(devices)\r\n  }\r\n\r\n  /// Implements [`Display::main_device`].\r\n  pub(crate) fn main_device(&self) -> crate::Result<crate::DisplayDevice> {\r\n    self\r\n      .devices()?\r\n      .into_iter()\r\n      .find(|device| {\r\n        matches!(\r\n          device.mirroring_state(),\r\n          Ok(None | Some(MirroringState::Source))\r\n        )\r\n      })\r\n      .ok_or(crate::Error::DisplayDeviceNotFound)\r\n  }\r\n\r\n  /// Implements [`DisplayExtWindows::hmonitor`].\r\n  pub(crate) fn hmonitor(&self) -> HMONITOR {\r\n    HMONITOR(self.monitor_handle)\r\n  }\r\n\r\n  /// Ref: <https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmonitorinfow>\r\n  fn monitor_info_ex(&self) -> crate::Result<MONITORINFOEXW> {\r\n    let mut monitor_info = MONITORINFOEXW {\r\n      monitorInfo: MONITORINFO {\r\n        #[allow(clippy::cast_possible_truncation)]\r\n        cbSize: std::mem::size_of::<MONITORINFOEXW>() as u32,\r\n        ..Default::default()\r\n      },\r\n      ..Default::default()\r\n    };\r\n\r\n    unsafe {\r\n      GetMonitorInfoW(\r\n        HMONITOR(self.monitor_handle),\r\n        std::ptr::from_mut(&mut monitor_info).cast(),\r\n      )\r\n    }\r\n    .ok()?;\r\n\r\n    Ok(monitor_info)\r\n  }\r\n}\r\n\r\nimpl From<Display> for crate::Display {\r\n  fn from(display: Display) -> Self {\r\n    crate::Display { inner: display }\r\n  }\r\n}\r\n\r\nimpl PartialEq for Display {\r\n  fn eq(&self, other: &Self) -> bool {\r\n    self.monitor_handle == other.monitor_handle\r\n  }\r\n}\r\n\r\nimpl Eq for Display {}\r\n\r\n/// Platform-specific implementation of [`DisplayDevice`].\r\n#[derive(Clone, Debug, PartialEq, Eq)]\r\npub(crate) struct DisplayDevice {\r\n  /// Display adapter name (e.g. `\\\\.\\DISPLAY1`).\r\n  adapter_name: String,\r\n\r\n  /// Device interface path (e.g.\r\n  /// `\\\\?\\DISPLAY#DEL40A3#5&1234abcd&0&UID256#\r\n  /// {e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}`).\r\n  pub(crate) device_path: Option<String>,\r\n}\r\n\r\nimpl DisplayDevice {\r\n  /// Creates an instance of `DisplayDevice`.\r\n  #[must_use]\r\n  pub(crate) fn new(adapter_name: String, device_path: &[u16]) -> Self {\r\n    // NOTE: This may be an empty string for virtual display devices.\r\n    let device_path = String::from_utf16_lossy(device_path)\r\n      .trim_end_matches('\\0')\r\n      .to_string();\r\n\r\n    // Check that the device path is valid. If not, set it to `None`.\r\n    let device_path =\r\n      (device_path.split('#').count() >= 4).then_some(device_path);\r\n\r\n    Self {\r\n      adapter_name,\r\n      device_path,\r\n    }\r\n  }\r\n\r\n  /// Implements [`DisplayDevice::id`].\r\n  pub(crate) fn id(&self) -> DisplayDeviceId {\r\n    // TODO: Display adapter name might not be unique.\r\n    DisplayDeviceId(\r\n      self\r\n        .hardware_id()\r\n        .unwrap_or_else(|| self.adapter_name.clone()),\r\n    )\r\n  }\r\n\r\n  /// Implements [`DisplayDevice::rotation`].\r\n  pub(crate) fn rotation(&self) -> crate::Result<f32> {\r\n    let orientation = unsafe {\r\n      self\r\n        .current_device_mode()?\r\n        .Anonymous1\r\n        .Anonymous2\r\n        .dmDisplayOrientation\r\n    };\r\n\r\n    Ok(match orientation.0 {\r\n      1 => 90.0,\r\n      2 => 180.0,\r\n      3 => 270.0,\r\n      _ => 0.0,\r\n    })\r\n  }\r\n\r\n  /// Implements [`DisplayDevice::refresh_rate`].\r\n  #[allow(clippy::unnecessary_wraps)]\r\n  pub(crate) fn refresh_rate(&self) -> crate::Result<f32> {\r\n    #[allow(clippy::cast_precision_loss)]\r\n    Ok(self.current_device_mode()?.dmDisplayFrequency as f32)\r\n  }\r\n\r\n  /// Implements [`DisplayDevice::is_builtin`].\r\n  #[allow(clippy::unnecessary_wraps, clippy::unused_self)]\r\n  pub(crate) fn is_builtin(&self) -> crate::Result<bool> {\r\n    // TODO: Use `DisplayConfigGetDeviceInfo` to determine whether the\r\n    // output technology is internal.\r\n    Ok(false)\r\n  }\r\n\r\n  /// Implements [`DisplayDevice::connection_state`].\r\n  #[allow(clippy::unnecessary_wraps, clippy::unused_self)]\r\n  pub(crate) fn connection_state(&self) -> crate::Result<ConnectionState> {\r\n    // TODO: Detect disconnected state.\r\n    Ok(ConnectionState::Active)\r\n  }\r\n\r\n  /// Implements [`DisplayDevice::mirroring_state`].\r\n  #[allow(clippy::unnecessary_wraps, clippy::unused_self)]\r\n  pub(crate) fn mirroring_state(\r\n    &self,\r\n  ) -> crate::Result<Option<MirroringState>> {\r\n    // TODO: Implement mirroring detection using\r\n    // `DisplayConfigGetDeviceInfo`.\r\n    Ok(None)\r\n  }\r\n\r\n  /// Implements [`DisplayDeviceExtWindows::hardware_id`].\r\n  pub(crate) fn hardware_id(&self) -> Option<String> {\r\n    self\r\n      .device_path\r\n      .as_deref()?\r\n      .split('#')\r\n      .nth(1)\r\n      .map(ToString::to_string)\r\n  }\r\n\r\n  /// Implements [`DisplayDeviceExtWindows::output_technology`].\r\n  #[allow(clippy::unnecessary_wraps, clippy::unused_self)]\r\n  pub(crate) fn output_technology(\r\n    &self,\r\n  ) -> crate::Result<Option<OutputTechnology>> {\r\n    // TODO: Use `DisplayConfigGetDeviceInfo` to get the output technology.\r\n    Ok(Some(OutputTechnology::Unknown))\r\n  }\r\n\r\n  /// Gets the current device mode.\r\n  fn current_device_mode(&self) -> crate::Result<DEVMODEW> {\r\n    #[allow(clippy::cast_possible_truncation)]\r\n    let mut device_mode = DEVMODEW {\r\n      dmSize: std::mem::size_of::<DEVMODEW>() as u16,\r\n      ..Default::default()\r\n    };\r\n\r\n    let wide_adapter_name = self\r\n      .adapter_name\r\n      .encode_utf16()\r\n      .chain(std::iter::once(0))\r\n      .collect::<Vec<_>>();\r\n\r\n    unsafe {\r\n      EnumDisplaySettingsW(\r\n        PCWSTR(wide_adapter_name.as_ptr()),\r\n        ENUM_CURRENT_SETTINGS,\r\n        &raw mut device_mode,\r\n      )\r\n    }\r\n    .ok()?;\r\n\r\n    Ok(device_mode)\r\n  }\r\n}\r\n\r\nimpl From<DisplayDevice> for crate::DisplayDevice {\r\n  fn from(device: DisplayDevice) -> Self {\r\n    crate::DisplayDevice { inner: device }\r\n  }\r\n}\r\n\r\n/// Implements [`Dispatcher::displays`].\r\npub(crate) fn all_displays(\r\n  _: &Dispatcher,\r\n) -> crate::Result<Vec<crate::Display>> {\r\n  let mut monitor_handles: Vec<isize> = Vec::new();\r\n\r\n  // Callback for `EnumDisplayMonitors` to collect monitor handles.\r\n  #[allow(clippy::items_after_statements)]\r\n  extern \"system\" fn monitor_enum_proc(\r\n    handle: HMONITOR,\r\n    _hdc: HDC,\r\n    _clip: *mut RECT,\r\n    data: LPARAM,\r\n  ) -> BOOL {\r\n    let handles = data.0 as *mut Vec<isize>;\r\n\r\n    // SAFETY: `data` is a valid pointer to the `monitor_handles` vec,\r\n    // which outlives this callback.\r\n    unsafe { (*handles).push(handle.0) };\r\n    true.into()\r\n  }\r\n\r\n  unsafe {\r\n    EnumDisplayMonitors(\r\n      HDC::default(),\r\n      None,\r\n      Some(monitor_enum_proc),\r\n      LPARAM(std::ptr::from_mut(&mut monitor_handles) as _),\r\n    )\r\n  }\r\n  .ok()?;\r\n\r\n  Ok(\r\n    monitor_handles\r\n      .into_iter()\r\n      .map(|handle| Display::new(handle).into())\r\n      .collect(),\r\n  )\r\n}\r\n\r\n/// Implements [`Dispatcher::display_devices`].\r\npub(crate) fn all_display_devices(\r\n  dispatcher: &Dispatcher,\r\n) -> crate::Result<Vec<crate::DisplayDevice>> {\r\n  all_displays(dispatcher)?\r\n    .into_iter()\r\n    .map(|display| display.devices())\r\n    .collect::<crate::Result<Vec<_>>>()\r\n    .map(|vecs| vecs.into_iter().flatten().collect())\r\n}\r\n\r\n/// Implements [`Dispatcher::display_from_point`].\r\n#[allow(clippy::unnecessary_wraps)]\r\npub(crate) fn display_from_point(\r\n  point: &Point,\r\n  _: &Dispatcher,\r\n) -> crate::Result<crate::Display> {\r\n  let handle = unsafe {\r\n    MonitorFromPoint(\r\n      POINT {\r\n        x: point.x,\r\n        y: point.y,\r\n      },\r\n      MONITOR_DEFAULTTOPRIMARY,\r\n    )\r\n  };\r\n\r\n  Ok(Display::new(handle.0).into())\r\n}\r\n\r\n/// Implements [`Dispatcher::primary_display`].\r\n#[allow(clippy::unnecessary_wraps)]\r\npub(crate) fn primary_display(\r\n  _: &Dispatcher,\r\n) -> crate::Result<crate::Display> {\r\n  let handle = unsafe {\r\n    MonitorFromPoint(POINT { x: 0, y: 0 }, MONITOR_DEFAULTTOPRIMARY)\r\n  };\r\n\r\n  Ok(Display::new(handle.0).into())\r\n}\r\n\r\n/// Implements [`Dispatcher::nearest_display`].\r\n#[allow(clippy::unnecessary_wraps)]\r\npub(crate) fn nearest_display(\r\n  native_window: &NativeWindow,\r\n  _: &Dispatcher,\r\n) -> crate::Result<crate::Display> {\r\n  let handle = unsafe {\r\n    MonitorFromWindow(native_window.inner.hwnd(), MONITOR_DEFAULTTONEAREST)\r\n  };\r\n\r\n  Ok(Display::new(handle.0).into())\r\n}\r\n"
  },
  {
    "path": "packages/wm-platform/src/platform_impl/windows/display_listener.rs",
    "content": "use std::sync::{\r\n  atomic::{AtomicBool, Ordering},\r\n  Arc,\r\n};\r\n\r\nuse tokio::sync::mpsc;\r\nuse tracing::warn;\r\nuse windows::Win32::UI::WindowsAndMessaging::{\r\n  DBT_DEVNODES_CHANGED, PBT_APMRESUMEAUTOMATIC, PBT_APMRESUMESUSPEND,\r\n  PBT_APMSUSPEND, SPI_SETWORKAREA, WM_DEVICECHANGE, WM_DISPLAYCHANGE,\r\n  WM_POWERBROADCAST, WM_SETTINGCHANGE,\r\n};\r\n\r\nuse crate::{Dispatcher, DispatcherExtWindows};\r\n\r\n/// Platform-specific implementation of [`DisplayListener`].\r\npub(crate) struct DisplayListener {\r\n  callback_id: Option<usize>,\r\n  dispatcher: Dispatcher,\r\n}\r\n\r\nimpl DisplayListener {\r\n  /// Implements [`DisplayListener::new`].\r\n  pub(crate) fn new(\r\n    event_tx: mpsc::UnboundedSender<()>,\r\n    dispatcher: &Dispatcher,\r\n  ) -> crate::Result<Self> {\r\n    let is_system_suspended = Arc::new(AtomicBool::new(false));\r\n\r\n    let callback_id = dispatcher.register_wndproc_callback(Box::new(\r\n      move |_hwnd, message, wparam, _lparam| {\r\n        match message {\r\n          WM_POWERBROADCAST => {\r\n            #[allow(clippy::cast_possible_truncation)]\r\n            match wparam as u32 {\r\n              // System is resuming from sleep/hibernation.\r\n              PBT_APMRESUMEAUTOMATIC | PBT_APMRESUMESUSPEND => {\r\n                is_system_suspended.store(false, Ordering::Relaxed);\r\n              }\r\n              // System is entering sleep/hibernation.\r\n              PBT_APMSUSPEND => {\r\n                is_system_suspended.store(true, Ordering::Relaxed);\r\n              }\r\n              _ => {}\r\n            }\r\n\r\n            Some(0)\r\n          }\r\n          WM_DISPLAYCHANGE | WM_SETTINGCHANGE | WM_DEVICECHANGE => {\r\n            let should_emit = {\r\n              // Ignore display change messages if the system hasn't fully\r\n              // resumed from sleep.\r\n              if is_system_suspended.load(Ordering::Relaxed) {\r\n                false\r\n              } else {\r\n                #[allow(clippy::cast_possible_truncation)]\r\n                match message {\r\n                  // Received when displays are connected and disconnected,\r\n                  // resolution changes, or arrangement changes.\r\n                  WM_DISPLAYCHANGE => true,\r\n                  // Received when the working area has changed. Fires when\r\n                  // the Windows taskbar is changed or an appbar is\r\n                  // registered or changed. 3rd-party apps like\r\n                  // ButteryTaskbar can trigger this message by calling\r\n                  // `SystemParametersInfo(SPI_SETWORKAREA, ...)`.\r\n                  WM_SETTINGCHANGE => wparam as u32 == SPI_SETWORKAREA.0,\r\n                  // Received when any device is connected or disconnected\r\n                  // (including non-display devices).\r\n                  // TODO: Check if this is actually needed. Previous C#\r\n                  // implementation did not use this.\r\n                  WM_DEVICECHANGE => wparam as u32 == DBT_DEVNODES_CHANGED,\r\n                  _ => unreachable!(),\r\n                }\r\n              }\r\n            };\r\n\r\n            if should_emit {\r\n              let _ = event_tx.send(());\r\n            }\r\n\r\n            Some(0)\r\n          }\r\n          _ => None,\r\n        }\r\n      },\r\n    ))?;\r\n\r\n    Ok(Self {\r\n      callback_id: Some(callback_id),\r\n      dispatcher: dispatcher.clone(),\r\n    })\r\n  }\r\n\r\n  /// Implements [`DisplayListener::terminate`].\r\n  pub(crate) fn terminate(&mut self) -> crate::Result<()> {\r\n    if let Some(id) = self.callback_id.take() {\r\n      self.dispatcher.deregister_wndproc_callback(id)?;\r\n    }\r\n\r\n    Ok(())\r\n  }\r\n}\r\n\r\nimpl Drop for DisplayListener {\r\n  fn drop(&mut self) {\r\n    if let Err(err) = self.terminate() {\r\n      warn!(\"Failed to terminate display listener: {}\", err);\r\n    }\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-platform/src/platform_impl/windows/event_loop.rs",
    "content": "use std::{\r\n  cell::RefCell,\r\n  collections::HashMap,\r\n  sync::{\r\n    atomic::{AtomicBool, AtomicUsize, Ordering},\r\n    Arc,\r\n  },\r\n  thread::{self, ThreadId},\r\n};\r\n\r\nuse windows::{\r\n  core::w,\r\n  Win32::{\r\n    Foundation::{HWND, LPARAM, LRESULT, WPARAM},\r\n    System::Threading::GetCurrentThreadId,\r\n    UI::WindowsAndMessaging::{\r\n      CreateWindowExW, DefWindowProcW, DestroyWindow, DispatchMessageW,\r\n      GetMessageW, PostMessageW, PostThreadMessageW, RegisterClassW,\r\n      RegisterWindowMessageW, SendMessageW, TranslateMessage, CS_HREDRAW,\r\n      CS_VREDRAW, CW_USEDEFAULT, MSG, WINDOW_EX_STYLE, WM_QUIT, WNDCLASSW,\r\n      WNDPROC, WS_OVERLAPPEDWINDOW,\r\n    },\r\n  },\r\n};\r\n\r\nuse crate::{DispatchFn, Dispatcher, WndProcCallback};\r\n\r\nthread_local! {\r\n  /// Custom message ID for dispatching closures to be run on the event\r\n  /// loop thread.\r\n  ///\r\n  /// `WPARAM` contains a `Box<Box<dyn FnOnce()>>` that must be retrieved\r\n  /// with `Box::from_raw`. `LPARAM` is unused.\r\n  ///\r\n  /// This message is sent using `PostMessageW` and handled in\r\n  /// [`EventLoop::window_proc`].\r\n  static WM_DISPATCH_CALLBACK: u32 = unsafe { RegisterWindowMessageW(w!(\"GlazeWM:Dispatch\")) };\r\n\r\n  /// Registered callbacks that pre-process messages in the event loop's\r\n  /// window procedure.\r\n  ///\r\n  /// Keyed by a unique callback ID for later deregistration.\r\n  static WNDPROC_CALLBACKS: RefCell<HashMap<usize, Box<WndProcCallback>>> =\r\n    RefCell::new(HashMap::new());\r\n}\r\n\r\n/// Source for dispatching callbacks onto the event loop thread.\r\n#[derive(Clone)]\r\npub(crate) struct EventLoopSource {\r\n  pub(crate) message_window_handle: isize,\r\n  pub(crate) thread_id: ThreadId,\r\n  os_thread_id: u32,\r\n  next_callback_id: Arc<AtomicUsize>,\r\n}\r\n\r\nimpl EventLoopSource {\r\n  pub(crate) fn send_dispatch_async<F>(\r\n    &self,\r\n    dispatch_fn: F,\r\n  ) -> crate::Result<()>\r\n  where\r\n    F: FnOnce() + Send + 'static,\r\n  {\r\n    // Double box the callback to avoid `STATUS_ACCESS_VIOLATION` on\r\n    // Windows. Ref: https://github.com/tauri-apps/tao/blob/dev/src/platform_impl/windows/event_loop.rs#L596\r\n    let dispatch_fn: Box<Box<DispatchFn>> =\r\n      Box::new(Box::new(dispatch_fn));\r\n\r\n    // Leak to a raw pointer to then be passed as `WPARAM` in the message.\r\n    let callback_ptr = Box::into_raw(dispatch_fn);\r\n\r\n    unsafe {\r\n      if PostMessageW(\r\n        HWND(self.message_window_handle),\r\n        WM_DISPATCH_CALLBACK.with(|v| *v),\r\n        WPARAM(callback_ptr as _),\r\n        LPARAM(0),\r\n      )\r\n      .is_ok()\r\n      {\r\n        Ok(())\r\n      } else {\r\n        // If `PostMessage` fails, we need to clean up the callback.\r\n        let _ = Box::from_raw(callback_ptr);\r\n        Err(crate::Error::WindowMessage(\r\n          \"Failed to post message\".to_string(),\r\n        ))\r\n      }\r\n    }\r\n  }\r\n\r\n  #[allow(clippy::unnecessary_wraps)]\r\n  pub(crate) fn send_dispatch_sync<F>(\r\n    &self,\r\n    dispatch_fn: F,\r\n  ) -> crate::Result<()>\r\n  where\r\n    F: FnOnce() + Send,\r\n  {\r\n    let dispatch_fn: Box<Box<dyn FnOnce() + Send>> =\r\n      Box::new(Box::new(dispatch_fn));\r\n    let callback_ptr = Box::into_raw(dispatch_fn);\r\n\r\n    // `SendMessageW` blocks the calling thread until the window procedure\r\n    // processes the message and executes the closure. This guarantees the\r\n    // closure's lifetime remains valid.\r\n    unsafe {\r\n      SendMessageW(\r\n        HWND(self.message_window_handle),\r\n        WM_DISPATCH_CALLBACK.with(|v| *v),\r\n        WPARAM(callback_ptr as _),\r\n        LPARAM(0),\r\n      );\r\n    }\r\n\r\n    Ok(())\r\n  }\r\n\r\n  pub(crate) fn send_stop(&self) -> crate::Result<()> {\r\n    unsafe {\r\n      PostThreadMessageW(self.os_thread_id, WM_QUIT, WPARAM(0), LPARAM(0))\r\n    }\r\n    .map_err(|_| {\r\n      crate::Error::WindowMessage(\r\n        \"Failed to post quit message\".to_string(),\r\n      )\r\n    })\r\n  }\r\n\r\n  pub(crate) fn register_wndproc_callback(\r\n    &self,\r\n    callback: Box<WndProcCallback>,\r\n  ) -> crate::Result<usize> {\r\n    let id = self.next_callback_id.fetch_add(1, Ordering::Relaxed);\r\n\r\n    // The callback is installed asynchronously on the event loop thread.\r\n    self.send_dispatch_async(move || {\r\n      WNDPROC_CALLBACKS.with(|cbs| {\r\n        cbs.borrow_mut().insert(id, callback);\r\n      });\r\n    })?;\r\n\r\n    Ok(id)\r\n  }\r\n\r\n  pub(crate) fn deregister_wndproc_callback(\r\n    &self,\r\n    id: usize,\r\n  ) -> crate::Result<()> {\r\n    self.send_dispatch_async(move || {\r\n      WNDPROC_CALLBACKS.with(|cbs| {\r\n        cbs.borrow_mut().remove(&id);\r\n      });\r\n    })\r\n  }\r\n}\r\n\r\n/// Platform-specific implementation of [`EventLoop`].\r\npub(crate) struct EventLoop {\r\n  source: EventLoopSource,\r\n}\r\n\r\nimpl EventLoop {\r\n  /// Implements [`EventLoop::new`].\r\n  pub(crate) fn new() -> crate::Result<(Self, Dispatcher)> {\r\n    // Create a hidden message window on the current thread.\r\n    let window_handle =\r\n      Self::create_message_window(Some(Self::window_proc))?;\r\n\r\n    let source = EventLoopSource {\r\n      message_window_handle: window_handle,\r\n      thread_id: thread::current().id(),\r\n      os_thread_id: unsafe { GetCurrentThreadId() },\r\n      next_callback_id: Arc::new(AtomicUsize::new(0)),\r\n    };\r\n\r\n    let stopped = Arc::new(AtomicBool::new(false));\r\n    let dispatcher = Dispatcher::new(Some(source.clone()), stopped);\r\n\r\n    Ok((Self { source }, dispatcher))\r\n  }\r\n\r\n  /// Implements [`EventLoop::run`].\r\n  pub(crate) fn run(&self) -> crate::Result<()> {\r\n    tracing::info!(\"Starting event loop.\");\r\n    let mut msg = MSG::default();\r\n\r\n    // Start the message loop. Blocks until `WM_QUIT` is received.\r\n    loop {\r\n      if unsafe { GetMessageW(&raw mut msg, None, 0, 0) }.as_bool() {\r\n        unsafe {\r\n          TranslateMessage(&raw const msg);\r\n          DispatchMessageW(&raw const msg);\r\n        }\r\n      } else {\r\n        break;\r\n      }\r\n    }\r\n\r\n    tracing::info!(\"Event loop thread exiting.\");\r\n    unsafe { DestroyWindow(HWND(self.source.message_window_handle)) }?;\r\n\r\n    Ok(())\r\n  }\r\n\r\n  /// Shuts down the event loop gracefully.\r\n  pub(crate) fn shutdown(&mut self) -> crate::Result<()> {\r\n    tracing::info!(\"Shutting down event loop.\");\r\n    self.source.send_stop()?;\r\n\r\n    Ok(())\r\n  }\r\n\r\n  /// Creates a hidden message window.\r\n  ///\r\n  /// Returns a handle to the created window.\r\n  fn create_message_window(\r\n    window_procedure: WNDPROC,\r\n  ) -> crate::Result<isize> {\r\n    let wnd_class = WNDCLASSW {\r\n      lpszClassName: w!(\"MessageWindow\"),\r\n      style: CS_HREDRAW | CS_VREDRAW,\r\n      lpfnWndProc: window_procedure,\r\n      ..Default::default()\r\n    };\r\n\r\n    unsafe { RegisterClassW(&raw const wnd_class) };\r\n\r\n    let handle = unsafe {\r\n      CreateWindowExW(\r\n        WINDOW_EX_STYLE::default(),\r\n        w!(\"MessageWindow\"),\r\n        w!(\"MessageWindow\"),\r\n        WS_OVERLAPPEDWINDOW,\r\n        CW_USEDEFAULT,\r\n        CW_USEDEFAULT,\r\n        CW_USEDEFAULT,\r\n        CW_USEDEFAULT,\r\n        None,\r\n        None,\r\n        wnd_class.hInstance,\r\n        None,\r\n      )\r\n    };\r\n\r\n    if handle.0 == 0 {\r\n      return Err(crate::Error::Platform(\r\n        \"Creation of message window failed.\".to_string(),\r\n      ));\r\n    }\r\n\r\n    Ok(handle.0)\r\n  }\r\n\r\n  /// Window procedure for handling messages.\r\n  unsafe extern \"system\" fn window_proc(\r\n    hwnd: HWND,\r\n    msg: u32,\r\n    wparam: WPARAM,\r\n    lparam: LPARAM,\r\n  ) -> LRESULT {\r\n    // Handle dispatch callbacks first.\r\n    if msg == WM_DISPATCH_CALLBACK.with(|v| *v) {\r\n      // Convert the `WPARAM` fn pointer back to a double-boxed function.\r\n      let dispatch_fn: Box<Box<dyn FnOnce() + Send>> =\r\n        Box::from_raw(wparam.0 as *mut _);\r\n      dispatch_fn();\r\n      return LRESULT(0);\r\n    }\r\n\r\n    // Let registered callbacks pre-process the message.\r\n    let handled = WNDPROC_CALLBACKS.with(|cbs| {\r\n      for callback in cbs.borrow().values() {\r\n        if let Some(result) = callback(hwnd.0, msg, wparam.0, lparam.0) {\r\n          return Some(LRESULT(result));\r\n        }\r\n      }\r\n      None\r\n    });\r\n\r\n    if let Some(result) = handled {\r\n      return result;\r\n    }\r\n\r\n    // `WM_QUIT` is handled by the message loop and should be forwarded\r\n    // along with other messages.\r\n    DefWindowProcW(hwnd, msg, wparam, lparam)\r\n  }\r\n}\r\n\r\nimpl Drop for EventLoop {\r\n  fn drop(&mut self) {\r\n    if let Err(err) = self.shutdown() {\r\n      tracing::warn!(\"Failed to shut down event loop: {err}\");\r\n    }\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-platform/src/platform_impl/windows/keyboard_hook.rs",
    "content": "use std::cell::Cell;\r\n\r\nuse windows::Win32::{\r\n  Foundation::{HINSTANCE, LPARAM, LRESULT, WPARAM},\r\n  UI::{\r\n    Input::KeyboardAndMouse::{\r\n      GetKeyState, VK_LCONTROL, VK_LMENU, VK_LSHIFT, VK_LWIN, VK_RCONTROL,\r\n      VK_RMENU, VK_RSHIFT, VK_RWIN,\r\n    },\r\n    WindowsAndMessaging::{\r\n      CallNextHookEx, SetWindowsHookExW, UnhookWindowsHookEx, HHOOK,\r\n      KBDLLHOOKSTRUCT, WH_KEYBOARD_LL, WM_KEYDOWN, WM_SYSKEYDOWN,\r\n    },\r\n  },\r\n};\r\n\r\nuse crate::{Dispatcher, Key, KeyCode};\r\n\r\n/// Callback stored in [`HOOK`] for intercepting keyboard events.\r\ntype HookCallback = Box<dyn Fn(KeyEvent) -> bool>;\r\n\r\nthread_local! {\r\n  /// Stores the hook callback for the current thread.\r\n  ///\r\n  /// The hook callback is called for every keyboard event and returns\r\n  /// `true` if the event should be intercepted.\r\n  static HOOK: Cell<Option<HookCallback>> = Cell::default();\r\n}\r\n\r\n/// A key event received from the keyboard hook.\r\n#[derive(Clone, Debug)]\r\npub struct KeyEvent {\r\n  /// The key that was pressed or released.\r\n  pub key: Key,\r\n\r\n  /// Key code that generated this event.\r\n  #[allow(dead_code)]\r\n  pub key_code: KeyCode,\r\n\r\n  /// Whether the event is for a key press or release.\r\n  pub is_keypress: bool,\r\n}\r\n\r\nimpl KeyEvent {\r\n  /// Gets whether the specified key is currently pressed.\r\n  #[allow(clippy::unused_self)]\r\n  pub fn is_key_down(&self, key: Key) -> bool {\r\n    match key {\r\n      Key::Cmd | Key::Win => {\r\n        Self::is_key_down_raw(VK_LWIN.0)\r\n          || Self::is_key_down_raw(VK_RWIN.0)\r\n      }\r\n      Key::Alt => {\r\n        Self::is_key_down_raw(VK_LMENU.0)\r\n          || Self::is_key_down_raw(VK_RMENU.0)\r\n      }\r\n      Key::Ctrl => {\r\n        Self::is_key_down_raw(VK_LCONTROL.0)\r\n          || Self::is_key_down_raw(VK_RCONTROL.0)\r\n      }\r\n      Key::Shift => {\r\n        Self::is_key_down_raw(VK_LSHIFT.0)\r\n          || Self::is_key_down_raw(VK_RSHIFT.0)\r\n      }\r\n      _ => {\r\n        if let Ok(key_code) = KeyCode::try_from(key) {\r\n          Self::is_key_down_raw(key_code.0)\r\n        } else {\r\n          false\r\n        }\r\n      }\r\n    }\r\n  }\r\n\r\n  /// Gets whether the specified key is currently down using the raw key\r\n  /// code.\r\n  fn is_key_down_raw(key: u16) -> bool {\r\n    unsafe { (GetKeyState(key.into()) & 0x80) == 0x80 }\r\n  }\r\n}\r\n\r\n/// A system-wide low-level keyboard hook.\r\n#[derive(Debug)]\r\npub struct KeyboardHook {\r\n  handle: HHOOK,\r\n  dispatcher: Dispatcher,\r\n}\r\n\r\nimpl KeyboardHook {\r\n  /// Creates an instance of `KeyboardHook`.\r\n  ///\r\n  /// The callback is called for every keyboard event and returns `true` if\r\n  /// the event should be intercepted.\r\n  ///\r\n  /// # Panics\r\n  ///\r\n  /// Panics when attempting to register multiple hooks on the dispatcher's\r\n  /// thread.\r\n  pub fn new<F>(\r\n    callback: F,\r\n    dispatcher: &Dispatcher,\r\n  ) -> crate::Result<Self>\r\n  where\r\n    F: Fn(KeyEvent) -> bool + Send + Sync + 'static,\r\n  {\r\n    let handle = dispatcher.dispatch_sync(move || {\r\n      HOOK.with(|state| {\r\n        assert!(\r\n          state.take().is_none(),\r\n          \"Only one keyboard hook can be registered on the dispatcher's thread.\"\r\n        );\r\n\r\n        state.set(Some(Box::new(callback)));\r\n      });\r\n\r\n      unsafe {\r\n        SetWindowsHookExW(\r\n          WH_KEYBOARD_LL,\r\n          Some(Self::hook_proc),\r\n          HINSTANCE::default(),\r\n          0,\r\n        )\r\n      }\r\n    })??;\r\n\r\n    Ok(Self {\r\n      handle,\r\n      dispatcher: dispatcher.clone(),\r\n    })\r\n  }\r\n\r\n  /// Terminates the keyboard hook by unregistering it.\r\n  pub fn terminate(&mut self) -> crate::Result<()> {\r\n    unsafe { UnhookWindowsHookEx(self.handle) }?;\r\n\r\n    // Dispatch cleanup to the event loop thread since the callback\r\n    // is stored in a thread-local on that thread.\r\n    let _ = self.dispatcher.dispatch_async(|| {\r\n      HOOK.with(|state| {\r\n        state.take();\r\n      });\r\n    });\r\n\r\n    Ok(())\r\n  }\r\n\r\n  /// Hook procedure for keyboard events.\r\n  ///\r\n  /// For use with `SetWindowsHookExW`.\r\n  extern \"system\" fn hook_proc(\r\n    code: i32,\r\n    wparam: WPARAM,\r\n    lparam: LPARAM,\r\n  ) -> LRESULT {\r\n    // If the code is less than zero, the hook procedure must pass the hook\r\n    // notification directly to other applications.\r\n    if code != 0 {\r\n      return unsafe { CallNextHookEx(None, code, wparam, lparam) };\r\n    }\r\n\r\n    // Get struct with the keyboard input event.\r\n    let input = unsafe { *(lparam.0 as *const KBDLLHOOKSTRUCT) };\r\n\r\n    #[allow(clippy::cast_possible_truncation)]\r\n    let key_code = KeyCode(input.vkCode as u16);\r\n    #[allow(clippy::cast_possible_truncation)]\r\n    let is_keypress =\r\n      wparam.0 as u32 == WM_KEYDOWN || wparam.0 as u32 == WM_SYSKEYDOWN;\r\n\r\n    let Ok(key) = Key::try_from(key_code) else {\r\n      return unsafe { CallNextHookEx(None, code, wparam, lparam) };\r\n    };\r\n\r\n    let key_event = KeyEvent {\r\n      key,\r\n      key_code,\r\n      is_keypress,\r\n    };\r\n\r\n    let should_intercept = HOOK.with(|state| {\r\n      if let Some(callback) = state.take() {\r\n        let result = callback(key_event);\r\n        state.set(Some(callback));\r\n        result\r\n      } else {\r\n        false\r\n      }\r\n    });\r\n\r\n    if should_intercept {\r\n      return LRESULT(1);\r\n    }\r\n\r\n    unsafe { CallNextHookEx(None, code, wparam, lparam) }\r\n  }\r\n}\r\n\r\nimpl Drop for KeyboardHook {\r\n  fn drop(&mut self) {\r\n    let _ = self.terminate();\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-platform/src/platform_impl/windows/mod.rs",
    "content": "pub(crate) mod com;\r\nmod display;\r\nmod display_listener;\r\nmod event_loop;\r\nmod keyboard_hook;\r\nmod mouse_listener;\r\nmod native_window;\r\nmod single_instance;\r\nmod window_listener;\r\n\r\npub(crate) use display::*;\r\npub(crate) use display_listener::*;\r\npub(crate) use event_loop::*;\r\npub(crate) use keyboard_hook::*;\r\npub(crate) use mouse_listener::*;\r\npub(crate) use native_window::*;\r\npub(crate) use single_instance::*;\r\npub(crate) use window_listener::*;\r\n"
  },
  {
    "path": "packages/wm-platform/src/platform_impl/windows/mouse_listener.rs",
    "content": "use std::{\r\n  sync::{Arc, Mutex},\r\n  time::{Duration, Instant},\r\n};\r\n\r\nuse tokio::sync::mpsc;\r\nuse windows::Win32::{\r\n  Devices::HumanInterfaceDevice::{\r\n    HID_USAGE_GENERIC_MOUSE, HID_USAGE_PAGE_GENERIC,\r\n  },\r\n  Foundation::{HWND, POINT},\r\n  UI::{\r\n    Input::{\r\n      GetRawInputData, RegisterRawInputDevices, HRAWINPUT, RAWINPUT,\r\n      RAWINPUTDEVICE, RAWINPUTHEADER, RIDEV_INPUTSINK, RIDEV_REMOVE,\r\n      RID_INPUT, RIM_TYPEMOUSE,\r\n    },\r\n    WindowsAndMessaging::{\r\n      GetCursorPos, RI_MOUSE_LEFT_BUTTON_DOWN, RI_MOUSE_LEFT_BUTTON_UP,\r\n      RI_MOUSE_RIGHT_BUTTON_DOWN, RI_MOUSE_RIGHT_BUTTON_UP, WM_INPUT,\r\n    },\r\n  },\r\n};\r\n\r\nuse super::FOREGROUND_INPUT_IDENTIFIER;\r\nuse crate::{\r\n  mouse_listener::MouseEventKind,\r\n  platform_event::{MouseButton, MouseEvent, PressedButtons},\r\n  Dispatcher, DispatcherExtWindows, Point,\r\n};\r\n\r\n/// Data shared with the window procedure callback.\r\nstruct CallbackData {\r\n  event_tx: mpsc::UnboundedSender<MouseEvent>,\r\n\r\n  /// Pressed button state tracked from events.\r\n  pressed: PressedButtons,\r\n\r\n  /// Timestamp of the last emitted `Move` event for throttling.\r\n  last_move_emission: Option<Instant>,\r\n}\r\n\r\n/// Platform-specific implementation of [`MouseListener`].\r\npub(crate) struct MouseListener {\r\n  callback_id: Option<usize>,\r\n  callback_data: Arc<Mutex<CallbackData>>,\r\n  dispatcher: Dispatcher,\r\n}\r\n\r\nimpl MouseListener {\r\n  /// Implements [`MouseListener::new`].\r\n  pub(crate) fn new(\r\n    enabled_events: &[MouseEventKind],\r\n    event_tx: mpsc::UnboundedSender<MouseEvent>,\r\n    dispatcher: &Dispatcher,\r\n  ) -> crate::Result<Self> {\r\n    let callback_data = Arc::new(Mutex::new(CallbackData {\r\n      event_tx,\r\n      pressed: PressedButtons::default(),\r\n      last_move_emission: None,\r\n    }));\r\n\r\n    let callback_id = Self::register_callback(\r\n      enabled_events,\r\n      Arc::clone(&callback_data),\r\n      dispatcher,\r\n    )?;\r\n\r\n    Ok(Self {\r\n      callback_id: Some(callback_id),\r\n      dispatcher: dispatcher.clone(),\r\n      callback_data,\r\n    })\r\n  }\r\n\r\n  /// Implements [`MouseListener::enable`].\r\n  pub(crate) fn enable(&mut self, enabled: bool) -> crate::Result<()> {\r\n    if self.callback_id.is_some() {\r\n      let handle = self.dispatcher.message_window_handle();\r\n      self.dispatcher.dispatch_sync(move || {\r\n        Self::enable_raw_input(handle, enabled)\r\n      })??;\r\n    }\r\n\r\n    Ok(())\r\n  }\r\n\r\n  /// Implements [`MouseListener::set_enabled_events`].\r\n  pub(crate) fn set_enabled_events(\r\n    &mut self,\r\n    enabled_events: &[MouseEventKind],\r\n  ) -> crate::Result<()> {\r\n    let _ = self.terminate();\r\n\r\n    let callback_id = Self::register_callback(\r\n      enabled_events,\r\n      Arc::clone(&self.callback_data),\r\n      &self.dispatcher,\r\n    )?;\r\n\r\n    self.callback_id = Some(callback_id);\r\n\r\n    Ok(())\r\n  }\r\n\r\n  /// Implements [`MouseListener::terminate`].\r\n  pub(crate) fn terminate(&mut self) -> crate::Result<()> {\r\n    self.enable(false)?;\r\n\r\n    if let Some(id) = self.callback_id.take() {\r\n      self.dispatcher.deregister_wndproc_callback(id)?;\r\n    }\r\n\r\n    Ok(())\r\n  }\r\n\r\n  /// Registers a window procedure callback for `WM_INPUT` and enables raw\r\n  /// input.\r\n  ///\r\n  /// Returns the ID of the created callback.\r\n  fn register_callback(\r\n    enabled_events: &[MouseEventKind],\r\n    callback_data: Arc<Mutex<CallbackData>>,\r\n    dispatcher: &Dispatcher,\r\n  ) -> crate::Result<usize> {\r\n    let enabled_events: Arc<[MouseEventKind]> =\r\n      Arc::from(enabled_events.to_vec().into_boxed_slice());\r\n\r\n    let callback_id = dispatcher.register_wndproc_callback(Box::new(\r\n      move |_hwnd, msg, _wparam, lparam| {\r\n        if msg != WM_INPUT {\r\n          return None;\r\n        }\r\n\r\n        let mut callback_data = callback_data\r\n          .lock()\r\n          .unwrap_or_else(std::sync::PoisonError::into_inner);\r\n\r\n        if let Err(err) = Self::handle_wm_input(\r\n          lparam,\r\n          &enabled_events,\r\n          &mut callback_data,\r\n        ) {\r\n          tracing::warn!(\"Failed to handle WM_INPUT message: {}\", err);\r\n        }\r\n\r\n        Some(0)\r\n      },\r\n    ))?;\r\n\r\n    // Register raw input devices, which will then deliver `WM_INPUT`\r\n    // messages to the event loop's message window.\r\n    let handle = dispatcher.message_window_handle();\r\n    dispatcher\r\n      .dispatch_sync(move || Self::enable_raw_input(handle, true))??;\r\n\r\n    Ok(callback_id)\r\n  }\r\n\r\n  /// Processes a `WM_INPUT` message, extracting raw input data and\r\n  /// sending the appropriate [`MouseEvent`] on the channel.\r\n  fn handle_wm_input(\r\n    lparam: isize,\r\n    enabled_events: &[MouseEventKind],\r\n    callback_data: &mut CallbackData,\r\n  ) -> crate::Result<()> {\r\n    let mut raw_input: RAWINPUT = unsafe { std::mem::zeroed() };\r\n    #[allow(clippy::cast_possible_truncation)]\r\n    let mut raw_input_size = std::mem::size_of::<RAWINPUT>() as u32;\r\n\r\n    let res_size = unsafe {\r\n      #[allow(clippy::cast_possible_truncation)]\r\n      GetRawInputData(\r\n        HRAWINPUT(lparam),\r\n        RID_INPUT,\r\n        Some(std::ptr::from_mut(&mut raw_input).cast()),\r\n        &raw mut raw_input_size,\r\n        std::mem::size_of::<RAWINPUTHEADER>() as u32,\r\n      )\r\n    };\r\n\r\n    // Ignore if input is invalid or not a mouse event. Inputs from our own\r\n    // process are also ignored, since `NativeWindow::focus` simulates\r\n    // mouse input.\r\n    #[allow(clippy::cast_possible_truncation)]\r\n    if res_size == 0\r\n      || raw_input_size == u32::MAX\r\n      || raw_input.header.dwType != RIM_TYPEMOUSE.0\r\n      || unsafe { raw_input.data.mouse.ulExtraInformation } as u32\r\n        == FOREGROUND_INPUT_IDENTIFIER\r\n    {\r\n      return Ok(());\r\n    }\r\n\r\n    // Map button flags to a `MouseEventKind`.\r\n    let event_kind = {\r\n      let button_flags = u32::from(unsafe {\r\n        raw_input.data.mouse.Anonymous.Anonymous.usButtonFlags\r\n      });\r\n\r\n      // Button flags indicate a transition in mouse button state.\r\n      // Ref: https://learn.microsoft.com/en-us/windows/win32/api/ntddmou/ns-ntddmou-mouse_input_data#members\r\n      if button_flags & RI_MOUSE_LEFT_BUTTON_DOWN != 0 {\r\n        MouseEventKind::LeftButtonDown\r\n      } else if button_flags & RI_MOUSE_LEFT_BUTTON_UP != 0 {\r\n        MouseEventKind::LeftButtonUp\r\n      } else if button_flags & RI_MOUSE_RIGHT_BUTTON_DOWN != 0 {\r\n        MouseEventKind::RightButtonDown\r\n      } else if button_flags & RI_MOUSE_RIGHT_BUTTON_UP != 0 {\r\n        MouseEventKind::RightButtonUp\r\n      } else {\r\n        MouseEventKind::Move\r\n      }\r\n    };\r\n\r\n    if !enabled_events.contains(&event_kind) {\r\n      return Ok(());\r\n    }\r\n\r\n    // Throttle mouse move events so that there's a minimum of 50ms between\r\n    // each emission. State change events (button down/up) always get\r\n    // emitted.\r\n    let should_emit = match event_kind {\r\n      MouseEventKind::Move => {\r\n        callback_data.last_move_emission.is_none_or(|timestamp| {\r\n          timestamp.elapsed() >= Duration::from_millis(50)\r\n        })\r\n      }\r\n      _ => true,\r\n    };\r\n\r\n    if !should_emit {\r\n      return Ok(());\r\n    }\r\n\r\n    callback_data.pressed.update(event_kind);\r\n\r\n    let mouse_event = match event_kind {\r\n      MouseEventKind::LeftButtonDown => MouseEvent::ButtonDown {\r\n        position: Self::cursor_pos()?,\r\n        button: MouseButton::Left,\r\n        pressed_buttons: callback_data.pressed,\r\n      },\r\n      MouseEventKind::LeftButtonUp => MouseEvent::ButtonUp {\r\n        position: Self::cursor_pos()?,\r\n        button: MouseButton::Left,\r\n        pressed_buttons: callback_data.pressed,\r\n      },\r\n      MouseEventKind::RightButtonDown => MouseEvent::ButtonDown {\r\n        position: Self::cursor_pos()?,\r\n        button: MouseButton::Right,\r\n        pressed_buttons: callback_data.pressed,\r\n      },\r\n      MouseEventKind::RightButtonUp => MouseEvent::ButtonUp {\r\n        position: Self::cursor_pos()?,\r\n        button: MouseButton::Right,\r\n        pressed_buttons: callback_data.pressed,\r\n      },\r\n      MouseEventKind::Move => MouseEvent::Move {\r\n        position: Self::cursor_pos()?,\r\n        pressed_buttons: callback_data.pressed,\r\n        window_below_cursor: None,\r\n      },\r\n    };\r\n\r\n    let _ = callback_data.event_tx.send(mouse_event);\r\n\r\n    if event_kind == MouseEventKind::Move {\r\n      callback_data.last_move_emission = Some(Instant::now());\r\n    }\r\n\r\n    Ok(())\r\n  }\r\n\r\n  /// Gets the current cursor position.\r\n  fn cursor_pos() -> crate::Result<Point> {\r\n    let mut point = POINT { x: 0, y: 0 };\r\n    unsafe { GetCursorPos(&raw mut point) }?;\r\n    Ok(Point {\r\n      x: point.x,\r\n      y: point.y,\r\n    })\r\n  }\r\n\r\n  /// Registers or deregisters the raw input device for mouse events.\r\n  fn enable_raw_input(\r\n    target_handle: isize,\r\n    enabled: bool,\r\n  ) -> crate::Result<()> {\r\n    let mode_flag = if enabled {\r\n      RIDEV_INPUTSINK\r\n    } else {\r\n      RIDEV_REMOVE\r\n    };\r\n\r\n    let target_hwnd = if enabled {\r\n      HWND(target_handle)\r\n    } else {\r\n      HWND::default()\r\n    };\r\n\r\n    let rid = RAWINPUTDEVICE {\r\n      usUsagePage: HID_USAGE_PAGE_GENERIC,\r\n      usUsage: HID_USAGE_GENERIC_MOUSE,\r\n      dwFlags: mode_flag,\r\n      hwndTarget: target_hwnd,\r\n    };\r\n\r\n    unsafe {\r\n      #[allow(clippy::cast_possible_truncation)]\r\n      RegisterRawInputDevices(\r\n        &[rid],\r\n        std::mem::size_of::<RAWINPUTDEVICE>() as u32,\r\n      )\r\n    }\r\n    .map_err(crate::Error::from)\r\n  }\r\n}\r\n\r\nimpl Drop for MouseListener {\r\n  fn drop(&mut self) {\r\n    if let Err(err) = self.terminate() {\r\n      tracing::warn!(\"Failed to terminate mouse listener: {}\", err);\r\n    }\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-platform/src/platform_impl/windows/native_window.rs",
    "content": "use std::time::Duration;\r\n\r\nuse tokio::task;\r\nuse tracing::warn;\r\nuse windows::{\r\n  core::PWSTR,\r\n  Win32::{\r\n    Foundation::{CloseHandle, BOOL, HWND, LPARAM, POINT, RECT},\r\n    Graphics::Dwm::{\r\n      DwmGetWindowAttribute, DwmSetWindowAttribute, DWMWA_BORDER_COLOR,\r\n      DWMWA_CLOAKED, DWMWA_COLOR_NONE, DWMWA_EXTENDED_FRAME_BOUNDS,\r\n      DWMWA_WINDOW_CORNER_PREFERENCE, DWMWCP_DEFAULT, DWMWCP_DONOTROUND,\r\n      DWMWCP_ROUND, DWMWCP_ROUNDSMALL,\r\n    },\r\n    System::Threading::{\r\n      OpenProcess, QueryFullProcessImageNameW, PROCESS_NAME_WIN32,\r\n      PROCESS_QUERY_LIMITED_INFORMATION,\r\n    },\r\n    UI::{\r\n      Input::KeyboardAndMouse::{\r\n        SendInput, INPUT, INPUT_0, INPUT_MOUSE, MOUSEINPUT,\r\n      },\r\n      WindowsAndMessaging::{\r\n        EnumWindows, GetAncestor, GetClassNameW, GetDesktopWindow,\r\n        GetForegroundWindow, GetLayeredWindowAttributes, GetShellWindow,\r\n        GetWindow, GetWindowLongPtrW, GetWindowRect, GetWindowTextW,\r\n        GetWindowThreadProcessId, IsIconic, IsWindow, IsWindowVisible,\r\n        IsZoomed, SendNotifyMessageW, SetForegroundWindow,\r\n        SetLayeredWindowAttributes, SetWindowLongPtrW, SetWindowPlacement,\r\n        SetWindowPos, ShowWindowAsync, WindowFromPoint, GA_ROOT,\r\n        GWL_EXSTYLE, GWL_STYLE, GW_OWNER, HWND_NOTOPMOST, HWND_TOP,\r\n        HWND_TOPMOST, LAYERED_WINDOW_ATTRIBUTES_FLAGS, LWA_ALPHA,\r\n        LWA_COLORKEY, SET_WINDOW_POS_FLAGS, SWP_ASYNCWINDOWPOS,\r\n        SWP_FRAMECHANGED, SWP_NOACTIVATE, SWP_NOCOPYBITS, SWP_NOMOVE,\r\n        SWP_NOOWNERZORDER, SWP_NOSENDCHANGING, SWP_NOSIZE, SWP_NOZORDER,\r\n        SWP_SHOWWINDOW, SW_HIDE, SW_MAXIMIZE, SW_MINIMIZE, SW_RESTORE,\r\n        SW_SHOWNA, WINDOWPLACEMENT, WINDOW_EX_STYLE, WINDOW_STYLE,\r\n        WM_CLOSE, WPF_ASYNCWINDOWPLACEMENT, WS_DLGFRAME, WS_EX_LAYERED,\r\n        WS_THICKFRAME,\r\n      },\r\n    },\r\n  },\r\n};\r\n\r\nuse super::com::{IApplicationView, COM_INIT};\r\nuse crate::{\r\n  Color, CornerStyle, Delta, Dispatcher, LengthValue, OpacityValue, Point,\r\n  Rect, RectDelta, WindowId, WindowZOrder,\r\n};\r\n\r\n/// Magic number used to identify programmatic mouse inputs from our own\r\n/// process.\r\npub(crate) const FOREGROUND_INPUT_IDENTIFIER: u32 = 6379;\r\n\r\n/// Platform-specific implementation of [`NativeWindow`].\r\n#[derive(Clone, Debug)]\r\npub(crate) struct NativeWindow {\r\n  pub(crate) handle: isize,\r\n}\r\n\r\nimpl NativeWindow {\r\n  /// Creates an instance of `NativeWindow`.\r\n  #[must_use]\r\n  pub(crate) fn new(handle: isize) -> Self {\r\n    Self { handle }\r\n  }\r\n\r\n  /// Implements [`NativeWindow::id`].\r\n  #[must_use]\r\n  pub(crate) fn id(&self) -> WindowId {\r\n    WindowId(self.handle)\r\n  }\r\n\r\n  /// Implements [`NativeWindow::title`].\r\n  #[allow(clippy::unnecessary_wraps)]\r\n  pub(crate) fn title(&self) -> crate::Result<String> {\r\n    let mut text: [u16; 512] = [0; 512];\r\n    let length = unsafe { GetWindowTextW(self.hwnd(), &mut text) };\r\n\r\n    #[allow(clippy::cast_sign_loss)]\r\n    Ok(String::from_utf16_lossy(&text[..length as usize]))\r\n  }\r\n\r\n  /// Implements [`NativeWindow::process_name`].\r\n  pub(crate) fn process_name(&self) -> crate::Result<String> {\r\n    let mut process_id = 0u32;\r\n    unsafe {\r\n      GetWindowThreadProcessId(self.hwnd(), Some(&raw mut process_id));\r\n    }\r\n\r\n    let process_handle = unsafe {\r\n      OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, process_id)\r\n    }?;\r\n\r\n    let mut buffer = [0u16; 256];\r\n    let mut length = u32::try_from(buffer.len())?;\r\n\r\n    unsafe {\r\n      let query_res = QueryFullProcessImageNameW(\r\n        process_handle,\r\n        PROCESS_NAME_WIN32,\r\n        PWSTR(buffer.as_mut_ptr()),\r\n        &raw mut length,\r\n      );\r\n\r\n      // Always close the process handle regardless of the query result.\r\n      CloseHandle(process_handle)?;\r\n\r\n      query_res\r\n    }?;\r\n\r\n    let exe_path = String::from_utf16_lossy(&buffer[..length as usize]);\r\n\r\n    exe_path\r\n      .split('\\\\')\r\n      .next_back()\r\n      .map(|file_name| {\r\n        file_name.split('.').next().unwrap_or(file_name).to_string()\r\n      })\r\n      .ok_or_else(|| {\r\n        crate::Error::Platform(\"Failed to parse process name.\".to_string())\r\n      })\r\n  }\r\n\r\n  /// Implements [`NativeWindow::frame`].\r\n  pub(crate) fn frame(&self) -> crate::Result<Rect> {\r\n    let mut rect = RECT::default();\r\n\r\n    let dwm_res = unsafe {\r\n      #[allow(clippy::cast_possible_truncation)]\r\n      DwmGetWindowAttribute(\r\n        self.hwnd(),\r\n        DWMWA_EXTENDED_FRAME_BOUNDS,\r\n        std::ptr::from_mut(&mut rect).cast(),\r\n        std::mem::size_of::<RECT>() as u32,\r\n      )\r\n    };\r\n\r\n    if let Ok(()) = dwm_res {\r\n      Ok(Rect::from_ltrb(\r\n        rect.left,\r\n        rect.top,\r\n        rect.right,\r\n        rect.bottom,\r\n      ))\r\n    } else {\r\n      warn!(\"Failed to get window's frame position. Falling back to border position.\");\r\n      self.frame_with_shadows()\r\n    }\r\n  }\r\n\r\n  /// Implements [`NativeWindow::position`].\r\n  pub(crate) fn position(&self) -> crate::Result<(f64, f64)> {\r\n    let frame = self.frame()?;\r\n    Ok((f64::from(frame.left), f64::from(frame.top)))\r\n  }\r\n\r\n  /// Implements [`NativeWindow::size`].\r\n  pub(crate) fn size(&self) -> crate::Result<(f64, f64)> {\r\n    let frame = self.frame()?;\r\n    Ok((f64::from(frame.width()), f64::from(frame.height())))\r\n  }\r\n\r\n  /// Implements [`NativeWindow::is_valid`].\r\n  pub(crate) fn is_valid(&self) -> bool {\r\n    unsafe { IsWindow(self.hwnd()) }.as_bool()\r\n  }\r\n\r\n  /// Implements [`NativeWindow::is_visible`].\r\n  pub(crate) fn is_visible(&self) -> crate::Result<bool> {\r\n    let is_visible = unsafe { IsWindowVisible(self.hwnd()) }.as_bool();\r\n\r\n    Ok(is_visible && !self.is_cloaked()?)\r\n  }\r\n\r\n  /// Implements [`NativeWindow::is_minimized`].\r\n  #[allow(clippy::unnecessary_wraps)]\r\n  pub(crate) fn is_minimized(&self) -> crate::Result<bool> {\r\n    Ok(unsafe { IsIconic(self.hwnd()) }.as_bool())\r\n  }\r\n\r\n  /// Implements [`NativeWindow::is_maximized`].\r\n  #[allow(clippy::unnecessary_wraps)]\r\n  pub(crate) fn is_maximized(&self) -> crate::Result<bool> {\r\n    Ok(unsafe { IsZoomed(self.hwnd()) }.as_bool())\r\n  }\r\n\r\n  /// Implements [`NativeWindow::is_resizable`].\r\n  #[allow(clippy::unnecessary_wraps)]\r\n  pub(crate) fn is_resizable(&self) -> crate::Result<bool> {\r\n    Ok(self.has_window_style(WS_THICKFRAME))\r\n  }\r\n\r\n  /// Implements [`NativeWindow::is_desktop_window`].\r\n  #[allow(clippy::unnecessary_wraps)]\r\n  pub(crate) fn is_desktop_window(&self) -> crate::Result<bool> {\r\n    Ok(*self == desktop_window())\r\n  }\r\n\r\n  /// Implements [`NativeWindow::set_frame`].\r\n  pub(crate) fn set_frame(&self, rect: &Rect) -> crate::Result<()> {\r\n    unsafe {\r\n      SetWindowPos(\r\n        self.hwnd(),\r\n        HWND_NOTOPMOST,\r\n        rect.x(),\r\n        rect.y(),\r\n        rect.width(),\r\n        rect.height(),\r\n        SWP_NOACTIVATE\r\n          | SWP_NOZORDER\r\n          | SWP_NOCOPYBITS\r\n          | SWP_NOSENDCHANGING\r\n          | SWP_ASYNCWINDOWPOS\r\n          | SWP_FRAMECHANGED,\r\n      )\r\n    }?;\r\n\r\n    Ok(())\r\n  }\r\n\r\n  /// Implements [`NativeWindow::resize`].\r\n  pub(crate) fn resize(\r\n    &self,\r\n    width: i32,\r\n    height: i32,\r\n  ) -> crate::Result<()> {\r\n    unsafe {\r\n      SetWindowPos(\r\n        self.hwnd(),\r\n        HWND_NOTOPMOST,\r\n        0,\r\n        0,\r\n        width,\r\n        height,\r\n        SWP_NOACTIVATE\r\n          | SWP_NOZORDER\r\n          | SWP_NOMOVE\r\n          | SWP_NOCOPYBITS\r\n          | SWP_NOSENDCHANGING\r\n          | SWP_ASYNCWINDOWPOS\r\n          | SWP_FRAMECHANGED,\r\n      )\r\n    }?;\r\n\r\n    Ok(())\r\n  }\r\n\r\n  /// Implements [`NativeWindow::reposition`].\r\n  pub(crate) fn reposition(&self, x: i32, y: i32) -> crate::Result<()> {\r\n    unsafe {\r\n      SetWindowPos(\r\n        self.hwnd(),\r\n        HWND_NOTOPMOST,\r\n        x,\r\n        y,\r\n        0,\r\n        0,\r\n        SWP_NOACTIVATE\r\n          | SWP_NOZORDER\r\n          | SWP_NOSIZE\r\n          | SWP_NOCOPYBITS\r\n          | SWP_NOSENDCHANGING\r\n          | SWP_ASYNCWINDOWPOS\r\n          | SWP_FRAMECHANGED,\r\n      )\r\n    }?;\r\n\r\n    Ok(())\r\n  }\r\n\r\n  /// Implements [`NativeWindow::minimize`].\r\n  pub(crate) fn minimize(&self) -> crate::Result<()> {\r\n    unsafe { ShowWindowAsync(self.hwnd(), SW_MINIMIZE).ok() }?;\r\n    Ok(())\r\n  }\r\n\r\n  /// Implements [`NativeWindow::maximize`].\r\n  pub(crate) fn maximize(&self) -> crate::Result<()> {\r\n    unsafe { ShowWindowAsync(self.hwnd(), SW_MAXIMIZE).ok() }?;\r\n    Ok(())\r\n  }\r\n\r\n  /// Implements [`NativeWindow::focus`].\r\n  pub(crate) fn focus(&self) -> crate::Result<()> {\r\n    let input = [INPUT {\r\n      r#type: INPUT_MOUSE,\r\n      Anonymous: INPUT_0 {\r\n        mi: MOUSEINPUT {\r\n          dwExtraInfo: FOREGROUND_INPUT_IDENTIFIER as usize,\r\n          ..Default::default()\r\n        },\r\n      },\r\n    }];\r\n\r\n    // Bypass restriction for setting the foreground window by sending an\r\n    // input to our own process first.\r\n    #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]\r\n    unsafe {\r\n      SendInput(&input, std::mem::size_of::<INPUT>() as i32)\r\n    };\r\n\r\n    // Set as the foreground window.\r\n    unsafe { SetForegroundWindow(self.hwnd()) }.ok()?;\r\n\r\n    Ok(())\r\n  }\r\n\r\n  /// Implements [`NativeWindow::close`].\r\n  pub(crate) fn close(&self) -> crate::Result<()> {\r\n    unsafe { SendNotifyMessageW(self.hwnd(), WM_CLOSE, None, None) }?;\r\n    Ok(())\r\n  }\r\n\r\n  /// Implements [`NativeWindowWindowsExt::hwnd`].\r\n  pub(crate) fn hwnd(&self) -> HWND {\r\n    HWND(self.handle)\r\n  }\r\n\r\n  /// Implements [`NativeWindowWindowsExt::class_name`].\r\n  pub(crate) fn class_name(&self) -> crate::Result<String> {\r\n    let mut buffer = [0u16; 256];\r\n    let result = unsafe { GetClassNameW(self.hwnd(), &mut buffer) };\r\n\r\n    if result == 0 {\r\n      return Err(windows::core::Error::from_win32().into());\r\n    }\r\n\r\n    #[allow(clippy::cast_sign_loss)]\r\n    let class_name = String::from_utf16_lossy(&buffer[..result as usize]);\r\n    Ok(class_name)\r\n  }\r\n\r\n  /// Implements [`NativeWindowWindowsExt::frame_with_shadows`].\r\n  pub(crate) fn frame_with_shadows(&self) -> crate::Result<Rect> {\r\n    let mut rect = RECT::default();\r\n\r\n    unsafe {\r\n      GetWindowRect(self.hwnd(), std::ptr::from_mut(&mut rect).cast())\r\n    }?;\r\n\r\n    Ok(Rect::from_ltrb(\r\n      rect.left,\r\n      rect.top,\r\n      rect.right,\r\n      rect.bottom,\r\n    ))\r\n  }\r\n\r\n  /// Implements [`NativeWindowWindowsExt::shadow_borders`].\r\n  // TODO: Return tuple of (left, top, right, bottom) instead of\r\n  // `RectDelta`.\r\n  pub(crate) fn shadow_borders(&self) -> crate::Result<RectDelta> {\r\n    let border_pos = self.frame_with_shadows()?;\r\n    let frame_pos = self.frame()?;\r\n\r\n    Ok(RectDelta::new(\r\n      LengthValue::from_px(frame_pos.left - border_pos.left),\r\n      LengthValue::from_px(frame_pos.top - border_pos.top),\r\n      LengthValue::from_px(border_pos.right - frame_pos.right),\r\n      LengthValue::from_px(border_pos.bottom - frame_pos.bottom),\r\n    ))\r\n  }\r\n\r\n  /// Implements [`NativeWindowWindowsExt::has_owner_window`].\r\n  pub(crate) fn has_owner_window(&self) -> bool {\r\n    unsafe { GetWindow(self.hwnd(), GW_OWNER) }.0 != 0\r\n  }\r\n\r\n  /// Implements [`NativeWindowWindowsExt::has_window_style`].\r\n  pub(crate) fn has_window_style(&self, style: WINDOW_STYLE) -> bool {\r\n    let current_style =\r\n      unsafe { GetWindowLongPtrW(self.hwnd(), GWL_STYLE) };\r\n\r\n    #[allow(clippy::cast_possible_wrap)]\r\n    let style = style.0 as isize;\r\n    (current_style & style) != 0\r\n  }\r\n\r\n  /// Implements [`NativeWindowWindowsExt::has_window_style_ex`].\r\n  pub(crate) fn has_window_style_ex(\r\n    &self,\r\n    style: WINDOW_EX_STYLE,\r\n  ) -> bool {\r\n    let current_style =\r\n      unsafe { GetWindowLongPtrW(self.hwnd(), GWL_EXSTYLE) };\r\n\r\n    #[allow(clippy::cast_possible_wrap)]\r\n    let style = style.0 as isize;\r\n    (current_style & style) != 0\r\n  }\r\n\r\n  /// Implements [`NativeWindowWindowsExt::set_window_pos`].\r\n  pub(crate) fn set_window_pos(\r\n    &self,\r\n    z_order: &WindowZOrder,\r\n    rect: &Rect,\r\n    flags: SET_WINDOW_POS_FLAGS,\r\n  ) -> crate::Result<()> {\r\n    let z_order_hwnd = match z_order {\r\n      WindowZOrder::TopMost => HWND_TOPMOST,\r\n      WindowZOrder::Top => HWND_TOP,\r\n      WindowZOrder::Normal => HWND_NOTOPMOST,\r\n      WindowZOrder::AfterWindow(window_id) => HWND(window_id.0),\r\n    };\r\n\r\n    unsafe {\r\n      SetWindowPos(\r\n        self.hwnd(),\r\n        z_order_hwnd,\r\n        rect.x(),\r\n        rect.y(),\r\n        rect.width(),\r\n        rect.height(),\r\n        flags,\r\n      )\r\n    }?;\r\n\r\n    Ok(())\r\n  }\r\n\r\n  /// Implements [`NativeWindowWindowsExt::show`].\r\n  pub(crate) fn show(&self) -> crate::Result<()> {\r\n    unsafe { ShowWindowAsync(self.hwnd(), SW_SHOWNA) }.ok()?;\r\n    Ok(())\r\n  }\r\n\r\n  /// Implements [`NativeWindowWindowsExt::hide`].\r\n  pub(crate) fn hide(&self) -> crate::Result<()> {\r\n    unsafe { ShowWindowAsync(self.hwnd(), SW_HIDE) }.ok()?;\r\n    Ok(())\r\n  }\r\n\r\n  /// Implements [`NativeWindowWindowsExt::restore`].\r\n  pub(crate) fn restore(\r\n    &self,\r\n    outer_frame: Option<&Rect>,\r\n  ) -> crate::Result<()> {\r\n    match outer_frame {\r\n      None => {\r\n        unsafe { ShowWindowAsync(self.hwnd(), SW_RESTORE) }.ok()?;\r\n        Ok(())\r\n      }\r\n      Some(rect) => {\r\n        let placement = WINDOWPLACEMENT {\r\n          #[allow(clippy::cast_possible_truncation)]\r\n          length: std::mem::size_of::<WINDOWPLACEMENT>() as u32,\r\n          flags: WPF_ASYNCWINDOWPLACEMENT,\r\n          showCmd: SW_RESTORE.0 as u32,\r\n          rcNormalPosition: RECT {\r\n            left: rect.left,\r\n            top: rect.top,\r\n            right: rect.right,\r\n            bottom: rect.bottom,\r\n          },\r\n          ..Default::default()\r\n        };\r\n\r\n        unsafe { SetWindowPlacement(self.hwnd(), &raw const placement) }?;\r\n        Ok(())\r\n      }\r\n    }\r\n  }\r\n\r\n  /// Implements [`NativeWindowWindowsExt::set_cloaked`].\r\n  pub(crate) fn set_cloaked(&self, cloaked: bool) -> crate::Result<()> {\r\n    COM_INIT.with(|com_init| -> crate::Result<()> {\r\n      com_init.borrow_mut().with_retry(|com| {\r\n        let view_collection = com.application_view_collection()?;\r\n\r\n        let mut view: Option<IApplicationView> = None;\r\n        unsafe {\r\n          view_collection.get_view_for_hwnd(self.hwnd().0, &raw mut view)\r\n        }\r\n        .ok()?;\r\n\r\n        let view = view.ok_or_else(|| {\r\n          crate::Error::Platform(\r\n            \"Unable to get application view by window handle.\".to_string(),\r\n          )\r\n        })?;\r\n\r\n        // Ref: https://github.com/Ciantic/AltTabAccessor/issues/1#issuecomment-1426877843\r\n        unsafe { view.set_cloak(1, if cloaked { 2 } else { 0 }) }\r\n          .ok()\r\n          .map_err(|_| {\r\n            crate::Error::Platform(\"Failed to cloak window.\".to_string())\r\n          })\r\n      })\r\n    })\r\n  }\r\n\r\n  /// Implements [`NativeWindowWindowsExt::mark_fullscreen`].\r\n  pub(crate) fn mark_fullscreen(\r\n    &self,\r\n    fullscreen: bool,\r\n  ) -> crate::Result<()> {\r\n    COM_INIT.with(|com_init| -> crate::Result<()> {\r\n      com_init.borrow_mut().with_retry(|com| {\r\n        let taskbar_list = com.taskbar_list()?;\r\n\r\n        unsafe {\r\n          taskbar_list.MarkFullscreenWindow(self.hwnd(), fullscreen)\r\n        }?;\r\n\r\n        Ok(())\r\n      })\r\n    })\r\n  }\r\n\r\n  /// Implements [`NativeWindowWindowsExt::set_taskbar_visibility`].\r\n  pub(crate) fn set_taskbar_visibility(\r\n    &self,\r\n    visible: bool,\r\n  ) -> crate::Result<()> {\r\n    COM_INIT.with(|com_init| -> crate::Result<()> {\r\n      com_init.borrow_mut().with_retry(|com| {\r\n        let taskbar_list = com.taskbar_list()?;\r\n\r\n        if visible {\r\n          unsafe { taskbar_list.AddTab(self.hwnd())? };\r\n        } else {\r\n          unsafe { taskbar_list.DeleteTab(self.hwnd())? };\r\n        }\r\n\r\n        Ok(())\r\n      })\r\n    })\r\n  }\r\n\r\n  /// Implements [`NativeWindowWindowsExt::add_window_style_ex`].\r\n  pub(crate) fn add_window_style_ex(&self, style: WINDOW_EX_STYLE) {\r\n    let current_style =\r\n      unsafe { GetWindowLongPtrW(self.hwnd(), GWL_EXSTYLE) };\r\n\r\n    #[allow(clippy::cast_possible_wrap)]\r\n    if current_style & style.0 as isize == 0 {\r\n      let new_style = current_style | style.0 as isize;\r\n\r\n      unsafe { SetWindowLongPtrW(self.hwnd(), GWL_EXSTYLE, new_style) };\r\n    }\r\n  }\r\n\r\n  /// Implements [`NativeWindowWindowsExt::set_z_order`].\r\n  pub(crate) fn set_z_order(\r\n    &self,\r\n    z_order: &WindowZOrder,\r\n  ) -> crate::Result<()> {\r\n    let z_order_hwnd = match z_order {\r\n      WindowZOrder::TopMost => HWND_TOPMOST,\r\n      WindowZOrder::Top => HWND_TOP,\r\n      WindowZOrder::Normal => HWND_NOTOPMOST,\r\n      WindowZOrder::AfterWindow(window_id) => HWND(window_id.0),\r\n    };\r\n\r\n    let flags = SWP_NOACTIVATE\r\n      | SWP_NOCOPYBITS\r\n      | SWP_ASYNCWINDOWPOS\r\n      | SWP_SHOWWINDOW\r\n      | SWP_NOMOVE\r\n      | SWP_NOSIZE;\r\n\r\n    unsafe { SetWindowPos(self.hwnd(), z_order_hwnd, 0, 0, 0, 0, flags) }?;\r\n\r\n    // Z-order can sometimes still be incorrect after the above call.\r\n    let handle = self.handle;\r\n    task::spawn(async move {\r\n      tokio::time::sleep(Duration::from_millis(10)).await;\r\n      let _ = unsafe {\r\n        SetWindowPos(HWND(handle), z_order_hwnd, 0, 0, 0, 0, flags)\r\n      };\r\n    });\r\n\r\n    Ok(())\r\n  }\r\n\r\n  /// Implements [`NativeWindowWindowsExt::set_title_bar_visibility`].\r\n  pub(crate) fn set_title_bar_visibility(\r\n    &self,\r\n    visible: bool,\r\n  ) -> crate::Result<()> {\r\n    let style = unsafe { GetWindowLongPtrW(self.hwnd(), GWL_STYLE) };\r\n\r\n    #[allow(clippy::cast_possible_wrap)]\r\n    let new_style = if visible {\r\n      style | (WS_DLGFRAME.0 as isize)\r\n    } else {\r\n      style & !(WS_DLGFRAME.0 as isize)\r\n    };\r\n\r\n    if new_style != style {\r\n      unsafe {\r\n        SetWindowLongPtrW(self.hwnd(), GWL_STYLE, new_style);\r\n        SetWindowPos(\r\n          self.hwnd(),\r\n          HWND_NOTOPMOST,\r\n          0,\r\n          0,\r\n          0,\r\n          0,\r\n          SWP_FRAMECHANGED\r\n            | SWP_NOMOVE\r\n            | SWP_NOSIZE\r\n            | SWP_NOZORDER\r\n            | SWP_NOOWNERZORDER\r\n            | SWP_NOACTIVATE\r\n            | SWP_NOCOPYBITS\r\n            | SWP_NOSENDCHANGING\r\n            | SWP_ASYNCWINDOWPOS,\r\n        )?;\r\n      }\r\n    }\r\n\r\n    Ok(())\r\n  }\r\n\r\n  /// Implements [`NativeWindowWindowsExt::set_border_color`].\r\n  pub(crate) fn set_border_color(\r\n    &self,\r\n    color: Option<&Color>,\r\n  ) -> crate::Result<()> {\r\n    let bgr = match color {\r\n      Some(color) => color.to_bgr(),\r\n      None => DWMWA_COLOR_NONE,\r\n    };\r\n\r\n    unsafe {\r\n      #[allow(clippy::cast_possible_truncation)]\r\n      DwmSetWindowAttribute(\r\n        self.hwnd(),\r\n        DWMWA_BORDER_COLOR,\r\n        std::ptr::from_ref(&bgr).cast(),\r\n        std::mem::size_of::<u32>() as u32,\r\n      )?;\r\n    }\r\n\r\n    Ok(())\r\n  }\r\n\r\n  /// Implements [`NativeWindowWindowsExt::set_corner_style`].\r\n  pub(crate) fn set_corner_style(\r\n    &self,\r\n    corner_style: &CornerStyle,\r\n  ) -> crate::Result<()> {\r\n    let corner_preference = match corner_style {\r\n      CornerStyle::Default => DWMWCP_DEFAULT,\r\n      CornerStyle::Square => DWMWCP_DONOTROUND,\r\n      CornerStyle::Rounded => DWMWCP_ROUND,\r\n      CornerStyle::SmallRounded => DWMWCP_ROUNDSMALL,\r\n    };\r\n\r\n    unsafe {\r\n      #[allow(clippy::cast_possible_truncation)]\r\n      DwmSetWindowAttribute(\r\n        self.hwnd(),\r\n        DWMWA_WINDOW_CORNER_PREFERENCE,\r\n        std::ptr::from_ref(&(corner_preference.0)).cast(),\r\n        std::mem::size_of::<i32>() as u32,\r\n      )?;\r\n    }\r\n\r\n    Ok(())\r\n  }\r\n\r\n  /// Implements [`NativeWindowWindowsExt::set_transparency`].\r\n  pub(crate) fn set_transparency(\r\n    &self,\r\n    opacity_value: &OpacityValue,\r\n  ) -> crate::Result<()> {\r\n    // Make the window layered if it isn't already.\r\n    self.add_window_style_ex(WS_EX_LAYERED);\r\n\r\n    unsafe {\r\n      SetLayeredWindowAttributes(\r\n        self.hwnd(),\r\n        None,\r\n        opacity_value.to_alpha(),\r\n        LWA_ALPHA,\r\n      )?;\r\n    }\r\n\r\n    Ok(())\r\n  }\r\n\r\n  /// Implements [`NativeWindowWindowsExt::adjust_transparency`].\r\n  pub(crate) fn adjust_transparency(\r\n    &self,\r\n    opacity_delta: &Delta<OpacityValue>,\r\n  ) -> crate::Result<()> {\r\n    let mut alpha = u8::MAX;\r\n    let mut flag = LAYERED_WINDOW_ATTRIBUTES_FLAGS::default();\r\n\r\n    unsafe {\r\n      GetLayeredWindowAttributes(\r\n        self.hwnd(),\r\n        None,\r\n        Some(&raw mut alpha),\r\n        Some(&raw mut flag),\r\n      )?;\r\n    }\r\n\r\n    if flag.contains(LWA_COLORKEY) {\r\n      return Err(crate::Error::Platform(\r\n        \"Window uses color key for its transparency and cannot be adjusted.\"\r\n          .to_string(),\r\n      ));\r\n    }\r\n\r\n    let target_alpha = if opacity_delta.is_negative {\r\n      alpha.saturating_sub(opacity_delta.inner.to_alpha())\r\n    } else {\r\n      alpha.saturating_add(opacity_delta.inner.to_alpha())\r\n    };\r\n\r\n    self.set_transparency(&OpacityValue::from_alpha(target_alpha))\r\n  }\r\n\r\n  /// Whether the window is cloaked. For some UWP apps, `WS_VISIBLE` will\r\n  /// be present even if the window isn't actually visible. The\r\n  /// `DWMWA_CLOAKED` attribute is used to check whether these apps are\r\n  /// visible.\r\n  fn is_cloaked(&self) -> crate::Result<bool> {\r\n    let mut cloaked = 0u32;\r\n\r\n    unsafe {\r\n      #[allow(clippy::cast_possible_truncation)]\r\n      DwmGetWindowAttribute(\r\n        self.hwnd(),\r\n        DWMWA_CLOAKED,\r\n        std::ptr::from_mut::<u32>(&mut cloaked).cast(),\r\n        std::mem::size_of::<u32>() as u32,\r\n      )\r\n    }?;\r\n\r\n    Ok(cloaked != 0)\r\n  }\r\n}\r\n\r\nimpl PartialEq for NativeWindow {\r\n  fn eq(&self, other: &Self) -> bool {\r\n    self.handle == other.handle\r\n  }\r\n}\r\n\r\nimpl Eq for NativeWindow {}\r\n\r\nimpl From<NativeWindow> for crate::NativeWindow {\r\n  fn from(window: NativeWindow) -> Self {\r\n    crate::NativeWindow { inner: window }\r\n  }\r\n}\r\n\r\n/// Implements [`Dispatcher::visible_windows`].\r\npub(crate) fn visible_windows(\r\n  _: &Dispatcher,\r\n) -> crate::Result<Vec<crate::NativeWindow>> {\r\n  let mut handles: Vec<isize> = Vec::new();\r\n\r\n  #[allow(clippy::items_after_statements)]\r\n  extern \"system\" fn visible_windows_proc(\r\n    handle: HWND,\r\n    data: LPARAM,\r\n  ) -> BOOL {\r\n    let handles = data.0 as *mut Vec<isize>;\r\n    unsafe { (*handles).push(handle.0) };\r\n    true.into()\r\n  }\r\n\r\n  unsafe {\r\n    EnumWindows(\r\n      Some(visible_windows_proc),\r\n      LPARAM(std::ptr::from_mut(&mut handles) as _),\r\n    )\r\n  }?;\r\n\r\n  Ok(\r\n    handles\r\n      .into_iter()\r\n      .map(NativeWindow::new)\r\n      .filter(|window| window.is_visible().unwrap_or(false))\r\n      .map(Into::into)\r\n      .collect(),\r\n  )\r\n}\r\n\r\n/// Implements [`Dispatcher::focused_window`].\r\n#[allow(clippy::unnecessary_wraps)]\r\npub(crate) fn focused_window(\r\n  _: &Dispatcher,\r\n) -> crate::Result<crate::NativeWindow> {\r\n  let handle = unsafe { GetForegroundWindow() };\r\n  Ok(NativeWindow::new(handle.0).into())\r\n}\r\n\r\n/// Implements [`Dispatcher::window_from_point`].\r\n#[allow(clippy::unnecessary_wraps)]\r\npub(crate) fn window_from_point(\r\n  point: &Point,\r\n  _: &Dispatcher,\r\n) -> crate::Result<Option<crate::NativeWindow>> {\r\n  let point = POINT {\r\n    x: point.x,\r\n    y: point.y,\r\n  };\r\n\r\n  let handle = unsafe { WindowFromPoint(point) };\r\n  if handle.0 == 0 {\r\n    return Ok(None);\r\n  }\r\n\r\n  let root = unsafe { GetAncestor(handle, GA_ROOT) };\r\n  if root.0 == 0 {\r\n    return Ok(None);\r\n  }\r\n\r\n  Ok(Some(NativeWindow::new(root.0).into()))\r\n}\r\n\r\n/// Implements [`Dispatcher::reset_focus`].\r\npub(crate) fn reset_focus(_dispatcher: &Dispatcher) -> crate::Result<()> {\r\n  desktop_window().focus()\r\n}\r\n\r\n/// Gets the `NativeWindow` instance of the desktop window.\r\n///\r\n/// This is the explorer.exe wallpaper window (i.e. \"Progman\"). If\r\n/// explorer.exe isn't running, then default to the desktop window below\r\n/// the wallpaper window.\r\n#[must_use]\r\nfn desktop_window() -> NativeWindow {\r\n  let handle = match unsafe { GetShellWindow() } {\r\n    HWND(0) => unsafe { GetDesktopWindow() },\r\n    handle => handle,\r\n  };\r\n\r\n  NativeWindow::new(handle.0)\r\n}\r\n"
  },
  {
    "path": "packages/wm-platform/src/platform_impl/windows/single_instance.rs",
    "content": "use windows::{\r\n  core::{w, PCWSTR},\r\n  Win32::{\r\n    Foundation::{\r\n      CloseHandle, GetLastError, ERROR_ALREADY_EXISTS,\r\n      ERROR_FILE_NOT_FOUND, HANDLE,\r\n    },\r\n    System::Threading::{\r\n      CreateMutexW, OpenMutexW, ReleaseMutex,\r\n      SYNCHRONIZATION_ACCESS_RIGHTS,\r\n    },\r\n  },\r\n};\r\n\r\n/// Arbitrary GUID to uniquely identify the application.\r\nconst APP_GUID: PCWSTR =\r\n  w!(\"Global\\\\325d0ed7-7f60-4925-8d1b-aa287b26b218\");\r\n\r\n/// Platform-specific implementation of [`SingleInstance`].\r\npub struct SingleInstance {\r\n  handle: HANDLE,\r\n}\r\n\r\nimpl SingleInstance {\r\n  /// Implements [`SingleInstance::new`].\r\n  pub(crate) fn new() -> crate::Result<Self> {\r\n    // Create a named system-wide mutex.\r\n    let handle = unsafe { CreateMutexW(None, true, APP_GUID) }?;\r\n\r\n    if let Err(err) = unsafe { GetLastError() } {\r\n      if err == ERROR_ALREADY_EXISTS.into() {\r\n        return Err(crate::Error::Platform(\r\n          \"Another instance of the application is already running.\"\r\n            .to_string(),\r\n        ));\r\n      }\r\n    }\r\n\r\n    Ok(Self { handle })\r\n  }\r\n\r\n  /// Implements [`SingleInstance::is_running`].\r\n  #[must_use]\r\n  pub(crate) fn is_running() -> bool {\r\n    let res = unsafe {\r\n      OpenMutexW(SYNCHRONIZATION_ACCESS_RIGHTS::default(), false, APP_GUID)\r\n    };\r\n\r\n    // If the mutex exists, then another instance is running.\r\n    match res {\r\n      Ok(_) => false,\r\n      Err(err) => err == ERROR_FILE_NOT_FOUND.into(),\r\n    }\r\n  }\r\n}\r\n\r\nimpl Drop for SingleInstance {\r\n  fn drop(&mut self) {\r\n    unsafe {\r\n      let _ = ReleaseMutex(self.handle);\r\n      let _ = CloseHandle(self.handle);\r\n    }\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-platform/src/platform_impl/windows/window_listener.rs",
    "content": "use std::sync::OnceLock;\r\n\r\nuse tokio::sync::mpsc;\r\nuse windows::Win32::{\r\n  Foundation::HWND,\r\n  UI::{\r\n    Accessibility::{SetWinEventHook, UnhookWinEvent, HWINEVENTHOOK},\r\n    WindowsAndMessaging::{\r\n      EVENT_OBJECT_CLOAKED, EVENT_OBJECT_DESTROY, EVENT_OBJECT_HIDE,\r\n      EVENT_OBJECT_LOCATIONCHANGE, EVENT_OBJECT_NAMECHANGE,\r\n      EVENT_OBJECT_SHOW, EVENT_OBJECT_UNCLOAKED, EVENT_SYSTEM_FOREGROUND,\r\n      EVENT_SYSTEM_MINIMIZEEND, EVENT_SYSTEM_MINIMIZESTART,\r\n      EVENT_SYSTEM_MOVESIZEEND, EVENT_SYSTEM_MOVESIZESTART, OBJID_WINDOW,\r\n      WINEVENT_OUTOFCONTEXT, WINEVENT_SKIPOWNPROCESS,\r\n    },\r\n  },\r\n};\r\n\r\nuse super::NativeWindow;\r\nuse crate::{Dispatcher, WindowEvent, WindowId};\r\n\r\nthread_local! {\r\n  /// Sender for window events. For use with hook procedure.\r\n  static EVENT_TX: OnceLock<mpsc::UnboundedSender<WindowEvent>> = const { OnceLock::new() };\r\n}\r\n\r\n/// Platform-specific implementation of [`WindowEventNotification`].\r\n#[derive(Clone, Debug)]\r\npub struct WindowEventNotificationInner;\r\n\r\n/// Platform-specific implementation of [`WindowListener`].\r\n#[derive(Debug)]\r\npub(crate) struct WindowListener {\r\n  hook_handles: Vec<HWINEVENTHOOK>,\r\n}\r\n\r\nimpl WindowListener {\r\n  /// Implements [`WindowListener::new`].\r\n  pub(crate) fn new(\r\n    event_tx: mpsc::UnboundedSender<WindowEvent>,\r\n    dispatcher: &Dispatcher,\r\n  ) -> crate::Result<Self> {\r\n    let hook_handles = dispatcher.dispatch_sync(move || {\r\n      EVENT_TX.with(|lock| lock.set(event_tx)).map_err(|_| {\r\n        crate::Error::Platform(\r\n          \"Window event sender already set.\".to_string(),\r\n        )\r\n      })?;\r\n\r\n      Self::hook_win_events()\r\n    })??;\r\n\r\n    Ok(Self { hook_handles })\r\n  }\r\n\r\n  /// Implements [`WindowListener::terminate`].\r\n  pub(crate) fn terminate(&mut self) {\r\n    for handle in self.hook_handles.drain(..) {\r\n      let _ = unsafe { UnhookWinEvent(handle) };\r\n    }\r\n  }\r\n\r\n  /// Creates several window event hooks via `SetWinEventHook`.\r\n  ///\r\n  /// Separate hooks are created per event range, which is more performant\r\n  /// than a single hook covering all events.\r\n  fn hook_win_events() -> crate::Result<Vec<HWINEVENTHOOK>> {\r\n    let event_ranges = [\r\n      (EVENT_OBJECT_DESTROY, EVENT_OBJECT_HIDE),\r\n      (EVENT_SYSTEM_MINIMIZESTART, EVENT_SYSTEM_MINIMIZEEND),\r\n      (EVENT_SYSTEM_MOVESIZESTART, EVENT_SYSTEM_MOVESIZEEND),\r\n      (EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND),\r\n      (EVENT_OBJECT_LOCATIONCHANGE, EVENT_OBJECT_NAMECHANGE),\r\n      (EVENT_OBJECT_CLOAKED, EVENT_OBJECT_UNCLOAKED),\r\n    ];\r\n\r\n    event_ranges\r\n      .iter()\r\n      .try_fold(Vec::new(), |mut handles, (min, max)| {\r\n        // Create a window hook for the event range.\r\n        let hook_handle = unsafe {\r\n          SetWinEventHook(\r\n            *min,\r\n            *max,\r\n            None,\r\n            Some(Self::window_event_proc),\r\n            0,\r\n            0,\r\n            WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS,\r\n          )\r\n        };\r\n\r\n        if hook_handle.is_invalid() {\r\n          return Err(crate::Error::Platform(\r\n            \"Failed to set window event hook.\".to_string(),\r\n          ));\r\n        }\r\n\r\n        handles.push(hook_handle);\r\n        Ok(handles)\r\n      })\r\n  }\r\n\r\n  /// Callback passed to `SetWinEventHook`.\r\n  ///\r\n  /// This function is called on selected window events, and forwards them\r\n  /// through an MPSC channel.\r\n  extern \"system\" fn window_event_proc(\r\n    _hook: HWINEVENTHOOK,\r\n    event_type: u32,\r\n    handle: HWND,\r\n    id_object: i32,\r\n    id_child: i32,\r\n    _event_thread: u32,\r\n    _event_time: u32,\r\n  ) {\r\n    // Check whether the event is associated with a window object rather\r\n    // than a UI control.\r\n    let is_window_event =\r\n      id_object == OBJID_WINDOW.0 && id_child == 0 && handle != HWND(0);\r\n\r\n    if !is_window_event {\r\n      return;\r\n    }\r\n\r\n    let Some(event_tx) = EVENT_TX.with(|lock| lock.get().cloned()) else {\r\n      return;\r\n    };\r\n\r\n    let notification = crate::WindowEventNotification(None);\r\n\r\n    let event = match event_type {\r\n      EVENT_OBJECT_DESTROY => WindowEvent::Destroyed {\r\n        window_id: WindowId(handle.0),\r\n        notification,\r\n      },\r\n      EVENT_SYSTEM_FOREGROUND => WindowEvent::Focused {\r\n        window: NativeWindow::new(handle.0).into(),\r\n        notification,\r\n      },\r\n      EVENT_OBJECT_HIDE | EVENT_OBJECT_CLOAKED => WindowEvent::Hidden {\r\n        window: NativeWindow::new(handle.0).into(),\r\n        notification,\r\n      },\r\n      EVENT_OBJECT_LOCATIONCHANGE => WindowEvent::MovedOrResized {\r\n        window: NativeWindow::new(handle.0).into(),\r\n        is_interactive_start: false,\r\n        is_interactive_end: false,\r\n        notification,\r\n      },\r\n      EVENT_SYSTEM_MINIMIZESTART => WindowEvent::Minimized {\r\n        window: NativeWindow::new(handle.0).into(),\r\n        notification,\r\n      },\r\n      EVENT_SYSTEM_MINIMIZEEND => WindowEvent::MinimizeEnded {\r\n        window: NativeWindow::new(handle.0).into(),\r\n        notification,\r\n      },\r\n      EVENT_SYSTEM_MOVESIZESTART => WindowEvent::MovedOrResized {\r\n        window: NativeWindow::new(handle.0).into(),\r\n        is_interactive_start: true,\r\n        is_interactive_end: false,\r\n        notification,\r\n      },\r\n      EVENT_SYSTEM_MOVESIZEEND => WindowEvent::MovedOrResized {\r\n        window: NativeWindow::new(handle.0).into(),\r\n        is_interactive_start: false,\r\n        is_interactive_end: true,\r\n        notification,\r\n      },\r\n      EVENT_OBJECT_SHOW | EVENT_OBJECT_UNCLOAKED => WindowEvent::Shown {\r\n        window: NativeWindow::new(handle.0).into(),\r\n        notification,\r\n      },\r\n      EVENT_OBJECT_NAMECHANGE => WindowEvent::TitleChanged {\r\n        window: NativeWindow::new(handle.0).into(),\r\n        notification,\r\n      },\r\n      _ => return,\r\n    };\r\n\r\n    if let Err(err) = event_tx.send(event) {\r\n      tracing::warn!(\"Failed to send window event: {}.\", err);\r\n    }\r\n  }\r\n}\r\n\r\nimpl Drop for WindowListener {\r\n  fn drop(&mut self) {\r\n    self.terminate();\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-platform/src/single_instance.rs",
    "content": "use crate::platform_impl;\r\n\r\n/// Ensures only one instance of the application is running at a time.\r\n///\r\n/// # Platform-specific\r\n///\r\n/// - **Windows**: Uses a named system-wide mutex.\r\n/// - **macOS**: Uses an exclusive file lock.\r\npub struct SingleInstance {\r\n  /// Inner platform-specific single instance implementation.\r\n  _inner: platform_impl::SingleInstance,\r\n}\r\n\r\nimpl SingleInstance {\r\n  /// Creates a new [`SingleInstance`], acquiring the platform-specific\r\n  /// lock or mutex.\r\n  ///\r\n  /// # Errors\r\n  ///\r\n  /// Returns [`Error::Platform`] if another instance is already running.\r\n  pub fn new() -> crate::Result<Self> {\r\n    let inner = platform_impl::SingleInstance::new()?;\r\n    Ok(Self { _inner: inner })\r\n  }\r\n\r\n  /// Returns whether another instance of the application is currently\r\n  /// running.\r\n  #[must_use]\r\n  pub fn is_running() -> bool {\r\n    platform_impl::SingleInstance::is_running()\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-platform/src/test.rs",
    "content": "#![feature(iterator_try_collect)]\r\n\r\n#[macro_use]\r\nextern crate libtest_mimic_collect;\r\n\r\nmod dispatcher;\r\nmod display;\r\nmod error;\r\nmod event_loop;\r\nmod keybinding_listener;\r\nmod models;\r\nmod mouse_listener;\r\nmod native_window;\r\nmod platform_event;\r\nmod platform_impl;\r\nmod thread_bound;\r\nmod window_listener;\r\n\r\npub use dispatcher::*;\r\npub use display::*;\r\npub use error::*;\r\npub use event_loop::*;\r\npub use keybinding_listener::*;\r\npub use models::*;\r\npub use mouse_listener::*;\r\npub use native_window::*;\r\npub use platform_event::*;\r\npub use thread_bound::*;\r\npub use window_listener::*;\r\n\r\npub fn main() {\r\n  // Due to macOS requiring the main thread for some UI APIs, these\r\n  // tests must execute on the main thread. Until this is natively\r\n  // supported via cargo's test harness, we use `libtest_mimic_collect`.\r\n  //\r\n  // To run these tests, run `cargo test <...args> -- --test-threads=1`.\r\n  //\r\n  // Ref: https://github.com/rust-lang/rust/issues/104053\r\n  libtest_mimic_collect::TestCollection::run();\r\n}\r\n"
  },
  {
    "path": "packages/wm-platform/src/thread_bound.rs",
    "content": "use core::{\r\n  fmt,\r\n  mem::{self, ManuallyDrop},\r\n};\r\nuse std::thread::ThreadId;\r\n\r\nuse crate::Dispatcher;\r\n\r\n/// Binds a value to the current event loop thread.\r\n///\r\n/// `ThreadBound<T>` wraps a value created on an event loop thread and\r\n/// guarantees that all access and destruction of that value happens on\r\n/// the same thread, using the provided [`Dispatcher`]. This allows the\r\n/// wrapper to be used across threads (`Send + Sync`) even when `T` itself\r\n/// is not thread-safe.\r\n///\r\n/// Inspired by:\r\n/// - `threadbound::ThreadBound` <https://github.com/dtolnay/threadbound>\r\n/// - `dispatch2::MainThreadBound` <https://github.com/madsmtm/objc2/tree/main/crates/dispatch2>\r\n///\r\n/// NOTE: Dropping the wrapper schedules the inner value to be dropped on\r\n/// the event loop thread. If the event loop has already stopped, the drop\r\n/// is skipped to avoid running `T`'s destructor on the wrong thread,\r\n/// potentially leaking the value.\r\n///\r\n/// # Example usage\r\n///\r\n/// ```no_run\r\n/// use wm_platform::{EventLoop, Dispatcher, ThreadBound};\r\n///\r\n/// # fn main() -> wm_platform::Result<()> {\r\n/// let (event_loop, dispatcher) = EventLoop::new()?;\r\n///\r\n/// // Create the value on the event loop thread.\r\n/// let bound = dispatcher.dispatch_sync(|| {\r\n///   ThreadBound::new(String::from(\"hello\"), dispatcher.clone())\r\n/// })?;\r\n///\r\n/// // Access from any thread via the dispatcher.\r\n/// let len = bound.with(|s| s.len())?;\r\n/// assert_eq!(len, 5);\r\n///\r\n/// // Direct access only works on the original thread.\r\n/// assert!(bound.get_ref().is_ok());\r\n///\r\n/// drop(bound); // Drop is scheduled on the event loop thread.\r\n/// # Ok(()) }\r\n/// ```\r\n#[derive(Clone)]\r\npub struct ThreadBound<T> {\r\n  value: ManuallyDrop<T>,\r\n  thread_id: ThreadId,\r\n  dispatcher: Dispatcher,\r\n}\r\n\r\n// SAFETY: Access to the inner value is only exposed on the event loop\r\n// thread.\r\nunsafe impl<T> Send for ThreadBound<T> {}\r\nunsafe impl<T> Sync for ThreadBound<T> {}\r\n\r\nimpl<T> ThreadBound<T> {\r\n  /// Binds a value to the current event loop thread.\r\n  ///\r\n  /// # Panics\r\n  ///\r\n  /// Panics if `dispatcher` is not tied to an event loop running on the\r\n  /// current thread.\r\n  #[inline]\r\n  pub fn new(inner: T, dispatcher: Dispatcher) -> Self {\r\n    let thread_id = std::thread::current().id();\r\n\r\n    // Ensure the dispatcher is tied to the same thread.\r\n    assert_eq!(thread_id, dispatcher.thread_id());\r\n\r\n    Self {\r\n      value: ManuallyDrop::new(inner),\r\n      thread_id,\r\n      dispatcher,\r\n    }\r\n  }\r\n\r\n  /// Returns `Ok(&T)` if called on the event loop thread.\r\n  ///\r\n  /// # Errors\r\n  ///\r\n  /// Returns `Error::NotMainThread` if called from a different thread.\r\n  #[inline]\r\n  pub fn get_ref(&self) -> crate::Result<&T> {\r\n    if self.is_event_loop_thread() {\r\n      Ok(&self.value)\r\n    } else {\r\n      Err(crate::Error::NotMainThread)\r\n    }\r\n  }\r\n\r\n  /// Returns `Ok(&mut T)` if called on the event loop thread.\r\n  ///\r\n  /// # Errors\r\n  ///\r\n  /// Returns `Error::NotMainThread` if called from a different thread.\r\n  #[inline]\r\n  pub fn get_mut(&mut self) -> crate::Result<&mut T> {\r\n    if self.is_event_loop_thread() {\r\n      Ok(&mut self.value)\r\n    } else {\r\n      Err(crate::Error::NotMainThread)\r\n    }\r\n  }\r\n\r\n  /// Consumes the wrapper and returns `Ok(T)` if called on the event loop\r\n  /// thread.\r\n  ///\r\n  /// # Errors\r\n  ///\r\n  /// Returns `Error::NotMainThread` if called from a different thread.\r\n  #[inline]\r\n  pub fn into_inner(self) -> crate::Result<T> {\r\n    if self.is_event_loop_thread() {\r\n      // Prevent `Drop` from running.\r\n      let mut this = ManuallyDrop::new(self);\r\n\r\n      // SAFETY: `self` is consumed by this function, and wrapped in\r\n      // `ManuallyDrop`, so the item's destructor is never run.\r\n      Ok(unsafe { ManuallyDrop::take(&mut this.value) })\r\n    } else {\r\n      Err(crate::Error::NotMainThread)\r\n    }\r\n  }\r\n\r\n  /// Execute a closure with `&T` on the event loop thread.\r\n  ///\r\n  /// Runs synchronously and returns the closure's result.\r\n  #[inline]\r\n  pub fn with<F, R>(&self, f: F) -> crate::Result<R>\r\n  where\r\n    F: Send + FnOnce(&T) -> R,\r\n    R: Send,\r\n  {\r\n    self.dispatcher.dispatch_sync(|| f(&self.value))\r\n  }\r\n\r\n  /// Execute a closure with `&mut T` on the event loop thread.\r\n  ///\r\n  /// Runs synchronously and returns the closure's result.\r\n  #[inline]\r\n  #[allow(\r\n    clippy::borrow_as_ptr,\r\n    clippy::ptr_as_ptr,\r\n    clippy::as_conversions\r\n  )]\r\n  pub fn with_mut<F, R>(&mut self, f: F) -> crate::Result<R>\r\n  where\r\n    F: Send + FnOnce(&mut T) -> R,\r\n    R: Send,\r\n  {\r\n    // TODO: This is pretty cursed. Should be a better way.\r\n    let value_ptr =\r\n      std::ptr::from_mut::<ManuallyDrop<T>>(&mut self.value) as usize;\r\n    self.dispatcher.dispatch_sync(|| unsafe {\r\n      // SAFETY: The closure executes on the event loop thread where the\r\n      // value was created, and we only create a unique mutable reference.\r\n      let value_mut: &mut T = &mut *(value_ptr as *mut T);\r\n      f(value_mut)\r\n    })\r\n  }\r\n\r\n  /// Returns `true` if called on the event loop thread.\r\n  #[inline]\r\n  #[must_use]\r\n  pub fn is_event_loop_thread(&self) -> bool {\r\n    std::thread::current().id() == self.thread_id\r\n  }\r\n}\r\n\r\nimpl<T> fmt::Debug for ThreadBound<T> {\r\n  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\r\n    f.debug_struct(\"ThreadBound\").finish_non_exhaustive()\r\n  }\r\n}\r\n\r\nimpl<T> Drop for ThreadBound<T> {\r\n  #[allow(\r\n    clippy::borrow_as_ptr,\r\n    clippy::ptr_as_ptr,\r\n    clippy::as_conversions,\r\n    clippy::ref_as_ptr\r\n  )]\r\n  fn drop(&mut self) {\r\n    if mem::needs_drop::<T>() {\r\n      // TODO: This is pretty cursed. Should be a better way.\r\n      let value_ptr =\r\n        std::ptr::from_mut::<ManuallyDrop<T>>(&mut self.value) as usize;\r\n\r\n      let _ = self.dispatcher.dispatch_sync(|| unsafe {\r\n        // SAFETY: The value is dropped on the event loop thread, which is\r\n        // the same thread that it originated from (guaranteed by `new`).\r\n        // Additionally, the value is never used again after this point.\r\n        ManuallyDrop::drop(&mut *(value_ptr as *mut ManuallyDrop<T>));\r\n      });\r\n    }\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-platform/src/window_listener.rs",
    "content": "use tokio::sync::mpsc;\r\n\r\nuse crate::{platform_impl, Dispatcher, WindowEvent};\r\n\r\n/// A listener for system-wide window events.\r\npub struct WindowListener {\r\n  event_rx: mpsc::UnboundedReceiver<WindowEvent>,\r\n\r\n  /// Inner platform-specific window listener.\r\n  inner: platform_impl::WindowListener,\r\n}\r\n\r\nimpl WindowListener {\r\n  /// Creates a new window listener.\r\n  pub fn new(dispatcher: &Dispatcher) -> crate::Result<Self> {\r\n    let (event_tx, event_rx) = mpsc::unbounded_channel();\r\n    let inner = platform_impl::WindowListener::new(event_tx, dispatcher)?;\r\n\r\n    Ok(Self { event_rx, inner })\r\n  }\r\n\r\n  /// Returns the next window event from the listener.\r\n  ///\r\n  /// This will block until a window event is available.\r\n  pub async fn next_event(&mut self) -> Option<WindowEvent> {\r\n    self.event_rx.recv().await\r\n  }\r\n\r\n  /// Terminates the window listener.\r\n  pub fn terminate(&mut self) {\r\n    self.inner.terminate();\r\n  }\r\n}\r\n"
  },
  {
    "path": "packages/wm-watcher/Cargo.toml",
    "content": "[package]\r\nname = \"wm-watcher\"\r\nversion = \"0.0.0\"\r\nedition = \"2021\"\r\n\r\n[[bin]]\r\nname = \"glazewm-watcher\"\r\npath = \"src/main.rs\"\r\n\r\n[build-dependencies]\r\ntauri-winres = { workspace = true }\r\n\r\n[dependencies]\r\nanyhow = { workspace = true }\r\nserde_json = { workspace = true }\r\ntokio = { workspace = true }\r\ntracing = { workspace = true }\r\ntracing-subscriber = { workspace = true }\r\nwm-common = { path = \"../wm-common\" }\r\nwm-ipc-client = { path = \"../wm-ipc-client\" }\r\nwm-platform = { path = \"../wm-platform\" }\r\n"
  },
  {
    "path": "packages/wm-watcher/build.rs",
    "content": "use tauri_winres::VersionInfo;\r\n\r\nfn main() {\r\n  if cfg!(not(target_os = \"windows\")) {\r\n    panic!(\"wm-watcher is only supported on Windows.\");\r\n  }\r\n\r\n  println!(\"cargo:rerun-if-env-changed=VERSION_NUMBER\");\r\n  let mut res = tauri_winres::WindowsResource::new();\r\n\r\n  res.set_icon(\"../../resources/assets/icon.ico\");\r\n\r\n  // Set language to English (US).\r\n  res.set_language(0x0409);\r\n\r\n  res.set(\"OriginalFilename\", \"glazewm-watcher.exe\");\r\n  res.set(\"ProductName\", \"GlazeWM Watcher\");\r\n  res.set(\"FileDescription\", \"GlazeWM Watcher\");\r\n\r\n  let version_parts = env!(\"VERSION_NUMBER\")\r\n    .split('.')\r\n    .take(3)\r\n    .map(|part| part.parse().unwrap_or(0))\r\n    .collect::<Vec<u16>>();\r\n\r\n  let [major, minor, patch] =\r\n    <[u16; 3]>::try_from(version_parts).unwrap_or([0, 0, 0]);\r\n\r\n  let version_str = format!(\"{major}.{minor}.{patch}.0\");\r\n  res.set(\"FileVersion\", &version_str);\r\n  res.set(\"ProductVersion\", &version_str);\r\n\r\n  let version_u64 = (u64::from(major) << 48)\r\n    | (u64::from(minor) << 32)\r\n    | (u64::from(patch) << 16);\r\n\r\n  res.set_version_info(VersionInfo::FILEVERSION, version_u64);\r\n  res.set_version_info(VersionInfo::PRODUCTVERSION, version_u64);\r\n\r\n  res.compile().unwrap();\r\n}\r\n"
  },
  {
    "path": "packages/wm-watcher/src/main.rs",
    "content": "// The `windows` or `console` subsystem (default is `console`) determines\r\n// whether a console window is spawned on launch, if not already ran\r\n// through a console. The following prevents this additional console window\r\n// in release mode.\r\n#![cfg_attr(not(debug_assertions), windows_subsystem = \"windows\")]\r\n#![warn(clippy::all, clippy::pedantic)]\r\n\r\nuse anyhow::Context;\r\nuse wm_common::{ClientResponseData, ContainerDto, WindowDto, WmEvent};\r\nuse wm_ipc_client::IpcClient;\r\nuse wm_platform::{NativeWindow, NativeWindowWindowsExt, OpacityValue};\r\n\r\n#[tokio::main]\r\nasync fn main() -> anyhow::Result<()> {\r\n  tracing_subscriber::fmt().init();\r\n\r\n  let mut client = IpcClient::connect().await?;\r\n\r\n  // Get handles to windows that are already open on watcher launch.\r\n  let mut managed_handles = query_initial_windows(&mut client)\r\n    .await?\r\n    .into_iter()\r\n    .map(|window| window.handle)\r\n    .collect::<Vec<_>>();\r\n\r\n  // Update window handles on window manage/unmanage events.\r\n  let subscribe_res =\r\n    watch_managed_handles(&mut client, &mut managed_handles).await;\r\n\r\n  match subscribe_res {\r\n    Ok(()) => {\r\n      tracing::info!(\"WM exited successfully. Skipping watcher cleanup.\");\r\n    }\r\n    Err(err) => {\r\n      tracing::info!(\r\n        \"Running watcher cleanup. WM exited unexpectedly: {}\",\r\n        err\r\n      );\r\n\r\n      let managed_windows =\r\n        managed_handles.into_iter().map(NativeWindow::from_handle);\r\n\r\n      for window in managed_windows {\r\n        if let Err(err) = window.show() {\r\n          tracing::warn!(\"Failed to show window: {:?}\", err);\r\n        }\r\n\r\n        let _ = window.set_taskbar_visibility(true);\r\n        let _ = window.set_border_color(None);\r\n        let _ =\r\n          window.set_transparency(&OpacityValue::from_alpha(u8::MAX));\r\n      }\r\n    }\r\n  }\r\n\r\n  Ok(())\r\n}\r\n\r\nasync fn query_initial_windows(\r\n  client: &mut IpcClient,\r\n) -> anyhow::Result<Vec<WindowDto>> {\r\n  let query_message = \"query windows\";\r\n\r\n  client\r\n    .send(query_message)\r\n    .await\r\n    .context(\"Failed to send window query command.\")?;\r\n\r\n  client\r\n    .client_response(query_message)\r\n    .await\r\n    .and_then(|response| match response.data {\r\n      Some(ClientResponseData::Windows(data)) => Some(data),\r\n      _ => None,\r\n    })\r\n    .map(|data| {\r\n      data\r\n        .windows\r\n        .into_iter()\r\n        .filter_map(|container| match container {\r\n          ContainerDto::Window(window) => Some(window),\r\n          _ => None,\r\n        })\r\n        .collect::<Vec<_>>()\r\n    })\r\n    .context(\"Invalid data in windows query response.\")\r\n}\r\n\r\nasync fn watch_managed_handles(\r\n  client: &mut IpcClient,\r\n  handles: &mut Vec<isize>,\r\n) -> anyhow::Result<()> {\r\n  let subscription_message =\r\n    \"sub -e window_managed window_unmanaged application_exiting\";\r\n\r\n  client\r\n    .send(subscription_message)\r\n    .await\r\n    .context(\"Failed to send subscribe command to IPC server.\")?;\r\n\r\n  let subscription_id = client\r\n    .client_response(subscription_message)\r\n    .await\r\n    .and_then(|response| match response.data {\r\n      Some(ClientResponseData::EventSubscribe(data)) => {\r\n        Some(data.subscription_id)\r\n      }\r\n      _ => None,\r\n    })\r\n    .context(\"No subscription ID in watcher event subscription.\")?;\r\n\r\n  loop {\r\n    let event_data = client\r\n      .event_subscription(&subscription_id)\r\n      .await\r\n      .and_then(|event| event.data);\r\n\r\n    match event_data {\r\n      Some(WmEvent::WindowManaged { managed_window }) => {\r\n        if let ContainerDto::Window(window) = managed_window {\r\n          tracing::info!(\"Watcher added handle: {}.\", window.handle);\r\n          handles.push(window.handle);\r\n        }\r\n      }\r\n      Some(WmEvent::WindowUnmanaged {\r\n        unmanaged_handle, ..\r\n      }) => {\r\n        tracing::info!(\"Watcher removed handle: {}.\", unmanaged_handle);\r\n        handles.retain(|&handle| handle != unmanaged_handle);\r\n      }\r\n      Some(WmEvent::ApplicationExiting) => {\r\n        return Ok(());\r\n      }\r\n      Some(_) => unreachable!(),\r\n      None => {\r\n        anyhow::bail!(\"IPC connection closed unexpectedly.\")\r\n      }\r\n    }\r\n  }\r\n}\r\n"
  },
  {
    "path": "resources/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\r\n<plist version=\"1.0\">\r\n<dict>\r\n    <key>CFBundleName</key>\r\n    <string>GlazeWM</string>\r\n\r\n    <key>CFBundleDisplayName</key>\r\n    <string>GlazeWM</string>\r\n\r\n    <key>CFBundleIdentifier</key>\r\n    <string>io.glzr.glazewm</string>\r\n\r\n    <key>CFBundleVersion</key>\r\n    <string>${VERSION}</string>\r\n\r\n    <key>CFBundleShortVersionString</key>\r\n    <string>${VERSION}</string>\r\n\r\n    <key>CFBundlePackageType</key>\r\n    <string>APPL</string>\r\n\r\n    <key>CFBundleSignature</key>\r\n    <string>????</string>\r\n\r\n    <key>CFBundleExecutable</key>\r\n    <string>glazewm</string>\r\n\r\n    <key>CFBundleIconFile</key>\r\n    <string>icon</string>\r\n\r\n    <key>CFBundleInfoDictionaryVersion</key>\r\n    <string>6.0</string>\r\n\r\n    <key>LSMinimumSystemVersion</key>\r\n    <string>10.13</string>\r\n\r\n    <key>NSHighResolutionCapable</key>\r\n    <true/>\r\n\r\n    <key>LSApplicationCategoryType</key>\r\n    <string>public.app-category.utilities</string>\r\n\r\n    <key>NSHumanReadableCopyright</key>\r\n    <string>Copyright © glzr-io. All rights reserved.</string>\r\n\r\n    <!-- Run as background/agent app (no dock icon). -->\r\n    <key>LSUIElement</key>\r\n    <true/>\r\n\r\n    <!-- Description for why accessibility permissions are needed. -->\r\n    <key>NSAccessibilityUsageDescription</key>\r\n    <string>GlazeWM requires accessibility permissions to manage and arrange windows.</string>\r\n\r\n    <key>LSArchitecturePriority</key>\r\n    <array>\r\n        <string>arm64</string>\r\n        <string>x86_64</string>\r\n    </array>\r\n</dict>\r\n</plist>\r\n"
  },
  {
    "path": "resources/assets/sample-config.yaml",
    "content": "general:\r\n  # Commands to run when the WM has started. This is useful for running a\r\n  # script or launching another application.\r\n  # Example: The below command launches Zebar.\r\n  startup_commands: ['shell-exec zebar']\r\n\r\n  # Commands to run just before the WM is shutdown.\r\n  # Example: The below command kills Zebar.\r\n  shutdown_commands: ['shell-exec taskkill /IM zebar.exe /F']\r\n\r\n  # Commands to run after the WM config is reloaded.\r\n  config_reload_commands: []\r\n\r\n  # Whether to automatically focus windows underneath the cursor.\r\n  focus_follows_cursor: false\r\n\r\n  # Whether to switch back and forth between the previously focused\r\n  # workspace when focusing the current workspace.\r\n  toggle_workspace_on_refocus: false\r\n\r\n  cursor_jump:\r\n    # Whether to automatically move the cursor on the specified trigger.\r\n    enabled: true\r\n\r\n    # Trigger for cursor jump:\r\n    # - 'monitor_focus': Jump when focus changes between monitors.\r\n    # - 'window_focus': Jump when focus changes between windows.\r\n    trigger: 'monitor_focus'\r\n\r\n  # How windows should be hidden when switching workspaces.\r\n  # - 'cloak': (Windows-only) Recommended option for Windows.\r\n  # - 'hide': (Windows-only) Legacy option for Windows. Has stability issues with some apps.\r\n  # - 'place_in_corner': Artifically hides the window by placing it in the corner of the\r\n  #   monitor. On macOS, this is always used instead of cloak/hide.\r\n  hide_method: 'cloak'\r\n\r\n  # Affects which windows get shown in the native Windows taskbar. Has no\r\n  # effect if `hide_method: 'hide'`.\r\n  # - 'true': Show all windows (regardless of workspace).\r\n  # - 'false': Only show windows from the currently shown workspaces.\r\n  show_all_in_taskbar: false\r\n\r\ngaps:\r\n  # Whether to scale the gaps with the DPI of the monitor.\r\n  scale_with_dpi: true\r\n\r\n  # Gap between adjacent windows.\r\n  inner_gap: '20px'\r\n\r\n  # Gap between windows and the screen edge.\r\n  outer_gap:\r\n    top: '60px'\r\n    right: '20px'\r\n    bottom: '20px'\r\n    left: '20px'\r\n\r\nwindow_effects:\r\n  # Visual effects to apply to the focused window.\r\n  focused_window:\r\n    # Highlight the window with a colored border.\r\n    # ** Exclusive to Windows 11 due to API limitations.\r\n    border:\r\n      enabled: true\r\n      color: '#8dbcff'\r\n\r\n    # Remove the title bar from the window's frame. Note that this can\r\n    # cause rendering issues for some applications.\r\n    hide_title_bar:\r\n      enabled: false\r\n\r\n    # Change the corner style of the window's frame.\r\n    # ** Exclusive to Windows 11 due to API limitations.\r\n    corner_style:\r\n      enabled: false\r\n      # Allowed values: 'square', 'rounded', 'small_rounded'.\r\n      style: 'square'\r\n\r\n    # Change the transparency of the window.\r\n    transparency:\r\n      enabled: false\r\n      # Can be something like '95%' or '0.95' for slightly transparent windows.\r\n      # '0' or '0%' is fully transparent (and, by consequence, unfocusable).\r\n      opacity: '95%'\r\n\r\n  # Visual effects to apply to non-focused windows.\r\n  other_windows:\r\n    border:\r\n      enabled: true\r\n      color: '#a1a1a1'\r\n    hide_title_bar:\r\n      enabled: false\r\n    corner_style:\r\n      enabled: false\r\n      style: 'square'\r\n    transparency:\r\n      enabled: false\r\n      opacity: '0%'\r\n\r\nwindow_behavior:\r\n  # New windows are created in this state whenever possible.\r\n  # Allowed values: 'tiling', 'floating'.\r\n  initial_state: 'tiling'\r\n\r\n  # Sets the default options for when a new window is created. This also\r\n  # changes the defaults for when the state change commands, like\r\n  # `set-floating`, are used without any flags.\r\n  state_defaults:\r\n    floating:\r\n      # Whether to center floating windows by default.\r\n      centered: true\r\n\r\n      # Whether to show floating windows as always on top.\r\n      shown_on_top: false\r\n\r\n    fullscreen:\r\n      # Maximize the window if possible. If the window doesn't have a\r\n      # maximize button, then it'll be fullscreen'ed normally instead.\r\n      maximized: false\r\n\r\n      # Whether to show fullscreen windows as always on top.\r\n      shown_on_top: false\r\n\r\nworkspaces:\r\n  - name: '1'\r\n  - name: '2'\r\n  - name: '3'\r\n  - name: '4'\r\n  - name: '5'\r\n  - name: '6'\r\n  - name: '7'\r\n  - name: '8'\r\n  - name: '9'\r\n\r\nwindow_rules:\r\n  - commands: ['ignore']\r\n    match:\r\n      # Ignores any Zebar windows.\r\n      - window_process: { equals: 'zebar' }\r\n\r\n      # Ignores picture-in-picture windows for browsers.\r\n      - window_title: { regex: '[Pp]icture.in.[Pp]icture' }\r\n        window_class: { regex: 'Chrome_WidgetWin_1|MozillaDialogClass' }\r\n\r\n      # Ignore rules for various 3rd-party apps.\r\n      - window_process: { equals: 'PowerToys' }\r\n        window_class: { regex: 'HwndWrapper\\[PowerToys\\.PowerAccent.*?\\]' }\r\n      - window_title: { equals: 'Command Palette' }\r\n        window_class: { equals: 'WinUIDesktopWin32WindowClass' }\r\n      - window_process: { equals: 'PowerToys' }\r\n        window_title: { regex: '.*? - Peek' }\r\n      - window_process: { equals: 'Lively' }\r\n        window_class: { regex: 'HwndWrapper' }\r\n      - window_process: { equals: 'EXCEL' }\r\n        window_class: { not_regex: 'XLMAIN' }\r\n      - window_process: { equals: 'WINWORD' }\r\n        window_class: { not_regex: 'OpusApp' }\r\n      - window_process: { equals: 'POWERPNT' }\r\n        window_class: { not_regex: 'PPTFrameClass' }\r\n\r\nbinding_modes:\r\n  # When enabled, the focused window can be resized via arrow keys or HJKL.\r\n  - name: 'resize'\r\n    keybindings:\r\n      - commands: ['resize --width -2%']\r\n        bindings: ['h', 'left']\r\n      - commands: ['resize --width +2%']\r\n        bindings: ['l', 'right']\r\n      - commands: ['resize --height +2%']\r\n        bindings: ['k', 'up']\r\n      - commands: ['resize --height -2%']\r\n        bindings: ['j', 'down']\r\n      # Press enter/escape to return to default keybindings.\r\n      - commands: ['wm-disable-binding-mode --name resize']\r\n        bindings: ['escape', 'enter']\r\n\r\nkeybindings:\r\n  # Shift focus in a given direction.\r\n  - commands: ['focus --direction left']\r\n    bindings: ['alt+h', 'alt+left']\r\n  - commands: ['focus --direction right']\r\n    bindings: ['alt+l', 'alt+right']\r\n  - commands: ['focus --direction up']\r\n    bindings: ['alt+k', 'alt+up']\r\n  - commands: ['focus --direction down']\r\n    bindings: ['alt+j', 'alt+down']\r\n\r\n  # Move focused window in a given direction.\r\n  - commands: ['move --direction left']\r\n    bindings: ['alt+shift+h', 'alt+shift+left']\r\n  - commands: ['move --direction right']\r\n    bindings: ['alt+shift+l', 'alt+shift+right']\r\n  - commands: ['move --direction up']\r\n    bindings: ['alt+shift+k', 'alt+shift+up']\r\n  - commands: ['move --direction down']\r\n    bindings: ['alt+shift+j', 'alt+shift+down']\r\n\r\n  # Resize focused window by a percentage or pixel amount.\r\n  - commands: ['resize --width -2%']\r\n    bindings: ['alt+u']\r\n  - commands: ['resize --width +2%']\r\n    bindings: ['alt+p']\r\n  - commands: ['resize --height +2%']\r\n    bindings: ['alt+o']\r\n  - commands: ['resize --height -2%']\r\n    bindings: ['alt+i']\r\n\r\n  # As an alternative to the resize keybindings above, resize mode enables\r\n  # resizing via arrow keys or HJKL. The binding mode is defined above with\r\n  # the name 'resize'.\r\n  - commands: ['wm-enable-binding-mode --name resize']\r\n    bindings: ['alt+r']\r\n\r\n  # Disables window management and all other keybindings until alt+shift+p\r\n  # is pressed again.\r\n  - commands: ['wm-toggle-pause']\r\n    bindings: ['alt+shift+p']\r\n\r\n  # Change tiling direction. This determines where new tiling windows will\r\n  # be inserted.\r\n  - commands: ['toggle-tiling-direction']\r\n    bindings: ['alt+v']\r\n\r\n  # Change focus from tiling windows -> floating -> fullscreen.\r\n  - commands: ['wm-cycle-focus']\r\n    bindings: ['alt+space']\r\n\r\n  # Change the focused window to be floating.\r\n  - commands: ['toggle-floating --centered']\r\n    bindings: ['alt+shift+space']\r\n\r\n  # Change the focused window to be tiling.\r\n  - commands: ['toggle-tiling']\r\n    bindings: ['alt+t']\r\n\r\n  # Change the focused window to be fullscreen.\r\n  - commands: ['toggle-fullscreen']\r\n    bindings: ['alt+f']\r\n\r\n  # Minimize focused window.\r\n  - commands: ['toggle-minimized']\r\n    bindings: ['alt+m']\r\n\r\n  # Close focused window.\r\n  - commands: ['close']\r\n    bindings: ['alt+shift+q']\r\n\r\n  # Kill GlazeWM process safely.\r\n  - commands: ['wm-exit']\r\n    bindings: ['alt+shift+e']\r\n\r\n  # Re-evaluate configuration file.\r\n  - commands: ['wm-reload-config']\r\n    bindings: ['alt+shift+r']\r\n\r\n  # Redraw all windows.\r\n  - commands: ['wm-redraw']\r\n    bindings: ['alt+shift+w']\r\n\r\n  # Launch CMD terminal. Alternatively, use `shell-exec wt` or\r\n  # `shell-exec %ProgramFiles%/Git/git-bash.exe` to start Windows\r\n  # Terminal and Git Bash respectively.\r\n  - commands: ['shell-exec cmd']\r\n    bindings: ['alt+enter']\r\n\r\n  # Focus the next/previous active workspace defined in `workspaces` config.\r\n  - commands: ['focus --next-active-workspace']\r\n    bindings: ['alt+s']\r\n  - commands: ['focus --prev-active-workspace']\r\n    bindings: ['alt+a']\r\n\r\n  # Focus the workspace that last had focus.\r\n  - commands: ['focus --recent-workspace']\r\n    bindings: ['alt+d']\r\n\r\n  # Change focus to a workspace defined in `workspaces` config.\r\n  - commands: ['focus --workspace 1']\r\n    bindings: ['alt+1']\r\n  - commands: ['focus --workspace 2']\r\n    bindings: ['alt+2']\r\n  - commands: ['focus --workspace 3']\r\n    bindings: ['alt+3']\r\n  - commands: ['focus --workspace 4']\r\n    bindings: ['alt+4']\r\n  - commands: ['focus --workspace 5']\r\n    bindings: ['alt+5']\r\n  - commands: ['focus --workspace 6']\r\n    bindings: ['alt+6']\r\n  - commands: ['focus --workspace 7']\r\n    bindings: ['alt+7']\r\n  - commands: ['focus --workspace 8']\r\n    bindings: ['alt+8']\r\n  - commands: ['focus --workspace 9']\r\n    bindings: ['alt+9']\r\n\r\n  # Move the focused window's parent workspace to a monitor in a given\r\n  # direction.\r\n  - commands: ['move-workspace --direction left']\r\n    bindings: ['alt+shift+a']\r\n  - commands: ['move-workspace --direction right']\r\n    bindings: ['alt+shift+f']\r\n  - commands: ['move-workspace --direction up']\r\n    bindings: ['alt+shift+d']\r\n  - commands: ['move-workspace --direction down']\r\n    bindings: ['alt+shift+s']\r\n\r\n  # Move focused window to a workspace defined in `workspaces` config.\r\n  - commands: ['move --workspace 1', 'focus --workspace 1']\r\n    bindings: ['alt+shift+1']\r\n  - commands: ['move --workspace 2', 'focus --workspace 2']\r\n    bindings: ['alt+shift+2']\r\n  - commands: ['move --workspace 3', 'focus --workspace 3']\r\n    bindings: ['alt+shift+3']\r\n  - commands: ['move --workspace 4', 'focus --workspace 4']\r\n    bindings: ['alt+shift+4']\r\n  - commands: ['move --workspace 5', 'focus --workspace 5']\r\n    bindings: ['alt+shift+5']\r\n  - commands: ['move --workspace 6', 'focus --workspace 6']\r\n    bindings: ['alt+shift+6']\r\n  - commands: ['move --workspace 7', 'focus --workspace 7']\r\n    bindings: ['alt+shift+7']\r\n  - commands: ['move --workspace 8', 'focus --workspace 8']\r\n    bindings: ['alt+shift+8']\r\n  - commands: ['move --workspace 9', 'focus --workspace 9']\r\n    bindings: ['alt+shift+9']\r\n"
  },
  {
    "path": "resources/scripts/package.ps1",
    "content": "# Usage: ./resources/scripts/package.ps1 -VersionNumber 1.0.0\r\nparam(\r\n  [Parameter(Mandatory=$true)]\r\n  [string]$VersionNumber\r\n)\r\n\r\nfunction ExitOnError() {\r\n  if ($LASTEXITCODE -ne 0) {\r\n    Exit 1\r\n  }\r\n}\r\n\r\nfunction SignFiles() {\r\n  param(\r\n    [Parameter(Mandatory)]\r\n    [string[]]$filePaths\r\n  )\r\n\r\n  if (!(Get-Command \"azuresigntool\" -ErrorAction SilentlyContinue)) {\r\n    Write-Output \"Skipping signing because AzureSignTool is not installed.\"\r\n    Return\r\n  }\r\n\r\n  $secrets = @(\r\n    \"AZ_VAULT_URL\",\r\n    \"AZ_CERT_NAME\",\r\n    \"AZ_CLIENT_ID\",\r\n    \"AZ_CLIENT_SECRET\",\r\n    \"AZ_TENANT_ID\",\r\n    \"RFC3161_TIMESTAMP_URL\"\r\n  )\r\n\r\n  foreach ($secret in $secrets) {\r\n    if (!(Test-Path \"env:$secret\")) {\r\n      Write-Output \"Skipping signing due to missing secret '$secret'.\"\r\n      Return\r\n    }\r\n  }\r\n\r\n  Write-Output \"Signing $filePaths.\"\r\n  azuresigntool sign -kvu $ENV:AZ_VAULT_URL `\r\n    -kvc $ENV:AZ_CERT_NAME `\r\n    -kvi $ENV:AZ_CLIENT_ID `\r\n    -kvs $ENV:AZ_CLIENT_SECRET `\r\n    -kvt $ENV:AZ_TENANT_ID `\r\n    -tr $ENV:RFC3161_TIMESTAMP_URL `\r\n    -td sha256 $filePaths\r\n\r\n  ExitOnError\r\n}\r\n\r\nfunction DownloadZebarInstallers() {\r\n  Write-Output \"Downloading latest Zebar MSI's\"\r\n\r\n  $latestRelease = 'https://api.github.com/repos/glzr-io/zebar/releases/latest'\r\n  $latestInstallers = Invoke-RestMethod $latestRelease | % assets | ? name -like \"*.msi\"\r\n\r\n  $latestInstallers | ForEach-Object {\r\n    $outFile = Join-Path \"out\" $_.name\r\n\r\n    # Rename the MSI files (e.g. `zebar-1.5.0-opt1-x64.msi` -> `zebar-x64.msi`).\r\n    if ($_.name -like \"*-x64.msi\") {\r\n      $outFile = \"out/zebar-x64.msi\"\r\n    }\r\n    elseif ($_.name -like \"*-arm64.msi\") {\r\n      $outFile = \"out/zebar-arm64.msi\"\r\n    }\r\n\r\n    Invoke-WebRequest $_.browser_download_url -OutFile $outFile\r\n  }\r\n}\r\n\r\nfunction BuildExes() {\r\n  # Rust targets to build for (x64 and arm64).\r\n  $rustTargets = @(\"x86_64-pc-windows-msvc\", \"aarch64-pc-windows-msvc\")\r\n\r\n  # Set the version number as an environment variable for `cargo build`.\r\n  $env:VERSION_NUMBER = $VersionNumber\r\n\r\n  foreach ($target in $rustTargets) {\r\n    $outDir = if ($target -eq \"x86_64-pc-windows-msvc\") { \"out/x64\" } else { \"out/arm64\" }\r\n    $sourceDir = \"target/$target/release\"\r\n\r\n    $requiredExes = @(\"glazewm.exe\", \"glazewm-cli.exe\", \"glazewm-watcher.exe\")\r\n    $sourcePaths = $requiredExes | ForEach-Object { \"$sourceDir/$_\" }\r\n\r\n    # Build for the target if the executables do not exist.\r\n    if (($sourcePaths | Where-Object { !(Test-Path $_) }).Count -gt 0) {\r\n      Write-Output \"Build artifact not found for target '$target'. Building now...\"\r\n\r\n      cargo build --locked --release --target $target --features ui_access\r\n      ExitOnError\r\n\r\n      Write-Output \"Build completed successfully for target '$target'.\"\r\n    }\r\n\r\n    Write-Output \"Moving built executables from $sourceDir to $outDir\"\r\n    New-Item -ItemType Directory -Force -Path $outDir\r\n    Move-Item -Force -Path $sourcePaths -Destination $outDir\r\n\r\n    $outPaths = $requiredExes | ForEach-Object { \"$outDir/$_\" }\r\n    SignFiles $outPaths\r\n  }\r\n}\r\n\r\nfunction BuildInstallers() {\r\n  # WiX architectures to create installers for (x64 and arm64).\r\n  $wixArchs = @(\"x64\", \"arm64\")\r\n\r\n  foreach ($arch in $wixArchs) {\r\n    Write-Output \"Creating MSI installer ($arch)\"\r\n    wix build -arch $arch -ext WixToolset.UI.wixext -ext WixToolset.Util.wixext `\r\n      -out \"./out/installer-$arch.msi\" \"./resources/wix/standalone.wxs\" \"./resources/wix/standalone-ui.wxs\" `\r\n      -d VERSION_NUMBER=\"$VersionNumber\" `\r\n      -d EXE_DIR=\"out/$arch\"\r\n  }\r\n\r\n  SignFiles @(\"out/installer-x64.msi\", \"out/installer-arm64.msi\")\r\n\r\n  Write-Output \"Creating universal installer\"\r\n  wix build -arch \"x64\" -ext WixToolset.BootstrapperApplications.wixext `\r\n    -out \"./out/unsigned-installer-universal.exe\" \"./resources/wix/bundle.wxs\" `\r\n    -d VERSION_NUMBER=\"$VersionNumber\"\r\n\r\n  Write-Output \"Detaching & reattaching Burn engine for signing\"\r\n  wix burn detach \"./out/unsigned-installer-universal.exe\" -engine \"./out/engine.exe\"\r\n  SignFiles @(\"out/engine.exe\")\r\n\r\n  wix burn reattach \"./out/unsigned-installer-universal.exe\" `\r\n    -engine \"./out/engine.exe\" `\r\n    -o \"./out/installer-universal.exe\"\r\n\r\n  SignFiles @(\"out/installer-universal.exe\")\r\n}\r\n\r\nfunction Package() {\r\n  Write-Output \"Packaging with version number: $VersionNumber\"\r\n\r\n  Write-Output \"Creating output directory\"\r\n  New-Item -ItemType Directory -Force -Path \"out\"\r\n\r\n  DownloadZebarInstallers\r\n  BuildExes\r\n  BuildInstallers\r\n}\r\n\r\nPackage\r\n"
  },
  {
    "path": "resources/wix/bundle-theme.wxl",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<!--\r\n  This is a modification of `HyperlinkSidebarTheme.wxl`.\r\n  Ref: https://github.com/wixtoolset/wix/blob/v5.0.0/src/ext/Bal/stdbas/Resources/HyperlinkTheme.wxl\r\n-->\r\n<WixLocalization Culture=\"en-us\" Language=\"1033\" xmlns=\"http://wixtoolset.org/schemas/v4/wxl\">\r\n  <String Id=\"Caption\" Value=\"[WixBundleName] Setup\" />\r\n  <String Id=\"Title\" Value=\"[WixBundleName]\" />\r\n  <String Id=\"CheckingForUpdatesLabel\" Value=\"Checking for updates\" />\r\n  <String Id=\"UpdateButton\" Value=\"&amp;Update to version [WixStdBAUpdateAvailable]\" />\r\n  <String Id=\"InstallHeader\" Value=\"Welcome\" />\r\n  <String Id=\"InstallMessage\" Value=\"Setup will install [WixBundleName] on your computer. Click Install to continue or Cancel to exit.\" />\r\n  <String Id=\"InstallMessageOptions\" Value=\"Setup will install [WixBundleName] on your computer. Click Install to continue, Options to set installation options, or Cancel to exit.\" />\r\n  <String Id=\"InstallVersion\" Value=\"Version [WixBundleVersion]\" />\r\n  <String Id=\"CustomInstallGlazeWM\" Value=\"Install GlazeWM\" />\r\n  <String Id=\"CustomInstallGlazeWMHint\" Value=\"&lt;a href=&quot;https://github.com/glzr-io/glazewm&quot;&gt;GlazeWM&lt;/a&gt; is a tiling window manager for Windows.\" />\r\n  <String Id=\"CustomInstallZebar\" Value=\"Install Zebar (recommended)\" />\r\n  <String Id=\"CustomInstallZebarHint1\" Value=\"&lt;a href=&quot;https://github.com/glzr-io/zebar&quot;&gt;Zebar&lt;/a&gt; is a lightweight tool for custom widgets.\" />\r\n  <String Id=\"CustomInstallZebarHint2\" Value=\"Adds a starter config for showing GlazeWM workspaces.\" />\r\n  <String Id=\"ConfirmCancelMessage\" Value=\"Are you sure you want to cancel?\" />\r\n  <String Id=\"ExecuteUpgradeRelatedBundleMessage\" Value=\"Previous version\" />\r\n  <String Id=\"HelpHeader\" Value=\"Setup Help\" />\r\n  <String Id=\"HelpText\" Value=\"/install | /repair | /uninstall | /layout [directory] - installs, repairs, uninstalls or&#xA;   creates a complete local copy of the bundle in directory. Install is the default.&#xA;&#xA;/passive | /quiet -  displays minimal UI with no prompts or displays no UI and&#xA;   no prompts. By default UI and all prompts are displayed.&#xA;&#xA;/norestart   - suppress any attempts to restart. By default UI will prompt before restart.&#xA;/log log.txt - logs to a specific file. By default a log file is created in %TEMP%.\" />\r\n  <String Id=\"HelpCloseButton\" Value=\"&amp;Close\" />\r\n  <String Id=\"InstallLicenseLinkText\" Value=\"[WixBundleName] &lt;a href=&quot;#&quot;&gt;license terms&lt;/a&gt;.\" />\r\n  <String Id=\"InstallAcceptCheckbox\" Value=\"I &amp;agree to the license terms and conditions\" />\r\n  <String Id=\"InstallOptionsButton\" Value=\"&amp;Options\" />\r\n  <String Id=\"InstallInstallButton\" Value=\"&amp;Install\" />\r\n  <String Id=\"InstallCancelButton\" Value=\"&amp;Cancel\" />\r\n  <String Id=\"OptionsHeader\" Value=\"Setup Options\" />\r\n  <String Id=\"OptionsLocationLabel\" Value=\"Install location:\" />\r\n  <String Id=\"OptionsBrowseButton\" Value=\"&amp;Browse\" />\r\n  <String Id=\"OptionsOkButton\" Value=\"&amp;OK\" />\r\n  <String Id=\"OptionsCancelButton\" Value=\"&amp;Cancel\" />\r\n  <String Id=\"ProgressHeader\" Value=\"Setup Progress\" />\r\n  <String Id=\"ProgressLabel\" Value=\"Processing:\" />\r\n  <String Id=\"OverallProgressPackageText\" Value=\"Initializing...\" />\r\n  <String Id=\"ProgressCancelButton\" Value=\"&amp;Cancel\" />\r\n  <String Id=\"ModifyHeader\" Value=\"Modify Setup\" />\r\n  <String Id=\"ModifyRepairButton\" Value=\"&amp;Repair\" />\r\n  <String Id=\"ModifyUninstallButton\" Value=\"&amp;Uninstall\" />\r\n  <String Id=\"ModifyCancelButton\" Value=\"&amp;Cancel\" />\r\n  <String Id=\"SuccessHeader\" Value=\"Setup Successful\" />\r\n  <String Id=\"SuccessCacheHeader\" Value=\"Cache Successfully Completed\" />\r\n  <String Id=\"SuccessInstallHeader\" Value=\"Installation Successfully Completed\" />\r\n  <String Id=\"SuccessLayoutHeader\" Value=\"Layout Successfully Completed\" />\r\n  <String Id=\"SuccessModifyHeader\" Value=\"Modify Successfully Completed\" />\r\n  <String Id=\"SuccessRepairHeader\" Value=\"Repair Successfully Completed\" />\r\n  <String Id=\"SuccessUninstallHeader\" Value=\"Uninstall Successfully Completed\" />\r\n  <String Id=\"SuccessUnsafeUninstallHeader\" Value=\"Uninstall Successfully Completed\" />\r\n  <String Id=\"SuccessLaunchButton\" Value=\"&amp;Launch\" />\r\n  <String Id=\"SuccessRestartText\" Value=\"You must restart your computer before you can use the software.\" />\r\n  <String Id=\"SuccessUninstallRestartText\" Value=\"You must restart your computer to complete the removal of the software.\" />\r\n  <String Id=\"SuccessRestartButton\" Value=\"&amp;Restart\" />\r\n  <String Id=\"SuccessCloseButton\" Value=\"&amp;Close\" />\r\n  <String Id=\"FailureHeader\" Value=\"Setup Failed\" />\r\n  <String Id=\"FailureCacheHeader\" Value=\"Cache Failed\" />\r\n  <String Id=\"FailureInstallHeader\" Value=\"Setup Failed\" />\r\n  <String Id=\"FailureLayoutHeader\" Value=\"Layout Failed\" />\r\n  <String Id=\"FailureModifyHeader\" Value=\"Modify Failed\" />\r\n  <String Id=\"FailureRepairHeader\" Value=\"Repair Failed\" />\r\n  <String Id=\"FailureUninstallHeader\" Value=\"Uninstall Failed\" />\r\n  <String Id=\"FailureUnsafeUninstallHeader\" Value=\"Uninstall Failed\" />\r\n  <String Id=\"FailureHyperlinkLogText\" Value=\"One or more issues caused the setup to fail. Please fix the issues and then retry setup. For more information see the &lt;a href=&quot;#&quot;&gt;log file&lt;/a&gt;.\" />\r\n  <String Id=\"FailureRestartText\" Value=\"You must restart your computer to complete the rollback of the software.\" />\r\n  <String Id=\"FailureRestartButton\" Value=\"&amp;Restart\" />\r\n  <String Id=\"FailureCloseButton\" Value=\"&amp;Close\" />\r\n  <String Id=\"FilesInUseTitle\" Value=\"Files In Use\" />\r\n  <String Id=\"FilesInUseLabel\" Value=\"The following applications are using files that need to be updated:\" />\r\n  <String Id=\"FilesInUseNetfxCloseRadioButton\" Value=\"Close the &amp;applications.\" />\r\n  <String Id=\"FilesInUseCloseRadioButton\" Value=\"Close the &amp;applications and attempt to restart them.\" />\r\n  <String Id=\"FilesInUseDontCloseRadioButton\" Value=\"&amp;Do not close applications. A reboot will be required.\" />\r\n  <String Id=\"FilesInUseRetryButton\" Value=\"&amp;Retry\" />\r\n  <String Id=\"FilesInUseIgnoreButton\" Value=\"&amp;Ignore\" />\r\n  <String Id=\"FilesInUseExitButton\" Value=\"E&amp;xit\" />\r\n</WixLocalization>\r\n"
  },
  {
    "path": "resources/wix/bundle-theme.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\r\n<!--\r\n  This is a modification of `HyperlinkSidebarTheme.xml`.\r\n  Ref: https://github.com/wixtoolset/wix/blob/v5.0.0/src/ext/Bal/stdbas/Resources/HyperlinkSidebarTheme.xml\r\n-->\r\n<Theme xmlns=\"http://wixtoolset.org/schemas/v4/thmutil\">\r\n  <Font Id=\"0\" Height=\"-12\" Weight=\"500\" Foreground=\"windowtext\" Background=\"window\">Segoe UI</Font>\r\n  <Font Id=\"1\" Height=\"-24\" Weight=\"500\" Foreground=\"windowtext\">Segoe UI</Font>\r\n  <Font Id=\"2\" Height=\"-22\" Weight=\"500\" Foreground=\"graytext\">Segoe UI</Font>\r\n  <Font Id=\"3\" Height=\"-12\" Weight=\"500\" Foreground=\"windowtext\" Background=\"window\">Segoe UI</Font>\r\n\r\n  <Font Id=\"4\" Height=\"-12\" Weight=\"500\" Foreground=\"ff0000\" Background=\"window\" Underline=\"yes\">Segoe UI</Font>\r\n  <Font Id=\"5\" Height=\"-28\" Weight=\"500\" Foreground=\"graytext\">Segoe UI</Font>\r\n  <Font Id=\"6\" Height=\"-12\" Weight=\"600\" Foreground=\"windowtext\" Background=\"window\">Segoe UI</Font>\r\n\r\n  <!-- Divider -->\r\n  <Font Id=\"7\" Height=\"-24\" Weight=\"500\" Foreground=\"windowtext\" Background=\"E0E0E0\">Segoe UI</Font>\r\n\r\n  <Window Width=\"600\" Height=\"450\" HexStyle=\"100a0000\" FontId=\"0\" Caption=\"#(loc.Caption)\">\r\n    <Label X=\"11\" Y=\"-45\" Width=\"-11\" Height=\"2\" FontId=\"7\" Visible=\"yes\" />\r\n    <Page Name=\"Help\">\r\n      <Label X=\"80\" Y=\"11\" Width=\"-11\" Height=\"32\" FontId=\"1\" DisablePrefix=\"yes\">#(loc.Title)</Label>\r\n      <ImageControl X=\"11\" Y=\"11\" Width=\"206\" Height=\"64\" ImageFile=\"logo.png\" />\r\n      <Label X=\"11\" Y=\"80\" Width=\"-11\" Height=\"32\" FontId=\"2\" DisablePrefix=\"yes\">#(loc.HelpHeader)</Label>\r\n      <Label X=\"11\" Y=\"121\" Width=\"-11\" Height=\"-35\" FontId=\"3\" DisablePrefix=\"yes\">#(loc.HelpText)</Label>\r\n      <Button Name=\"HelpCloseButton\" X=\"-11\" Y=\"-11\" Width=\"75\" Height=\"23\" TabStop=\"yes\" FontId=\"0\">\r\n        <Text>#(loc.HelpCloseButton)</Text>\r\n        <CloseWindowAction />\r\n      </Button>\r\n    </Page>\r\n\r\n    <Page Name=\"Install\">\r\n      <ImageControl X=\"11\" Y=\"11\" Width=\"165\" Height=\"400\" ImageFile=\"logoside.png\" />\r\n      <Label X=\"185\" Y=\"11\" Width=\"-11\" Height=\"40\" FontId=\"5\" DisablePrefix=\"yes\">#(loc.InstallHeader)</Label>\r\n      <Label X=\"185\" Y=\"82\" Width=\"-11\" Height=\"160\" FontId=\"3\" DisablePrefix=\"yes\">#(loc.InstallMessage)</Label>\r\n      <Hypertext\r\n        Name=\"EulaHyperlink\"\r\n        X=\"185\"\r\n        Y=\"-111\"\r\n        Width=\"-11\"\r\n        Height=\"17\"\r\n        TabStop=\"yes\"\r\n        FontId=\"3\"\r\n        HideWhenDisabled=\"yes\"\r\n      >#(loc.InstallLicenseLinkText)</Hypertext>\r\n      <Label\r\n        Name=\"InstallVersion\"\r\n        X=\"185\"\r\n        Y=\"-81\"\r\n        Width=\"-11\"\r\n        Height=\"17\"\r\n        FontId=\"3\"\r\n        DisablePrefix=\"yes\"\r\n        VisibleCondition=\"WixStdBAShowVersion\"\r\n      >#(loc.InstallVersion)</Label>\r\n      <Checkbox\r\n        Name=\"Custom_InstallGlazeWM\"\r\n        X=\"185\"\r\n        Y=\"180\"\r\n        Width=\"-11\"\r\n        Height=\"17\"\r\n        TabStop=\"yes\"\r\n        FontId=\"6\"\r\n        EnableCondition=\"0=1\"\r\n      >#(loc.CustomInstallGlazeWM)</Checkbox>\r\n      <Hypertext\r\n        X=\"204\"\r\n        Y=\"200\"\r\n        Width=\"-11\"\r\n        Height=\"20\"\r\n        FontId=\"3\"\r\n      >#(loc.CustomInstallGlazeWMHint)</Hypertext>\r\n      <Checkbox\r\n        Name=\"Custom_InstallZebar\"\r\n        X=\"185\"\r\n        Y=\"250\"\r\n        Width=\"-11\"\r\n        Height=\"17\"\r\n        TabStop=\"yes\"\r\n        FontId=\"6\"\r\n      >#(loc.CustomInstallZebar)</Checkbox>\r\n      <Hypertext\r\n        X=\"204\"\r\n        Y=\"270\"\r\n        Width=\"-11\"\r\n        Height=\"20\"\r\n        FontId=\"3\"\r\n      >#(loc.CustomInstallZebarHint1)</Hypertext>\r\n      <Label\r\n        X=\"204\"\r\n        Y=\"290\"\r\n        Width=\"-11\"\r\n        Height=\"20\"\r\n        FontId=\"3\"\r\n      >#(loc.CustomInstallZebarHint2)</Label>\r\n      <Button\r\n        Name=\"OptionsButton\"\r\n        X=\"-171\"\r\n        Y=\"-11\"\r\n        Width=\"75\"\r\n        Height=\"23\"\r\n        TabStop=\"yes\"\r\n        FontId=\"0\"\r\n        VisibleCondition=\"NOT WixStdBASuppressOptionsUI\"\r\n      >\r\n        <Text>#(loc.InstallOptionsButton)</Text>\r\n        <ChangePageAction Page=\"Options\" />\r\n      </Button>\r\n      <Button Name=\"InstallButton\" X=\"-91\" Y=\"-11\" Width=\"75\" Height=\"23\" TabStop=\"yes\" FontId=\"0\">\r\n        #(loc.InstallInstallButton)</Button>\r\n      <Button Name=\"InstallCancelButton\" X=\"-11\" Y=\"-11\" Width=\"75\" Height=\"23\" TabStop=\"yes\" FontId=\"0\">\r\n        <Text>#(loc.InstallCancelButton)</Text>\r\n        <CloseWindowAction />\r\n      </Button>\r\n    </Page>\r\n\r\n    <Page Name=\"Options\">\r\n      <ImageControl X=\"11\" Y=\"11\" Width=\"206\" Height=\"64\" ImageFile=\"logo.png\" />\r\n      <Label X=\"11\" Y=\"80\" Width=\"-11\" Height=\"30\" FontId=\"2\" DisablePrefix=\"yes\">#(loc.OptionsHeader)</Label>\r\n      <Label X=\"11\" Y=\"121\" Width=\"-11\" Height=\"17\" FontId=\"3\">#(loc.OptionsLocationLabel)</Label>\r\n      <Editbox\r\n        Name=\"InstallFolder\"\r\n        X=\"11\"\r\n        Y=\"143\"\r\n        Width=\"-91\"\r\n        Height=\"21\"\r\n        TabStop=\"yes\"\r\n        FontId=\"3\"\r\n        FileSystemAutoComplete=\"yes\"\r\n      />\r\n      <Button Name=\"BrowseButton\" X=\"-11\" Y=\"142\" Width=\"75\" Height=\"23\" TabStop=\"yes\" FontId=\"3\">\r\n        <Text>#(loc.OptionsBrowseButton)</Text>\r\n        <BrowseDirectoryAction VariableName=\"InstallFolder\" />\r\n      </Button>\r\n      <Button Name=\"OptionsOkButton\" X=\"-91\" Y=\"-11\" Width=\"75\" Height=\"23\" TabStop=\"yes\" FontId=\"0\">\r\n        <Text>#(loc.OptionsOkButton)</Text>\r\n        <ChangePageAction Page=\"Install\" />\r\n      </Button>\r\n      <Button Name=\"OptionsCancelButton\" X=\"-11\" Y=\"-11\" Width=\"75\" Height=\"23\" TabStop=\"yes\" FontId=\"0\">\r\n        <Text>#(loc.OptionsCancelButton)</Text>\r\n        <ChangePageAction Page=\"Install\" Cancel=\"yes\" />\r\n      </Button>\r\n    </Page>\r\n\r\n    <Page Name=\"Progress\">\r\n      <ImageControl X=\"11\" Y=\"11\" Width=\"206\" Height=\"64\" ImageFile=\"logo.png\" />\r\n      <Label X=\"11\" Y=\"80\" Width=\"-11\" Height=\"30\" FontId=\"2\" DisablePrefix=\"yes\">#(loc.ProgressHeader)</Label>\r\n      <Label X=\"11\" Y=\"141\" Width=\"70\" Height=\"17\" FontId=\"3\" DisablePrefix=\"yes\">#(loc.ProgressLabel)</Label>\r\n      <Label\r\n        Name=\"OverallProgressPackageText\"\r\n        X=\"85\"\r\n        Y=\"141\"\r\n        Width=\"-11\"\r\n        Height=\"17\"\r\n        FontId=\"3\"\r\n        DisablePrefix=\"yes\"\r\n      >#(loc.OverallProgressPackageText)</Label>\r\n      <Progressbar Name=\"OverallCalculatedProgressbar\" X=\"11\" Y=\"163\" Width=\"-11\" Height=\"20\" />\r\n      <Button\r\n        Name=\"ProgressCancelButton\"\r\n        X=\"-11\"\r\n        Y=\"-11\"\r\n        Width=\"75\"\r\n        Height=\"23\"\r\n        TabStop=\"yes\"\r\n        FontId=\"0\"\r\n      >#(loc.ProgressCancelButton)</Button>\r\n    </Page>\r\n    <Page Name=\"Modify\">\r\n      <Label X=\"185\" Y=\"11\" Width=\"-11\" Height=\"32\" FontId=\"1\" DisablePrefix=\"yes\">#(loc.Title)</Label>\r\n      <ImageControl X=\"11\" Y=\"11\" Width=\"165\" Height=\"400\" ImageFile=\"logoside.png\" />\r\n      <Label X=\"185\" Y=\"50\" Width=\"-11\" Height=\"30\" FontId=\"2\" DisablePrefix=\"yes\">#(loc.ModifyHeader)</Label>\r\n      <Button\r\n        Name=\"RepairButton\"\r\n        X=\"-171\"\r\n        Y=\"-11\"\r\n        Width=\"75\"\r\n        Height=\"23\"\r\n        TabStop=\"yes\"\r\n        FontId=\"0\"\r\n        HideWhenDisabled=\"yes\"\r\n      >#(loc.ModifyRepairButton)</Button>\r\n      <Button\r\n        Name=\"UninstallButton\"\r\n        X=\"-91\"\r\n        Y=\"-11\"\r\n        Width=\"75\"\r\n        Height=\"23\"\r\n        TabStop=\"yes\"\r\n        FontId=\"0\"\r\n      >#(loc.ModifyUninstallButton)</Button>\r\n      <Button Name=\"ModifyCancelButton\" X=\"-11\" Y=\"-11\" Width=\"75\" Height=\"23\" TabStop=\"yes\" FontId=\"0\">\r\n        <Text>#(loc.ModifyCancelButton)</Text>\r\n        <CloseWindowAction />\r\n      </Button>\r\n    </Page>\r\n\r\n    <Page Name=\"Success\">\r\n      <Label X=\"185\" Y=\"11\" Width=\"-11\" Height=\"32\" FontId=\"1\" DisablePrefix=\"yes\">#(loc.Title)</Label>\r\n      <ImageControl X=\"11\" Y=\"11\" Width=\"165\" Height=\"400\" ImageFile=\"logoside.png\" />\r\n      <Label X=\"185\" Y=\"50\" Width=\"-11\" Height=\"30\" FontId=\"2\" DisablePrefix=\"yes\">\r\n        <Text>#(loc.SuccessHeader)</Text>\r\n        <Text Condition=\"WixBundleAction = 4\">#(loc.SuccessUninstallHeader)</Text>\r\n        <Text Condition=\"WixBundleAction = 6\">#(loc.SuccessInstallHeader)</Text>\r\n        <Text Condition=\"WixBundleAction = 8\">#(loc.SuccessRepairHeader)</Text>\r\n      </Label>\r\n      <Button\r\n        Name=\"LaunchButton\"\r\n        X=\"-91\"\r\n        Y=\"-11\"\r\n        Width=\"125\"\r\n        Height=\"23\"\r\n        TabStop=\"yes\"\r\n        FontId=\"0\"\r\n        HideWhenDisabled=\"yes\"\r\n      >#(loc.SuccessLaunchButton)</Button>\r\n      <Label\r\n        X=\"185\"\r\n        Y=\"-51\"\r\n        Width=\"400\"\r\n        Height=\"34\"\r\n        FontId=\"3\"\r\n        DisablePrefix=\"yes\"\r\n        VisibleCondition=\"WixStdBARestartRequired\"\r\n      >\r\n        <Text>#(loc.SuccessRestartText)</Text>\r\n        <Text Condition=\"WixBundleAction = 3\">#(loc.SuccessUninstallRestartText)</Text>\r\n      </Label>\r\n      <Button\r\n        Name=\"SuccessRestartButton\"\r\n        X=\"-91\"\r\n        Y=\"-11\"\r\n        Width=\"75\"\r\n        Height=\"23\"\r\n        TabStop=\"yes\"\r\n        FontId=\"0\"\r\n        HideWhenDisabled=\"yes\"\r\n      >#(loc.SuccessRestartButton)</Button>\r\n      <Button Name=\"SuccessCloseButton\" X=\"-11\" Y=\"-11\" Width=\"75\" Height=\"23\" TabStop=\"yes\" FontId=\"0\">\r\n        <Text>#(loc.SuccessCloseButton)</Text>\r\n        <CloseWindowAction />\r\n      </Button>\r\n    </Page>\r\n\r\n    <Page Name=\"Failure\">\r\n      <ImageControl X=\"11\" Y=\"11\" Width=\"165\" Height=\"400\" ImageFile=\"logoside.png\" />\r\n      <Label X=\"185\" Y=\"50\" Width=\"-11\" Height=\"30\" FontId=\"2\" DisablePrefix=\"yes\">\r\n        <Text>#(loc.FailureHeader)</Text>\r\n        <Text Condition=\"WixBundleAction = 4\">#(loc.FailureUninstallHeader)</Text>\r\n        <Text Condition=\"WixBundleAction = 6\">#(loc.FailureInstallHeader)</Text>\r\n        <Text Condition=\"WixBundleAction = 8\">#(loc.FailureRepairHeader)</Text>\r\n      </Label>\r\n      <Hypertext\r\n        Name=\"FailureLogFileLink\"\r\n        X=\"185\"\r\n        Y=\"121\"\r\n        Width=\"-11\"\r\n        Height=\"68\"\r\n        FontId=\"3\"\r\n        TabStop=\"yes\"\r\n        HideWhenDisabled=\"yes\"\r\n      >#(loc.FailureHyperlinkLogText)</Hypertext>\r\n      <Hypertext\r\n        Name=\"FailureMessageText\"\r\n        X=\"185\"\r\n        Y=\"-115\"\r\n        Width=\"-11\"\r\n        Height=\"80\"\r\n        FontId=\"3\"\r\n        TabStop=\"yes\"\r\n        HideWhenDisabled=\"yes\"\r\n      />\r\n      <Label\r\n        X=\"185\"\r\n        Y=\"-57\"\r\n        Width=\"-11\"\r\n        Height=\"80\"\r\n        FontId=\"3\"\r\n        DisablePrefix=\"yes\"\r\n        VisibleCondition=\"WixStdBARestartRequired\"\r\n      >#(loc.FailureRestartText)</Label>\r\n      <Button\r\n        Name=\"FailureRestartButton\"\r\n        X=\"-91\"\r\n        Y=\"-11\"\r\n        Width=\"75\"\r\n        Height=\"23\"\r\n        TabStop=\"yes\"\r\n        FontId=\"0\"\r\n        HideWhenDisabled=\"yes\"\r\n      >#(loc.FailureRestartButton)</Button>\r\n      <Button Name=\"FailureCloseButton\" X=\"-11\" Y=\"-11\" Width=\"75\" Height=\"23\" TabStop=\"yes\" FontId=\"0\">\r\n        <Text>#(loc.FailureCloseButton)</Text>\r\n        <CloseWindowAction />\r\n      </Button>\r\n    </Page>\r\n  </Window>\r\n</Theme>\r\n"
  },
  {
    "path": "resources/wix/bundle.wxs",
    "content": "<Wix\r\n  xmlns=\"http://wixtoolset.org/schemas/v4/wxs\"\r\n  xmlns:ui=\"http://wixtoolset.org/schemas/v4/wxs/ui\"\r\n  xmlns:bal=\"http://wixtoolset.org/schemas/v4/wxs/bal\">\r\n  <Bundle Name=\"GlazeWM\" Version=\"$(var.VERSION_NUMBER)\"\r\n    UpgradeCode=\"f57adff3-4521-4a6b-87b1-d34a7d9bae1e\"\r\n    Manufacturer=\"Glzr Software Pte. Ltd.\"\r\n    DisableModify=\"yes\"\r\n    DisableRemove=\"yes\"\r\n    IconSourceFile=\"resources/assets/icon.ico\"\r\n  >\r\n    <BootstrapperApplication>\r\n      <bal:WixStandardBootstrapperApplication\r\n        LicenseUrl=\"\"\r\n        Theme=\"hyperlinkSidebarLicense\"\r\n        ThemeFile=\"resources/wix/bundle-theme.xml\"\r\n        LocalizationFile=\"resources/wix/bundle-theme.wxl\"\r\n        SuppressOptionsUI=\"yes\"\r\n        ShowVersion=\"yes\" />\r\n    </BootstrapperApplication>\r\n\r\n    <!-- User input variable for whether to install GlazeWM and Zebar. -->\r\n    <Variable Name=\"Custom_InstallGlazeWM\" Type=\"numeric\" Value=\"1\" bal:Overridable=\"yes\" />\r\n    <Variable Name=\"Custom_InstallZebar\" Type=\"numeric\" Value=\"1\" bal:Overridable=\"yes\" />\r\n\r\n    <Chain>\r\n      <!-- GlazeWM MSI for x64 architectures. -->\r\n      <MsiPackage\r\n        Id=\"Custom_GlazeWMPackageX64\"\r\n        SourceFile=\"out/installer-x64.msi\"\r\n        InstallCondition=\"(VersionNT64 AND NOT NativeMachine) OR (NativeMachine = 34404)\"\r\n        bal:DisplayInternalUICondition=\"1\"\r\n        Permanent=\"yes\"\r\n      />\r\n\r\n      <!-- GlazeWM MSI for arm64 architectures. -->\r\n      <MsiPackage\r\n        Id=\"Custom_GlazeWMPackageArm64\"\r\n        SourceFile=\"out/installer-arm64.msi\"\r\n        InstallCondition=\"NativeMachine = 43620\"\r\n        bal:DisplayInternalUICondition=\"1\"\r\n        Permanent=\"yes\"\r\n      />\r\n\r\n      <!-- Zebar MSI for x64 architectures. -->\r\n      <MsiPackage\r\n        Id=\"Custom_ZebarPackageX64\"\r\n        SourceFile=\"out/zebar-x64.msi\"\r\n        InstallCondition=\"(VersionNT64 AND NOT NativeMachine) OR (NativeMachine = 34404) AND (Custom_InstallZebar = 1)\"\r\n        bal:DisplayInternalUICondition=\"1\"\r\n        Permanent=\"yes\"\r\n      >\r\n        <MsiProperty Name=\"ADD_GLAZEWM_STARTER\" Value=\"1\" />\r\n      </MsiPackage>\r\n\r\n      <!-- Zebar MSI for arm64 architectures. -->\r\n      <MsiPackage\r\n        Id=\"Custom_ZebarPackageArm64\"\r\n        SourceFile=\"out/zebar-arm64.msi\"\r\n        InstallCondition=\"(NativeMachine = 43620) AND (Custom_InstallZebar = 1)\"\r\n        bal:DisplayInternalUICondition=\"1\"\r\n        Permanent=\"yes\"\r\n      >\r\n        <MsiProperty Name=\"ADD_GLAZEWM_STARTER\" Value=\"1\" />\r\n      </MsiPackage>\r\n    </Chain>\r\n  </Bundle>\r\n</Wix>\r\n"
  },
  {
    "path": "resources/wix/standalone-ui.wxs",
    "content": "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\" xmlns:ui=\"http://wixtoolset.org/schemas/v4/wxs/ui\">\r\n  <Fragment>\r\n    <UI>\r\n      <Dialog Id=\"WelcomeInstallDlg\" Width=\"370\" Height=\"270\" Title=\"!(loc.WelcomeDlg_Title)\">\r\n        <Control\r\n          Id=\"Bitmap\"\r\n          Type=\"Bitmap\"\r\n          X=\"0\"\r\n          Y=\"0\"\r\n          Width=\"370\"\r\n          Height=\"234\"\r\n          TabSkip=\"no\"\r\n          Text=\"!(loc.WelcomeDlgBitmap)\"\r\n        />\r\n        <Control\r\n          Id=\"Title\"\r\n          Type=\"Text\"\r\n          X=\"135\"\r\n          Y=\"20\"\r\n          Width=\"220\"\r\n          Height=\"60\"\r\n          Transparent=\"yes\"\r\n          NoPrefix=\"yes\"\r\n          Text=\"!(loc.WelcomeDlgTitle)\"\r\n        />\r\n        <Control Id=\"BottomLine\" Type=\"Line\" X=\"0\" Y=\"234\" Width=\"370\" Height=\"0\" />\r\n        <Control\r\n          Id=\"Description\"\r\n          Type=\"Text\"\r\n          X=\"135\"\r\n          Y=\"50\"\r\n          Width=\"220\"\r\n          Height=\"60\"\r\n          Transparent=\"yes\"\r\n          NoPrefix=\"yes\"\r\n          Text=\"!(loc.WelcomeDlgDescription)\"\r\n        />\r\n        <Control\r\n          Id=\"Custom_DesktopCheckbox\"\r\n          Type=\"CheckBox\"\r\n          X=\"135\"\r\n          Y=\"160\"\r\n          Width=\"226\"\r\n          Height=\"18\"\r\n          CheckBoxValue=\"1\"\r\n          Property=\"ENABLE_DESKTOP_SHORTCUT\"\r\n          Text=\"Add shortcut to desktop\"\r\n        />\r\n        <Control\r\n          Id=\"Custom_StartMenuCheckbox\"\r\n          Type=\"CheckBox\"\r\n          X=\"135\"\r\n          Y=\"190\"\r\n          Width=\"226\"\r\n          Height=\"18\"\r\n          CheckBoxValue=\"1\"\r\n          Property=\"ENABLE_START_MENU_SHORTCUT\"\r\n          Default=\"yes\"\r\n          Text=\"Add shortcut to Start Menu\"\r\n        />\r\n        <Control\r\n          Id=\"Back\"\r\n          Type=\"PushButton\"\r\n          X=\"156\"\r\n          Y=\"243\"\r\n          Width=\"56\"\r\n          Height=\"17\"\r\n          Disabled=\"yes\"\r\n          Text=\"!(loc.WixUIBack)\"\r\n        />\r\n        <Control\r\n          Id=\"Install\"\r\n          Type=\"PushButton\"\r\n          ElevationShield=\"yes\"\r\n          X=\"212\"\r\n          Y=\"243\"\r\n          Width=\"80\"\r\n          Height=\"17\"\r\n          Default=\"yes\"\r\n          Text=\"!(loc.WelcomeEulaDlgInstall)\"\r\n        >\r\n          <Publish\r\n            Event=\"SpawnWaitDialog\"\r\n            Value=\"WaitForCostingDlg\"\r\n            Condition=\"!(wix.WixUICostingPopupOptOut) OR CostingComplete = 1\"\r\n          />\r\n          <Publish Event=\"EndDialog\" Value=\"Return\" Condition=\"OutOfDiskSpace &lt;&gt; 1\" />\r\n          <Publish\r\n            Event=\"SpawnDialog\"\r\n            Value=\"OutOfRbDiskDlg\"\r\n            Condition=\"OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND (PROMPTROLLBACKCOST=&quot;P&quot; OR NOT PROMPTROLLBACKCOST)\"\r\n          />\r\n          <Publish\r\n            Event=\"EndDialog\"\r\n            Value=\"Return\"\r\n            Condition=\"OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND PROMPTROLLBACKCOST=&quot;D&quot;\"\r\n          />\r\n          <Publish\r\n            Event=\"EnableRollback\"\r\n            Value=\"False\"\r\n            Condition=\"OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND PROMPTROLLBACKCOST=&quot;D&quot;\"\r\n          />\r\n          <Publish\r\n            Event=\"SpawnDialog\"\r\n            Value=\"OutOfDiskDlg\"\r\n            Condition=\"(OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 1) OR (OutOfDiskSpace = 1 AND PROMPTROLLBACKCOST=&quot;F&quot;)\"\r\n          />\r\n        </Control>\r\n        <Control\r\n          Id=\"Cancel\"\r\n          Type=\"PushButton\"\r\n          X=\"304\"\r\n          Y=\"243\"\r\n          Width=\"56\"\r\n          Height=\"17\"\r\n          Cancel=\"yes\"\r\n          Text=\"!(loc.WixUICancel)\"\r\n        >\r\n          <Publish Event=\"SpawnDialog\" Value=\"CancelDlg\" />\r\n        </Control>\r\n      </Dialog>\r\n    </UI>\r\n\r\n    <UI Id=\"Custom_UI\">\r\n      <TextStyle Id=\"WixUI_Font_Normal\" FaceName=\"Tahoma\" Size=\"8\" />\r\n      <TextStyle Id=\"WixUI_Font_Bigger\" FaceName=\"Tahoma\" Size=\"12\" />\r\n      <TextStyle Id=\"WixUI_Font_Title\" FaceName=\"Tahoma\" Size=\"9\" Bold=\"yes\" />\r\n      <Property Id=\"DefaultUIFont\" Value=\"WixUI_Font_Normal\" />\r\n      <Property Id=\"WixUI_Mode\" Value=\"Minimal\" />\r\n      <DialogRef Id=\"ErrorDlg\" />\r\n      <DialogRef Id=\"FatalError\" />\r\n      <DialogRef Id=\"FilesInUse\" />\r\n      <DialogRef Id=\"MsiRMFilesInUse\" />\r\n      <DialogRef Id=\"PrepareDlg\" />\r\n      <DialogRef Id=\"ProgressDlg\" />\r\n      <DialogRef Id=\"ResumeDlg\" />\r\n      <DialogRef Id=\"UserExit\" />\r\n      <DialogRef Id=\"WelcomeInstallDlg\" />\r\n      <Publish Dialog=\"ExitDialog\" Control=\"Finish\" Event=\"EndDialog\" Value=\"Return\" Order=\"999\" />\r\n      <Publish Dialog=\"VerifyReadyDlg\" Control=\"Back\" Event=\"NewDialog\" Value=\"MaintenanceTypeDlg\" />\r\n      <Publish Dialog=\"MaintenanceWelcomeDlg\" Control=\"Next\" Event=\"NewDialog\"\r\n        Value=\"MaintenanceTypeDlg\" />\r\n      <Publish Dialog=\"MaintenanceTypeDlg\" Control=\"RepairButton\" Event=\"NewDialog\"\r\n        Value=\"VerifyReadyDlg\" />\r\n      <Publish Dialog=\"MaintenanceTypeDlg\" Control=\"RemoveButton\" Event=\"NewDialog\"\r\n        Value=\"VerifyReadyDlg\" />\r\n      <Publish Dialog=\"MaintenanceTypeDlg\" Control=\"Back\" Event=\"NewDialog\"\r\n        Value=\"MaintenanceWelcomeDlg\" />\r\n      <Publish\r\n        Dialog=\"VerifyReadyDlg\"\r\n        Control=\"Back\"\r\n        Event=\"NewDialog\"\r\n        Value=\"WelcomeInstallDlg\"\r\n        Order=\"2\"\r\n        Condition=\"Installed AND PATCH\"\r\n      />\r\n\r\n      <InstallUISequence>\r\n        <Show Dialog=\"WelcomeInstallDlg\" Before=\"ProgressDlg\"\r\n          Condition=\"(NOT Installed) AND NOT AFTERREBOOT\" />\r\n      </InstallUISequence>\r\n\r\n      <Property Id=\"ARPNOMODIFY\" Value=\"1\" />\r\n    </UI>\r\n\r\n    <UIRef Id=\"WixUI_Common\" />\r\n  </Fragment>\r\n</Wix>\r\n"
  },
  {
    "path": "resources/wix/standalone.wxs",
    "content": "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\" xmlns:ui=\"http://wixtoolset.org/schemas/v4/wxs/ui\">\r\n  <Package\r\n    Name=\"GlazeWM\"\r\n    Version=\"$(var.VERSION_NUMBER)\"\r\n    UpgradeCode=\"ef0f849b-d90d-4647-bb26-730c6fce5f9e\"\r\n    Manufacturer=\"Glzr Software Pte. Ltd.\"\r\n    Language=\"1033\"\r\n    Scope=\"perMachine\"\r\n  >\r\n    <!--\r\n      Reinstall all files; rewrite all registry entries; reinstall all shortcuts.\r\n      Ref: https://docs.microsoft.com/en-us/windows/win32/msi/reinstallmode\r\n    -->\r\n    <Property Id=\"REINSTALLMODE\" Value=\"amus\" />\r\n\r\n    <!-- Allow version downgrades. -->\r\n    <MajorUpgrade Schedule=\"afterInstallInitialize\" AllowDowngrades=\"yes\" />\r\n\r\n    <!-- Set icon used by installer. -->\r\n    <Icon Id=\"Custom_Ico\" SourceFile=\"resources/assets/icon.ico\" />\r\n    <Property Id=\"ARPPRODUCTICON\" Value=\"Custom_Ico\" />\r\n\r\n    <!-- Enable CAB embed, otherwise, a separate .cab file is created. -->\r\n    <MediaTemplate EmbedCab=\"yes\" />\r\n\r\n\t\t<UIRef Id=\"Custom_UI\" />\r\n\r\n    <!-- User input variables for whether to install shortcuts. -->\r\n    <Property Id=\"ENABLE_DESKTOP_SHORTCUT\" Secure=\"yes\" />\r\n    <Property Id=\"ENABLE_START_MENU_SHORTCUT\" Value=\"1\" Secure=\"yes\" />\r\n\r\n    <Feature Id=\"Custom_ProductFeature\">\r\n      <ComponentRef Id=\"Custom_CreateInstallDir\" />\r\n      <ComponentRef Id=\"Custom_InstallRootFiles\" />\r\n      <ComponentRef Id=\"Custom_InstallCliFiles\" />\r\n      <ComponentRef Id=\"Custom_AddDesktopShortcut\" />\r\n      <ComponentRef Id=\"Custom_AddStartMenuShortcut\" />\r\n      <ComponentRef Id=\"Custom_AddPathEnv\" />\r\n      <ComponentRef Id=\"Custom_RemoveAutoLaunchRegistry\" />\r\n    </Feature>\r\n\r\n    <StandardDirectory Id=\"ProgramFiles6432Folder\">\r\n      <Directory Id=\"Custom_OrgDir\" Name=\"glzr.io\">\r\n        <Directory Id=\"INSTALLFOLDER\" Name=\"GlazeWM\">\r\n          <Component Id=\"Custom_CreateInstallDir\">\r\n            <RegistryValue\r\n              Root=\"HKLM\"\r\n              Key=\"SOFTWARE\\glzr.io\\GlazeWM\"\r\n              Name=\"InstallDir\"\r\n              Value=\"[INSTALLFOLDER]\"\r\n              Type=\"string\"\r\n              KeyPath=\"yes\"\r\n            />\r\n          </Component>\r\n\r\n          <!-- Add main + watcher executable. -->\r\n          <Component Id=\"Custom_InstallRootFiles\" Guid=\"73752F94-6589-4C7B-ABED-39D655A19714\">\r\n            <File Id=\"Custom_RootExe\" Source=\"$(var.EXE_DIR)/glazewm.exe\" KeyPath=\"yes\" />\r\n            <File Id=\"Custom_RootWatcherExe\" Source=\"$(var.EXE_DIR)/glazewm-watcher.exe\" />\r\n          </Component>\r\n\r\n          <!-- Add CLI executable. -->\r\n          <Directory Id=\"Custom_CliDir\" Name=\"cli\">\r\n            <Component Id=\"Custom_InstallCliFiles\" Guid=\"916284d8-100c-4f37-8d8c-2722b01cb3dd\">\r\n              <File Id=\"Custom_CliExe\" Source=\"$(var.EXE_DIR)/glazewm-cli.exe\" Name=\"glazewm.exe\" KeyPath=\"yes\" />\r\n            </Component>\r\n          </Directory>\r\n        </Directory>\r\n      </Directory>\r\n    </StandardDirectory>\r\n\r\n    <!-- Add shortcut to desktop. -->\r\n    <StandardDirectory Id=\"DesktopFolder\">\r\n      <Component Id=\"Custom_AddDesktopShortcut\" Condition=\"ENABLE_DESKTOP_SHORTCUT = 1\">\r\n        <Shortcut\r\n          Id=\"Custom_DesktopShortcut\"\r\n          Name=\"GlazeWM\"\r\n          Description=\"Launch GlazeWM\"\r\n          Target=\"[INSTALLFOLDER]glazewm.exe\"\r\n        />\r\n\r\n        <RegistryValue\r\n          Root=\"HKLM\"\r\n          Key=\"SOFTWARE\\glzr.io\\GlazeWM\"\r\n          Name=\"DesktopShortcutInstalled\"\r\n          Type=\"integer\"\r\n          Value=\"1\"\r\n          KeyPath=\"yes\"\r\n        />\r\n      </Component>\r\n    </StandardDirectory>\r\n\r\n    <!-- Add shortcut to Start Menu. -->\r\n    <StandardDirectory Id=\"ProgramMenuFolder\">\r\n      <Component Id=\"Custom_AddStartMenuShortcut\" Condition=\"ENABLE_START_MENU_SHORTCUT = 1\">\r\n        <Shortcut\r\n          Id=\"Custom_StartMenuShortcut\"\r\n          Name=\"GlazeWM\"\r\n          Description=\"Launch GlazeWM\"\r\n          Target=\"[INSTALLFOLDER]glazewm.exe\"\r\n        />\r\n\r\n        <RegistryValue\r\n          Root=\"HKLM\"\r\n          Key=\"SOFTWARE\\glzr.io\\GlazeWM\"\r\n          Name=\"StartMenuShortcutInstalled\"\r\n          Type=\"integer\"\r\n          Value=\"1\"\r\n          KeyPath=\"yes\"\r\n        />\r\n      </Component>\r\n    </StandardDirectory>\r\n\r\n    <!-- Add CLI directory to PATH. -->\r\n    <Component Id=\"Custom_AddPathEnv\" Guid=\"66a4be46-19ae-4ed4-944f-b01673887f64\" KeyPath=\"yes\">\r\n      <Environment\r\n        Id=\"Custom_PathEnv\"\r\n        Value=\"[INSTALLFOLDER]\\cli\"\r\n        Name=\"PATH\"\r\n        Permanent=\"no\"\r\n        Part=\"first\"\r\n        Action=\"set\"\r\n        System=\"yes\"\r\n      />\r\n    </Component>\r\n\r\n    <!-- Remove auto-launch registry values on uninstall. -->\r\n    <Component Id=\"Custom_RemoveAutoLaunchRegistry\" Guid=\"B1A2C3D4-E5F6-47A8-9B0C-1234567890AB\">\r\n      <!-- `RegistryValue` with `KeyPath=\"yes\"` is needed for component state tracking. -->\r\n      <RegistryValue\r\n        Root=\"HKLM\"\r\n        Key=\"SOFTWARE\\glzr.io\\GlazeWM\"\r\n        Name=\"AutoLaunchCleanup\"\r\n        Type=\"integer\"\r\n        Value=\"1\"\r\n        KeyPath=\"yes\"\r\n      />\r\n      <RemoveRegistryValue\r\n        Root=\"HKCU\"\r\n        Key=\"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run\"\r\n        Name=\"GlazeWM\" />\r\n      <RemoveRegistryValue\r\n        Root=\"HKCU\"\r\n        Key=\"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\StartupApproved\\Run\"\r\n        Name=\"GlazeWM\" />\r\n    </Component>\r\n  </Package>\r\n</Wix>\r\n"
  },
  {
    "path": "rust-toolchain.toml",
    "content": "[toolchain]\r\nchannel = \"nightly\"\r\n"
  },
  {
    "path": "rustfmt.toml",
    "content": "tab_spaces = 2\r\nmax_width = 75\r\nimports_granularity = \"Crate\"\r\ngroup_imports = \"StdExternalCrate\"\r\nuse_field_init_shorthand = true\r\nwrap_comments = true\r\n"
  }
]