[
  {
    "path": ".gitattributes",
    "content": "*.rtf linguist-vendored\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "github: jordanbaird\nbuy_me_a_coffee: jordanbaird\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "content": "name: Bug Report\ndescription: Submit a bug report.\ntitle: \"[Bug]: \"\nlabels: Bug\nbody:\n  - type: checkboxes\n    attributes:\n      label: Search for Similar Reports\n      description: |\n        Please use the search feature on the repository's [Issues](https://github.com/jordanbaird/Ice/issues) page to see if a report already exists for the bug you encountered. Make sure to include both open and closed issues in your search.\n\n        **Important**: Only submit a new issue if the bug you encountered hasn't been reported in an existing open issue, or if a regression has occurred with a previously closed issue.\n      options:\n        - label: I have searched existing issues for similar reports\n          required: true\n  - type: textarea\n    attributes:\n      label: Description\n      placeholder: A clear and concise description of the bug...\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: Steps to Reproduce\n      description: Steps to reliably reproduce the behavior.\n      placeholder: |\n        1. Go to '...'\n        2. Click on '....'\n        3. Scroll down to '....'\n        4. See error\n    validations:\n      required: true\n  - type: input\n    id: app_version\n    attributes:\n      label: App Version\n      placeholder: e.g. 1.2.3\n    validations:\n      required: true\n  - type: input\n    id: macos_version\n    attributes:\n      label: macOS Version\n      placeholder: e.g. 15.3.2\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: Additional Information\n      placeholder: Screenshots, screen recordings, logs, crash reports, or anything else you think might be helpful...\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "content": "name: Feature Request\ndescription: Request a feature or suggest an idea.\ntitle: \"[Feature Request]: \"\nlabels: Feature\nbody:\n  - type: checkboxes\n    attributes:\n      label: Search for Similar Requests\n      description: |\n        Please use the search feature on the repository's [Issues](https://github.com/jordanbaird/Ice/issues) page to see if this feature has already been requested. Make sure to include both open and closed issues in your search.\n\n        **Important**: Only submit a new issue if the feature you want hasn't been requested in another issue.\n      options:\n        - label: I have searched existing issues for similar requests\n          required: true\n  - type: textarea\n    attributes:\n      label: Description\n      placeholder: A clear and concise description of the feature...\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: Additional Information\n      placeholder: Screenshots, screen recordings, mockups, or anything else you think might be helpful...\n"
  },
  {
    "path": ".github/workflows/lint.yml",
    "content": "name: Lint Swift Files\non:\n  push:\n    branches: [ \"main\" ]\n    paths:\n      - \".github/workflows/lint.yml\"\n      - \".swiftlint.yml\"\n      - \"**/*.swift\"\n  pull_request:\n    paths:\n      - \".github/workflows/lint.yml\"\n      - \".swiftlint.yml\"\n      - \"**/*.swift\"\njobs:\n  swiftlint:\n    if: '!github.event.pull_request.merged'\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n      - name: Run SwiftLint\n        uses: norio-nomura/action-swiftlint@3.2.1\n        with:\n          args: --strict\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\nbuild/\nxcuserdata/\n"
  },
  {
    "path": ".swiftlint.yml",
    "content": "included:\n  - Ice\n\ndisabled_rules:\n  - cyclomatic_complexity\n  - file_length\n  - function_body_length\n  - function_parameter_count\n  - generic_type_name\n  - identifier_name\n  - large_tuple\n  - line_length\n  - nesting\n  - opening_brace\n  - todo\n  - type_body_length\n\nopt_in_rules:\n  - closure_end_indentation\n  - closure_spacing\n  - collection_alignment\n  - convenience_type\n  - discouraged_object_literal\n  - empty_count\n  - fatal_error_message\n  - file_header\n  - force_unwrapping\n  - implicitly_unwrapped_optional\n  - indentation_width\n  - literal_expression_end_indentation\n  - lower_acl_than_parent\n  - modifier_order\n  - multiline_arguments\n  - multiline_arguments_brackets\n  - multiline_literal_brackets\n  - multiline_parameters\n  - multiline_parameters_brackets\n  - period_spacing\n  - unavailable_function\n  - vertical_parameter_alignment_on_call\n  - vertical_whitespace_closing_braces\n  - yoda_condition\n\ncustom_rules:\n  objc_dynamic:\n    name: \"@objc dynamic\"\n    message: \"`dynamic` modifier should immediately follow `@objc` attribute\"\n    regex: '@objc\\b(\\(\\w*\\))?+\\s*(\\S+|\\v+\\S*)\\s*\\bdynamic'\n    match_kinds: attribute.builtin\n  prefer_spaces_over_tabs:\n    name: Prefer Spaces Over Tabs\n    message: \"Indentation should use 4 spaces per indentation level instead of tabs\"\n    regex: ^\\t\n\nfile_header:\n  required_pattern: |\n    //\n    //  SWIFTLINT_CURRENT_FILENAME\n    //  Ice\n    //\n\nmodifier_order:\n  preferred_modifier_order:\n    - acl\n    - setterACL\n    - override\n    - mutators\n    - lazy\n    - final\n    - required\n    - convenience\n    - typeMethods\n    - owned\n\ntrailing_comma:\n  mandatory_comma: true\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participation in our\ncommunity a harassment-free experience for everyone, regardless of age, body\nsize, visible or invisible disability, ethnicity, sex characteristics, gender\nidentity and expression, level of experience, education, socio-economic status,\nnationality, personal appearance, race, religion, or sexual identity\nand orientation.\n\nWe pledge to act and interact in ways that contribute to an open, welcoming,\ndiverse, inclusive, and healthy community.\n\n## Our Standards\n\nExamples of behavior that contributes to a positive environment for our\ncommunity include:\n\n* Demonstrating empathy and kindness toward other people\n* Being respectful of differing opinions, viewpoints, and experiences\n* Giving and gracefully accepting constructive feedback\n* Accepting responsibility and apologizing to those affected by our mistakes,\n  and learning from the experience\n* Focusing on what is best not just for us as individuals, but for the\n  overall community\n\nExamples of unacceptable behavior include:\n\n* The use of sexualized language or imagery, and sexual attention or\n  advances of any kind\n* Trolling, insulting or derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or email\n  address, without their explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Enforcement Responsibilities\n\nCommunity leaders are responsible for clarifying and enforcing our standards of\nacceptable behavior and will take appropriate and fair corrective action in\nresponse to any behavior that they deem inappropriate, threatening, offensive,\nor harmful.\n\nCommunity leaders have the right and responsibility to remove, edit, or reject\ncomments, commits, code, wiki edits, issues, and other contributions that are\nnot aligned to this Code of Conduct, and will communicate reasons for moderation\ndecisions when appropriate.\n\n## Scope\n\nThis Code of Conduct applies within all community spaces, and also applies when\nan individual is officially representing the community in public spaces.\nExamples of representing our community include using an official e-mail address,\nposting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported to the community leaders responsible for enforcement at\njordanbaird.dev@gmail.com.\nAll complaints will be reviewed and investigated promptly and fairly.\n\nAll community leaders are obligated to respect the privacy and security of the\nreporter of any incident.\n\n## Enforcement Guidelines\n\nCommunity leaders will follow these Community Impact Guidelines in determining\nthe consequences for any action they deem in violation of this Code of Conduct:\n\n### 1. Correction\n\n**Community Impact**: Use of inappropriate language or other behavior deemed\nunprofessional or unwelcome in the community.\n\n**Consequence**: A private, written warning from community leaders, providing\nclarity around the nature of the violation and an explanation of why the\nbehavior was inappropriate. A public apology may be requested.\n\n### 2. Warning\n\n**Community Impact**: A violation through a single incident or series\nof actions.\n\n**Consequence**: A warning with consequences for continued behavior. No\ninteraction with the people involved, including unsolicited interaction with\nthose enforcing the Code of Conduct, for a specified period of time. This\nincludes avoiding interactions in community spaces as well as external channels\nlike social media. Violating these terms may lead to a temporary or\npermanent ban.\n\n### 3. Temporary Ban\n\n**Community Impact**: A serious violation of community standards, including\nsustained inappropriate behavior.\n\n**Consequence**: A temporary ban from any sort of interaction or public\ncommunication with the community for a specified period of time. No public or\nprivate interaction with the people involved, including unsolicited interaction\nwith those enforcing the Code of Conduct, is allowed during this period.\nViolating these terms may lead to a permanent ban.\n\n### 4. Permanent Ban\n\n**Community Impact**: Demonstrating a pattern of violation of community\nstandards, including sustained inappropriate behavior,  harassment of an\nindividual, or aggression toward or disparagement of classes of individuals.\n\n**Consequence**: A permanent ban from any sort of public interaction within\nthe community.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage],\nversion 2.0, available at\nhttps://www.contributor-covenant.org/version/2/0/code_of_conduct.html.\n\nCommunity Impact Guidelines were inspired by [Mozilla's code of conduct\nenforcement ladder](https://github.com/mozilla/diversity).\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see the FAQ at\nhttps://www.contributor-covenant.org/faq. Translations are available at\nhttps://www.contributor-covenant.org/translations.\n"
  },
  {
    "path": "FREQUENT_ISSUES.md",
    "content": "# Frequent Issues <!-- omit in toc -->\n\n- [Items are moved to the always-hidden section](#items-are-moved-to-the-always-hidden-section)\n- [Ice removed an item](#ice-removed-an-item)\n- [Ice does not remember the order of items](#ice-does-not-remember-the-order-of-items)\n- [How do I solve the `Ice cannot arrange menu bar items in automatically hidden menu bars` error?](#how-do-i-solve-the-ice-cannot-arrange-menu-bar-items-in-automatically-hidden-menu-bars-error)\n\n## Items are moved to the always-hidden section\n\nBy default, macOS adds new items to the far left of the menu bar, which is also the location of Ice's always-hidden section. Most apps are configured\nto remember the positions of their items, but some are not. macOS treats the items of these apps as new items each time they appear. This results in\nthese items appearing in the always-hidden section, even if they have been previously been moved.\n\nIce does not currently manage individual items, and in fact cannot, as of the current release. Once issues\n[#6](https://github.com/jordanbaird/Ice/issues/6) and [#26](https://github.com/jordanbaird/Ice/issues/26) are implemented, Ice will be able to\nmonitor the items in the menu bar, and move the ones it recognizes to their previous locations, even if macOS rearranges them.\n\n## Ice removed an item\n\nIce does not have the ability to move or remove items. It likely got placed in the always-hidden section by macOS. Option + click the Ice icon to show\nthe always-hidden section, then Command + drag the item into a different section.\n\n## Ice does not remember the order of items\n\nThis is not a bug, but a missing feature. It is being tracked in [#26](https://github.com/jordanbaird/Ice/issues/26).\n\n## How do I solve the `Ice cannot arrange menu bar items in automatically hidden menu bars` error?\n\n1. Open `System Settings` on your Mac\n2. Go to `Control Center`\n3. Select `Never` as shown in the image below\n4. Update your `Menu Bar Items` in `Ice`\n5. Return `Automatically hide and show the menu bar` to your preferred settings\n\n![Disable Menu Bar Hiding](https://github.com/user-attachments/assets/74c1fde6-d310-4fe3-9f2b-703d8ccb636a)\n"
  },
  {
    "path": "Ice/Assets.xcassets/AccentColor.colorset/Contents.json",
    "content": "{\n  \"colors\" : [\n    {\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "Ice/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"icon_16x16.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"16x16\"\n    },\n    {\n      \"filename\" : \"icon_16x16@2x.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"16x16\"\n    },\n    {\n      \"filename\" : \"icon_32x32.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"32x32\"\n    },\n    {\n      \"filename\" : \"icon_32x32@2x.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"32x32\"\n    },\n    {\n      \"filename\" : \"icon_128x128.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"128x128\"\n    },\n    {\n      \"filename\" : \"icon_128x128@2x.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"128x128\"\n    },\n    {\n      \"filename\" : \"icon_256x256.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"256x256\"\n    },\n    {\n      \"filename\" : \"icon_256x256@2x.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"256x256\"\n    },\n    {\n      \"filename\" : \"icon_512x512.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"512x512\"\n    },\n    {\n      \"filename\" : \"icon_512x512@2x.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"512x512\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "Ice/Assets.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "Ice/Assets.xcassets/ControlItemImages/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "Ice/Assets.xcassets/ControlItemImages/Dot/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "Ice/Assets.xcassets/ControlItemImages/Dot/DotFill.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"DotFill.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"template\"\n  }\n}\n"
  },
  {
    "path": "Ice/Assets.xcassets/ControlItemImages/Dot/DotStroke.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"DotStroke.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"template\"\n  }\n}\n"
  },
  {
    "path": "Ice/Assets.xcassets/ControlItemImages/Ellipsis/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "Ice/Assets.xcassets/ControlItemImages/Ellipsis/EllipsisFill.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"EllipsisFill.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"template\"\n  }\n}\n"
  },
  {
    "path": "Ice/Assets.xcassets/ControlItemImages/Ellipsis/EllipsisStroke.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"EllipsisStroke.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"template\"\n  }\n}\n"
  },
  {
    "path": "Ice/Assets.xcassets/ControlItemImages/IceCube/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "Ice/Assets.xcassets/ControlItemImages/IceCube/IceCubeFill.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"IceCubeFill.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"template\"\n  }\n}\n"
  },
  {
    "path": "Ice/Assets.xcassets/ControlItemImages/IceCube/IceCubeStroke.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"IceCubeStroke.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  },\n  \"properties\" : {\n    \"template-rendering-intent\" : \"template\"\n  }\n}\n"
  },
  {
    "path": "Ice/Assets.xcassets/DefaultLayoutBarColor.colorset/Contents.json",
    "content": "{\n  \"colors\" : [\n    {\n      \"color\" : {\n        \"color-space\" : \"display-p3\",\n        \"components\" : {\n          \"alpha\" : \"0.170\",\n          \"blue\" : \"0.000\",\n          \"green\" : \"0.000\",\n          \"red\" : \"0.000\"\n        }\n      },\n      \"idiom\" : \"universal\"\n    },\n    {\n      \"appearances\" : [\n        {\n          \"appearance\" : \"luminosity\",\n          \"value\" : \"dark\"\n        }\n      ],\n      \"color\" : {\n        \"color-space\" : \"display-p3\",\n        \"components\" : {\n          \"alpha\" : \"0.070\",\n          \"blue\" : \"1.000\",\n          \"green\" : \"1.000\",\n          \"red\" : \"1.000\"\n        }\n      },\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "Ice/Assets.xcassets/Warning.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"Warning.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "Ice/Bridging/Bridging.swift",
    "content": "//\n//  Bridging.swift\n//  Ice\n//\n\nimport Cocoa\n\n/// A namespace for bridged functionality.\nenum Bridging { }\n\n// MARK: - CGSConnection\n\nextension Bridging {\n    /// Sets a value for the given key in the current connection to the window server.\n    ///\n    /// - Parameters:\n    ///   - value: The value to set for `key`.\n    ///   - key: A key associated with the current connection to the window server.\n    static func setConnectionProperty(_ value: Any?, forKey key: String) {\n        let result = CGSSetConnectionProperty(\n            CGSMainConnectionID(),\n            CGSMainConnectionID(),\n            key as CFString,\n            value as CFTypeRef\n        )\n        if result != .success {\n            Logger.bridging.error(\"CGSSetConnectionProperty failed with error \\(result.logString)\")\n        }\n    }\n\n    /// Returns the value for the given key in the current connection to the window server.\n    ///\n    /// - Parameter key: A key associated with the current connection to the window server.\n    /// - Returns: The value associated with `key` in the current connection to the window server.\n    static func getConnectionProperty(forKey key: String) -> Any? {\n        var value: Unmanaged<CFTypeRef>?\n        let result = CGSCopyConnectionProperty(\n            CGSMainConnectionID(),\n            CGSMainConnectionID(),\n            key as CFString,\n            &value\n        )\n        if result != .success {\n            Logger.bridging.error(\"CGSCopyConnectionProperty failed with error \\(result.logString)\")\n        }\n        return value?.takeRetainedValue()\n    }\n}\n\n// MARK: - CGSWindow\n\nextension Bridging {\n    /// Returns the frame for the window with the specified identifier.\n    ///\n    /// - Parameter windowID: An identifier for a window.\n    /// - Returns: The frame -- specified in screen coordinates -- of the window associated\n    ///   with `windowID`, or `nil` if the operation failed.\n    static func getWindowFrame(for windowID: CGWindowID) -> CGRect? {\n        var rect = CGRect.zero\n        let result = CGSGetScreenRectForWindow(CGSMainConnectionID(), windowID, &rect)\n        guard result == .success else {\n            Logger.bridging.error(\"CGSGetScreenRectForWindow failed with error \\(result.logString)\")\n            return nil\n        }\n        return rect\n    }\n}\n\n// MARK: Private Window List Helpers\nextension Bridging {\n    private static func getWindowCount() -> Int {\n        var count: Int32 = 0\n        let result = CGSGetWindowCount(CGSMainConnectionID(), 0, &count)\n        if result != .success {\n            Logger.bridging.error(\"CGSGetWindowCount failed with error \\(result.logString)\")\n        }\n        return Int(count)\n    }\n\n    private static func getOnScreenWindowCount() -> Int {\n        var count: Int32 = 0\n        let result = CGSGetOnScreenWindowCount(CGSMainConnectionID(), 0, &count)\n        if result != .success {\n            Logger.bridging.error(\"CGSGetOnScreenWindowCount failed with error \\(result.logString)\")\n        }\n        return Int(count)\n    }\n\n    private static func getWindowList() -> [CGWindowID] {\n        let windowCount = getWindowCount()\n        var list = [CGWindowID](repeating: 0, count: windowCount)\n        var realCount: Int32 = 0\n        let result = CGSGetWindowList(\n            CGSMainConnectionID(),\n            0,\n            Int32(windowCount),\n            &list,\n            &realCount\n        )\n        guard result == .success else {\n            Logger.bridging.error(\"CGSGetWindowList failed with error \\(result.logString)\")\n            return []\n        }\n        return [CGWindowID](list[..<Int(realCount)])\n    }\n\n    private static func getOnScreenWindowList() -> [CGWindowID] {\n        let windowCount = getOnScreenWindowCount()\n        var list = [CGWindowID](repeating: 0, count: windowCount)\n        var realCount: Int32 = 0\n        let result = CGSGetOnScreenWindowList(\n            CGSMainConnectionID(),\n            0,\n            Int32(windowCount),\n            &list,\n            &realCount\n        )\n        guard result == .success else {\n            Logger.bridging.error(\"CGSGetOnScreenWindowList failed with error \\(result.logString)\")\n            return []\n        }\n        return [CGWindowID](list[..<Int(realCount)])\n    }\n\n    private static func getMenuBarWindowList() -> [CGWindowID] {\n        let windowCount = getWindowCount()\n        var list = [CGWindowID](repeating: 0, count: windowCount)\n        var realCount: Int32 = 0\n        let result = CGSGetProcessMenuBarWindowList(\n            CGSMainConnectionID(),\n            0,\n            Int32(windowCount),\n            &list,\n            &realCount\n        )\n        guard result == .success else {\n            Logger.bridging.error(\"CGSGetProcessMenuBarWindowList failed with error \\(result.logString)\")\n            return []\n        }\n        return [CGWindowID](list[..<Int(realCount)])\n    }\n\n    private static func getOnScreenMenuBarWindowList() -> [CGWindowID] {\n        let onScreenList = Set(getOnScreenWindowList())\n        return getMenuBarWindowList().filter(onScreenList.contains)\n    }\n}\n\n// MARK: Public Window List API\nextension Bridging {\n    /// Options that determine the window identifiers to return in a window list.\n    struct WindowListOption: OptionSet {\n        let rawValue: Int\n\n        /// Specifies windows that are currently on-screen.\n        static let onScreen = WindowListOption(rawValue: 1 << 0)\n\n        /// Specifies windows that represent items in the menu bar.\n        static let menuBarItems = WindowListOption(rawValue: 1 << 1)\n\n        /// Specifies windows on the currently active space.\n        static let activeSpace = WindowListOption(rawValue: 1 << 2)\n    }\n\n    /// The total number of windows.\n    static var windowCount: Int {\n        getWindowCount()\n    }\n\n    /// The number of windows currently on-screen.\n    static var onScreenWindowCount: Int {\n        getOnScreenWindowCount()\n    }\n\n    /// Returns a list of window identifiers using the given options.\n    ///\n    /// - Parameter option: Options that filter the returned list.\n    static func getWindowList(option: WindowListOption = []) -> [CGWindowID] {\n        let list = if option.contains(.menuBarItems) {\n            if option.contains(.onScreen) {\n                getOnScreenMenuBarWindowList()\n            } else {\n                getMenuBarWindowList()\n            }\n        } else if option.contains(.onScreen) {\n            getOnScreenWindowList()\n        } else {\n            getWindowList()\n        }\n        return if option.contains(.activeSpace) {\n            list.filter(isWindowOnActiveSpace)\n        } else {\n            list\n        }\n    }\n}\n\n// MARK: - CGSSpace\n\nextension Bridging {\n    /// Options that determine the space identifiers to return in a space list.\n    enum SpaceListOption {\n        case allSpaces, visibleSpaces\n    }\n\n    /// The identifier of the active space.\n    static var activeSpaceID: CGSSpaceID {\n        CGSGetActiveSpace(CGSMainConnectionID())\n    }\n\n    /// Returns an array of identifiers for the spaces containing the window with\n    /// the given identifier.\n    ///\n    /// - Parameter windowID: An identifier for a window.\n    static func getSpaceList(for windowID: CGWindowID, option: SpaceListOption) -> [CGSSpaceID] {\n        let mask: CGSSpaceMask = switch option {\n        case .allSpaces: .allSpaces\n        case .visibleSpaces: .allVisibleSpaces\n        }\n        guard let spaces = CGSCopySpacesForWindows(CGSMainConnectionID(), mask, [windowID] as CFArray) else {\n            Logger.bridging.error(\"CGSCopySpacesForWindows failed\")\n            return []\n        }\n        guard let spaceIDs = spaces.takeRetainedValue() as? [CGSSpaceID] else {\n            Logger.bridging.error(\"CGSCopySpacesForWindows returned array of unexpected type\")\n            return []\n        }\n        return spaceIDs\n    }\n\n    /// Returns a Boolean value that indicates whether the window with the\n    /// given identifier is on the active space.\n    ///\n    /// - Parameter windowID: An identifier for a window.\n    static func isWindowOnActiveSpace(_ windowID: CGWindowID) -> Bool {\n        getSpaceList(for: windowID, option: .allSpaces).contains(activeSpaceID)\n    }\n\n    /// Returns a Boolean value that indicates whether the space with the given\n    /// identifier is a fullscreen space.\n    ///\n    /// - Parameter spaceID: An identifier for a space.\n    static func isSpaceFullscreen(_ spaceID: CGSSpaceID) -> Bool {\n        let type = CGSSpaceGetType(CGSMainConnectionID(), spaceID)\n        return type == .fullscreen\n    }\n}\n\n// MARK: - Process Responsivity\n\nextension Bridging {\n    /// Constants that indicate the responsivity of an app.\n    enum Responsivity {\n        case responsive, unresponsive, unknown\n    }\n\n    /// Returns the responsivity of the given process.\n    ///\n    /// - Parameter pid: The Unix process identifier of the process to check.\n    static func responsivity(for pid: pid_t) -> Responsivity {\n        var psn = ProcessSerialNumber()\n        let result = GetProcessForPID(pid, &psn)\n        guard result == noErr else {\n            Logger.bridging.error(\"GetProcessForPID failed with error \\(result)\")\n            return .unknown\n        }\n        if CGSEventIsAppUnresponsive(CGSMainConnectionID(), &psn) {\n            return .unresponsive\n        }\n        return .responsive\n    }\n}\n\n// MARK: - Logger\nprivate extension Logger {\n    static let bridging = Logger(category: \"Bridging\")\n}\n"
  },
  {
    "path": "Ice/Bridging/Shims/Deprecated.swift",
    "content": "//\n//  Deprecated.swift\n//  Ice\n//\n\nimport ApplicationServices\n\n/// Returns a PSN for a given PID.\n@_silgen_name(\"GetProcessForPID\")\nfunc GetProcessForPID(\n    _ pid: pid_t,\n    _ psn: inout ProcessSerialNumber\n) -> OSStatus\n"
  },
  {
    "path": "Ice/Bridging/Shims/Private.swift",
    "content": "//\n//  Private.swift\n//  Ice\n//\n\nimport CoreGraphics\n\n// MARK: - Bridged Types\n\ntypealias CGSConnectionID = Int32\ntypealias CGSSpaceID = size_t\n\nenum CGSSpaceType: UInt32 {\n    case user = 0\n    case system = 2\n    case fullscreen = 4\n}\n\nstruct CGSSpaceMask: OptionSet {\n    let rawValue: UInt32\n\n    static let includesCurrent = CGSSpaceMask(rawValue: 1 << 0)\n    static let includesOthers = CGSSpaceMask(rawValue: 1 << 1)\n    static let includesUser = CGSSpaceMask(rawValue: 1 << 2)\n\n    static let includesVisible = CGSSpaceMask(rawValue: 1 << 16)\n\n    static let currentSpace: CGSSpaceMask = [.includesUser, .includesCurrent]\n    static let otherSpaces: CGSSpaceMask = [.includesOthers, .includesCurrent]\n    static let allSpaces: CGSSpaceMask = [.includesUser, .includesOthers, .includesCurrent]\n    static let allVisibleSpaces: CGSSpaceMask = [.includesVisible, .allSpaces]\n}\n\n// MARK: - CGSConnection Functions\n\n@_silgen_name(\"CGSMainConnectionID\")\nfunc CGSMainConnectionID() -> CGSConnectionID\n\n@_silgen_name(\"CGSCopyConnectionProperty\")\nfunc CGSCopyConnectionProperty(\n    _ cid: CGSConnectionID,\n    _ targetCID: CGSConnectionID,\n    _ key: CFString,\n    _ outValue: inout Unmanaged<CFTypeRef>?\n) -> CGError\n\n@_silgen_name(\"CGSSetConnectionProperty\")\nfunc CGSSetConnectionProperty(\n    _ cid: CGSConnectionID,\n    _ targetCID: CGSConnectionID,\n    _ key: CFString,\n    _ value: CFTypeRef\n) -> CGError\n\n// MARK: - CGSEvent Functions\n\n@_silgen_name(\"CGSEventIsAppUnresponsive\")\nfunc CGSEventIsAppUnresponsive(\n    _ cid: CGSConnectionID,\n    _ psn: inout ProcessSerialNumber\n) -> Bool\n\n// MARK: - CGSSpace Functions\n\n@_silgen_name(\"CGSGetActiveSpace\")\nfunc CGSGetActiveSpace(_ cid: CGSConnectionID) -> CGSSpaceID\n\n@_silgen_name(\"CGSCopySpacesForWindows\")\nfunc CGSCopySpacesForWindows(\n    _ cid: CGSConnectionID,\n    _ mask: CGSSpaceMask,\n    _ windowIDs: CFArray\n) -> Unmanaged<CFArray>?\n\n@_silgen_name(\"CGSSpaceGetType\")\nfunc CGSSpaceGetType(\n    _ cid: CGSConnectionID,\n    _ sid: CGSSpaceID\n) -> CGSSpaceType\n\n// MARK: - CGSWindow Functions\n\n@_silgen_name(\"CGSGetWindowList\")\nfunc CGSGetWindowList(\n    _ cid: CGSConnectionID,\n    _ targetCID: CGSConnectionID,\n    _ count: Int32,\n    _ list: UnsafeMutablePointer<CGWindowID>,\n    _ outCount: inout Int32\n) -> CGError\n\n@_silgen_name(\"CGSGetOnScreenWindowList\")\nfunc CGSGetOnScreenWindowList(\n    _ cid: CGSConnectionID,\n    _ targetCID: CGSConnectionID,\n    _ count: Int32,\n    _ list: UnsafeMutablePointer<CGWindowID>,\n    _ outCount: inout Int32\n) -> CGError\n\n@_silgen_name(\"CGSGetProcessMenuBarWindowList\")\nfunc CGSGetProcessMenuBarWindowList(\n    _ cid: CGSConnectionID,\n    _ targetCID: CGSConnectionID,\n    _ count: Int32,\n    _ list: UnsafeMutablePointer<CGWindowID>,\n    _ outCount: inout Int32\n) -> CGError\n\n@_silgen_name(\"CGSGetWindowCount\")\nfunc CGSGetWindowCount(\n    _ cid: CGSConnectionID,\n    _ targetCID: CGSConnectionID,\n    _ outCount: inout Int32\n) -> CGError\n\n@_silgen_name(\"CGSGetOnScreenWindowCount\")\nfunc CGSGetOnScreenWindowCount(\n    _ cid: CGSConnectionID,\n    _ targetCID: CGSConnectionID,\n    _ outCount: inout Int32\n) -> CGError\n\n@_silgen_name(\"CGSGetScreenRectForWindow\")\nfunc CGSGetScreenRectForWindow(\n    _ cid: CGSConnectionID,\n    _ wid: CGWindowID,\n    _ outRect: inout CGRect\n) -> CGError\n"
  },
  {
    "path": "Ice/Events/EventManager.swift",
    "content": "//\n//  EventManager.swift\n//  Ice\n//\n\nimport Cocoa\nimport Combine\n\n/// Manager for the various event monitors maintained by the app.\n@MainActor\nfinal class EventManager {\n    /// The shared app state.\n    private weak var appState: AppState?\n\n    /// Storage for internal observers.\n    private var cancellables = Set<AnyCancellable>()\n\n    // MARK: Monitors\n\n    /// Monitor for mouse down events.\n    private(set) lazy var mouseDownMonitor = UniversalEventMonitor(\n        mask: [.leftMouseDown, .rightMouseDown]\n    ) { [weak self] event in\n        guard let self else {\n            return event\n        }\n        switch event.type {\n        case .leftMouseDown:\n            handleShowOnClick()\n            handleSmartRehide(with: event)\n        case .rightMouseDown:\n            handleShowRightClickMenu()\n        default:\n            break\n        }\n        handlePreventShowOnHover(with: event)\n        return event\n    }\n\n    /// Monitor for mouse up events.\n    private(set) lazy var mouseUpMonitor = UniversalEventMonitor(\n        mask: .leftMouseUp\n    ) { [weak self] event in\n        self?.handleLeftMouseUp()\n        return event\n    }\n\n    /// Monitor for mouse dragged events.\n    private(set) lazy var mouseDraggedMonitor = UniversalEventMonitor(\n        mask: .leftMouseDragged\n    ) { [weak self] event in\n        self?.handleLeftMouseDragged(with: event)\n        return event\n    }\n\n    /// Monitor for mouse moved events.\n    private(set) lazy var mouseMovedMonitor = UniversalEventMonitor(\n        mask: .mouseMoved\n    ) { [weak self] event in\n        self?.handleShowOnHover()\n        return event\n    }\n\n    /// Monitor for scroll wheel events.\n    private(set) lazy var scrollWheelMonitor = UniversalEventMonitor(\n        mask: .scrollWheel\n    ) { [weak self] event in\n        self?.handleShowOnScroll(with: event)\n        return event\n    }\n\n    // MARK: All Monitors\n\n    /// All monitors maintained by the app.\n    private lazy var allMonitors = [\n        mouseDownMonitor,\n        mouseUpMonitor,\n        mouseDraggedMonitor,\n        mouseMovedMonitor,\n        scrollWheelMonitor,\n    ]\n\n    // MARK: Initializers\n\n    /// Creates an event manager with the given app state.\n    init(appState: AppState) {\n        self.appState = appState\n    }\n\n    /// Sets up the manager.\n    func performSetup() {\n        startAll()\n        configureCancellables()\n    }\n\n    /// Configures the internal observers for the manager.\n    private func configureCancellables() {\n        var c = Set<AnyCancellable>()\n\n        if let appState {\n            if let hiddenSection = appState.menuBarManager.section(withName: .hidden) {\n                // In fullscreen mode, the menu bar slides down from the top on hover. Observe\n                // the frame of the hidden section's control item, which we know will always be\n                // in the menu bar, and run the show-on-hover check when it changes.\n                Publishers.CombineLatest(\n                    hiddenSection.controlItem.$windowFrame,\n                    appState.$isActiveSpaceFullscreen\n                )\n                .sink { [weak self] _, isFullscreen in\n                    guard\n                        let self,\n                        isFullscreen\n                    else {\n                        return\n                    }\n                    handleShowOnHover()\n                }\n                .store(in: &c)\n            }\n        }\n\n        cancellables = c\n    }\n\n    // MARK: Start/Stop\n\n    /// Starts all monitors.\n    func startAll() {\n        for monitor in allMonitors {\n            monitor.start()\n        }\n    }\n\n    /// Stops all monitors.\n    func stopAll() {\n        for monitor in allMonitors {\n            monitor.stop()\n        }\n    }\n}\n\n// MARK: - Handlers\n\nextension EventManager {\n\n    // MARK: Handle Show On Click\n\n    private func handleShowOnClick() {\n        guard\n            let appState,\n            appState.settingsManager.generalSettingsManager.showOnClick,\n            isMouseInsideEmptyMenuBarSpace\n        else {\n            return\n        }\n\n        Task {\n            // Short delay helps the toggle action feel more natural.\n            try? await Task.sleep(for: .milliseconds(50))\n\n            if NSEvent.modifierFlags == .control {\n                handleShowRightClickMenu()\n            } else if\n                NSEvent.modifierFlags == .option,\n                appState.settingsManager.advancedSettingsManager.canToggleAlwaysHiddenSection\n            {\n                if let alwaysHiddenSection = appState.menuBarManager.section(withName: .alwaysHidden) {\n                    alwaysHiddenSection.toggle()\n                }\n            } else {\n                if let hiddenSection = appState.menuBarManager.section(withName: .hidden) {\n                    hiddenSection.toggle()\n                }\n            }\n        }\n    }\n\n    // MARK: Handle Smart Rehide\n\n    private func handleSmartRehide(with event: NSEvent) {\n        guard\n            let appState,\n            appState.settingsManager.generalSettingsManager.autoRehide,\n            case .smart = appState.settingsManager.generalSettingsManager.rehideStrategy\n        else {\n            return\n        }\n\n        if let visibleSection = appState.menuBarManager.section(withName: .visible) {\n            guard event.window !== visibleSection.controlItem.window else {\n                return\n            }\n        }\n\n        // Make sure clicking the Ice Bar doesn't trigger rehide.\n        guard event.window !== appState.menuBarManager.iceBarPanel else {\n            return\n        }\n\n        // Only continue if a section is currently visible.\n        guard appState.menuBarManager.sections.contains(where: { !$0.isHidden }) else {\n            return\n        }\n\n        // Make sure the mouse is not in the menu bar.\n        guard !isMouseInsideMenuBar else {\n            return\n        }\n\n        Task {\n            let initialSpaceID = Bridging.activeSpaceID\n\n            // Sleep for a bit to give the window under the mouse a chance to focus.\n            try? await Task.sleep(for: .seconds(0.25))\n\n            // If clicking caused a space change, don't bother with the window check.\n            if Bridging.activeSpaceID != initialSpaceID {\n                for section in appState.menuBarManager.sections {\n                    section.hide()\n                }\n                return\n            }\n\n            // Get the window that the user has clicked into.\n            guard\n                let mouseLocation = MouseCursor.locationCoreGraphics,\n                let windowUnderMouse = WindowInfo.getOnScreenWindows(excludeDesktopWindows: false)\n                    .filter({ $0.layer < CGWindowLevelForKey(.cursorWindow) })\n                    .first(where: { $0.frame.contains(mouseLocation) && $0.title?.isEmpty == false }),\n                let owningApplication = windowUnderMouse.owningApplication\n            else {\n                return\n            }\n\n            // The dock is an exception to the following check.\n            if owningApplication.bundleIdentifier != \"com.apple.dock\" {\n                // Only continue if the user has clicked into an active window with\n                // a regular activation policy.\n                guard\n                    owningApplication.isActive,\n                    owningApplication.activationPolicy == .regular\n                else {\n                    return\n                }\n            }\n\n            // If all the above checks have passed, hide all sections.\n            for section in appState.menuBarManager.sections {\n                section.hide()\n            }\n        }\n    }\n\n    // MARK: Handle Show Right Click Menu\n\n    private func handleShowRightClickMenu() {\n        guard\n            let appState,\n            appState.settingsManager.advancedSettingsManager.showContextMenuOnRightClick,\n            isMouseInsideEmptyMenuBarSpace,\n            let mouseLocation = MouseCursor.locationAppKit\n        else {\n            return\n        }\n        appState.menuBarManager.showRightClickMenu(at: mouseLocation)\n    }\n\n    // MARK: Handle Prevent Show On Hover\n\n    private func handlePreventShowOnHover(with event: NSEvent) {\n        guard\n            let appState,\n            appState.settingsManager.generalSettingsManager.showOnHover,\n            !appState.settingsManager.generalSettingsManager.useIceBar,\n            isMouseInsideMenuBar\n        else {\n            return\n        }\n\n        if isMouseInsideMenuBarItem {\n            switch event.type {\n            case .leftMouseDown:\n                if appState.menuBarManager.sections.contains(where: { !$0.isHidden }) || isMouseInsideIceIcon {\n                    // We have a left click that is inside the menu bar while at least one\n                    // section is visible or the mouse is inside the Ice icon.\n                    appState.preventShowOnHover()\n                }\n            case .rightMouseDown:\n                if appState.menuBarManager.sections.contains(where: { !$0.isHidden }) {\n                    // We have a right click that is inside the menu bar while at least one\n                    // section is visible.\n                    appState.preventShowOnHover()\n                }\n            default:\n                break\n            }\n        } else if !isMouseInsideApplicationMenu {\n            // We have a left or right click that is inside the menu bar, outside\n            // a menu bar item, and outside the application menu, so it _must_ be\n            // inside an empty menu bar space.\n            appState.preventShowOnHover()\n        }\n    }\n\n    // MARK: Handle Left Mouse Up\n\n    private func handleLeftMouseUp() {\n        guard let appearanceManager = appState?.appearanceManager else {\n            return\n        }\n        appearanceManager.setIsDraggingMenuBarItem(false)\n    }\n\n    // MARK: Handle Left Mouse Dragged\n\n    private func handleLeftMouseDragged(with event: NSEvent) {\n        guard\n            let appState,\n            event.modifierFlags.contains(.command),\n            isMouseInsideMenuBar\n        else {\n            return\n        }\n\n        // Notify each overlay panel that a menu bar item is being dragged.\n        appState.appearanceManager.setIsDraggingMenuBarItem(true)\n\n        // Don't continue if the setting to show the sections is disabled.\n        guard appState.settingsManager.advancedSettingsManager.showAllSectionsOnUserDrag else {\n            return\n        }\n\n        // Show all items, including section dividers.\n        for section in appState.menuBarManager.sections {\n            section.controlItem.state = .showItems\n            guard\n                section.controlItem.isSectionDivider,\n                !section.controlItem.isVisible\n            else {\n                continue\n            }\n            section.controlItem.isVisible = true\n        }\n    }\n\n    // MARK: Handle Show On Hover\n\n    private func handleShowOnHover() {\n        guard let appState else {\n            return\n        }\n\n        // Make sure the \"ShowOnHover\" feature is enabled and not prevented.\n        guard\n            appState.settingsManager.generalSettingsManager.showOnHover,\n            !appState.isShowOnHoverPrevented\n        else {\n            return\n        }\n\n        // Only continue if we have a hidden section (we should).\n        guard let hiddenSection = appState.menuBarManager.section(withName: .hidden) else {\n            return\n        }\n\n        let delay = appState.settingsManager.advancedSettingsManager.showOnHoverDelay\n\n        Task {\n            if hiddenSection.isHidden {\n                guard self.isMouseInsideEmptyMenuBarSpace else {\n                    return\n                }\n                try? await Task.sleep(for: .seconds(delay))\n                // Make sure the mouse is still inside.\n                guard self.isMouseInsideEmptyMenuBarSpace else {\n                    return\n                }\n                hiddenSection.show()\n            } else {\n                guard\n                    !self.isMouseInsideMenuBar,\n                    !self.isMouseInsideIceBar\n                else {\n                    return\n                }\n                try? await Task.sleep(for: .seconds(delay))\n                // Make sure the mouse is still outside.\n                guard\n                    !self.isMouseInsideMenuBar,\n                    !self.isMouseInsideIceBar\n                else {\n                    return\n                }\n                hiddenSection.hide()\n            }\n        }\n    }\n\n    // MARK: Handle Show On Scroll\n\n    private func handleShowOnScroll(with event: NSEvent) {\n        guard let appState else {\n            return\n        }\n\n        // Make sure the \"ShowOnScroll\" feature is enabled.\n        guard appState.settingsManager.generalSettingsManager.showOnScroll else {\n            return\n        }\n\n        // Make sure the mouse is inside the menu bar.\n        guard isMouseInsideMenuBar else {\n            return\n        }\n\n        // Only continue if we have a hidden section (we should).\n        guard let hiddenSection = appState.menuBarManager.section(withName: .hidden) else {\n            return\n        }\n\n        let averageDelta = (event.scrollingDeltaX + event.scrollingDeltaY) / 2\n\n        if averageDelta > 5 {\n            hiddenSection.show()\n        } else if averageDelta < -5 {\n            hiddenSection.hide()\n        }\n    }\n}\n\n// MARK: - Helpers\n\nextension EventManager {\n    /// Returns the best screen to use for event manager calculations.\n    var bestScreen: NSScreen? {\n        guard let appState else {\n            return nil\n        }\n        if appState.isActiveSpaceFullscreen {\n            return NSScreen.screenWithMouse ?? NSScreen.main\n        } else {\n            return NSScreen.main\n        }\n    }\n\n    /// A Boolean value that indicates whether the mouse pointer is within\n    /// the bounds of the menu bar.\n    var isMouseInsideMenuBar: Bool {\n        guard\n            let screen = bestScreen,\n            let appState\n        else {\n            return false\n        }\n        if appState.menuBarManager.isMenuBarHiddenBySystem || appState.isActiveSpaceFullscreen {\n            if\n                let mouseLocation = MouseCursor.locationCoreGraphics,\n                let menuBarWindow = WindowInfo.getMenuBarWindow(for: screen.displayID)\n            {\n                return menuBarWindow.frame.contains(mouseLocation)\n            }\n        } else if let mouseLocation = MouseCursor.locationAppKit {\n            return mouseLocation.y > screen.visibleFrame.maxY && mouseLocation.y <= screen.frame.maxY\n        }\n        return false\n    }\n\n    /// A Boolean value that indicates whether the mouse pointer is within\n    /// the bounds of the current application menu.\n    var isMouseInsideApplicationMenu: Bool {\n        guard\n            let mouseLocation = MouseCursor.locationCoreGraphics,\n            let screen = bestScreen,\n            let appState,\n            var applicationMenuFrame = appState.menuBarManager.getApplicationMenuFrame(for: screen.displayID)\n        else {\n            return false\n        }\n        applicationMenuFrame.size.width += applicationMenuFrame.origin.x - screen.frame.origin.x\n        applicationMenuFrame.origin.x = screen.frame.origin.x\n        return applicationMenuFrame.contains(mouseLocation)\n    }\n\n    /// A Boolean value that indicates whether the mouse pointer is within\n    /// the bounds of a menu bar item.\n    var isMouseInsideMenuBarItem: Bool {\n        guard\n            let screen = bestScreen,\n            let mouseLocation = MouseCursor.locationCoreGraphics\n        else {\n            return false\n        }\n        let menuBarItems = MenuBarItem.getMenuBarItems(on: screen.displayID, onScreenOnly: true, activeSpaceOnly: true)\n        return menuBarItems.contains { $0.frame.contains(mouseLocation) }\n    }\n\n    /// A Boolean value that indicates whether the mouse pointer is within\n    /// the bounds of the screen's notch, if it has one.\n    ///\n    /// If the screen returned from ``bestScreen`` does not have a notch,\n    /// this property returns `false`.\n    var isMouseInsideNotch: Bool {\n        guard\n            let screen = bestScreen,\n            let mouseLocation = MouseCursor.locationAppKit,\n            let frameOfNotch = screen.frameOfNotch\n        else {\n            return false\n        }\n        return frameOfNotch.contains(mouseLocation)\n    }\n\n    /// A Boolean value that indicates whether the mouse pointer is within\n    /// the bounds of an empty space in the menu bar.\n    var isMouseInsideEmptyMenuBarSpace: Bool {\n        isMouseInsideMenuBar &&\n        !isMouseInsideApplicationMenu &&\n        !isMouseInsideMenuBarItem &&\n        !isMouseInsideNotch\n    }\n\n    /// A Boolean value that indicates whether the mouse pointer is within\n    /// the bounds of the Ice Bar panel.\n    var isMouseInsideIceBar: Bool {\n        guard\n            let appState,\n            let mouseLocation = MouseCursor.locationAppKit\n        else {\n            return false\n        }\n        let panel = appState.menuBarManager.iceBarPanel\n        // Pad the frame to be more forgiving if the user accidentally\n        // moves their mouse outside of the Ice Bar.\n        let paddedFrame = panel.frame.insetBy(dx: -10, dy: -10)\n        return paddedFrame.contains(mouseLocation)\n    }\n\n    /// A Boolean value that indicates whether the mouse pointer is within\n    /// the bounds of the Ice icon.\n    var isMouseInsideIceIcon: Bool {\n        guard\n            let appState,\n            let visibleSection = appState.menuBarManager.section(withName: .visible),\n            let iceIconFrame = visibleSection.controlItem.windowFrame,\n            let mouseLocation = MouseCursor.locationAppKit\n        else {\n            return false\n        }\n        return iceIconFrame.contains(mouseLocation)\n    }\n}\n\n// MARK: - Logger\nprivate extension Logger {\n    static let eventManager = Logger(category: \"EventManager\")\n}\n"
  },
  {
    "path": "Ice/Events/EventMonitors/GlobalEventMonitor.swift",
    "content": "//\n//  GlobalEventMonitor.swift\n//  Ice\n//\n\nimport Cocoa\nimport Combine\n\n/// A type that monitors for events outside the scope of the current process.\nfinal class GlobalEventMonitor {\n    private let mask: NSEvent.EventTypeMask\n    private let handler: (NSEvent) -> Void\n    private var monitor: Any?\n\n    /// Creates an event monitor with the given event type mask and handler.\n    ///\n    /// - Parameters:\n    ///   - mask: An event type mask specifying which events to monitor.\n    ///   - handler: A handler to execute when the event monitor receives\n    ///     an event corresponding to the event types in `mask`.\n    init(mask: NSEvent.EventTypeMask, handler: @escaping (_ event: NSEvent) -> Void) {\n        self.mask = mask\n        self.handler = handler\n    }\n\n    deinit {\n        stop()\n    }\n\n    /// Starts monitoring for events.\n    func start() {\n        guard monitor == nil else {\n            return\n        }\n        monitor = NSEvent.addGlobalMonitorForEvents(\n            matching: mask,\n            handler: handler\n        )\n    }\n\n    /// Stops monitoring for events.\n    func stop() {\n        guard let monitor else {\n            return\n        }\n        NSEvent.removeMonitor(monitor)\n        self.monitor = nil\n    }\n}\n\nextension GlobalEventMonitor {\n    /// A publisher that emits global events for an event type mask.\n    struct GlobalEventPublisher: Publisher {\n        typealias Output = NSEvent\n        typealias Failure = Never\n\n        let mask: NSEvent.EventTypeMask\n\n        func receive<S: Subscriber<Output, Failure>>(subscriber: S) {\n            let subscription = GlobalEventSubscription(mask: mask, subscriber: subscriber)\n            subscriber.receive(subscription: subscription)\n        }\n    }\n\n    /// Returns a publisher that emits global events for the given event type mask.\n    ///\n    /// - Parameter mask: An event type mask specifying which events to publish.\n    static func publisher(for mask: NSEvent.EventTypeMask) -> GlobalEventPublisher {\n        GlobalEventPublisher(mask: mask)\n    }\n}\n\nextension GlobalEventMonitor.GlobalEventPublisher {\n    private final class GlobalEventSubscription<S: Subscriber<Output, Failure>>: Subscription {\n        var subscriber: S?\n        let monitor: GlobalEventMonitor\n\n        init(mask: NSEvent.EventTypeMask, subscriber: S) {\n            self.subscriber = subscriber\n            self.monitor = GlobalEventMonitor(mask: mask) { event in\n                _ = subscriber.receive(event)\n            }\n            monitor.start()\n        }\n\n        func request(_ demand: Subscribers.Demand) { }\n\n        func cancel() {\n            monitor.stop()\n            subscriber = nil\n        }\n    }\n}\n"
  },
  {
    "path": "Ice/Events/EventMonitors/LocalEventMonitor.swift",
    "content": "//\n//  LocalEventMonitor.swift\n//  Ice\n//\n\nimport Cocoa\nimport Combine\n\n/// A type that monitors for events within the scope of the current process.\nfinal class LocalEventMonitor {\n    private let mask: NSEvent.EventTypeMask\n    private let handler: (NSEvent) -> NSEvent?\n    private var monitor: Any?\n\n    /// Creates an event monitor with the given event type mask and handler.\n    ///\n    /// - Parameters:\n    ///   - mask: An event type mask specifying which events to monitor.\n    ///   - handler: A handler to execute when the event monitor receives\n    ///     an event corresponding to the event types in `mask`.\n    init(mask: NSEvent.EventTypeMask, handler: @escaping (_ event: NSEvent) -> NSEvent?) {\n        self.mask = mask\n        self.handler = handler\n    }\n\n    deinit {\n        stop()\n    }\n\n    /// Starts monitoring for events.\n    func start() {\n        guard monitor == nil else {\n            return\n        }\n        monitor = NSEvent.addLocalMonitorForEvents(\n            matching: mask,\n            handler: handler\n        )\n    }\n\n    /// Stops monitoring for events.\n    func stop() {\n        guard let monitor else {\n            return\n        }\n        NSEvent.removeMonitor(monitor)\n        self.monitor = nil\n    }\n}\n\nextension LocalEventMonitor {\n    /// A publisher that emits local events for an event type mask.\n    struct LocalEventPublisher: Publisher {\n        typealias Output = NSEvent\n        typealias Failure = Never\n\n        let mask: NSEvent.EventTypeMask\n\n        func receive<S: Subscriber<Output, Failure>>(subscriber: S) {\n            let subscription = LocalEventSubscription(mask: mask, subscriber: subscriber)\n            subscriber.receive(subscription: subscription)\n        }\n    }\n\n    /// Returns a publisher that emits local events for the given event type mask.\n    ///\n    /// - Parameter mask: An event type mask specifying which events to publish.\n    static func publisher(for mask: NSEvent.EventTypeMask) -> LocalEventPublisher {\n        LocalEventPublisher(mask: mask)\n    }\n}\n\nextension LocalEventMonitor.LocalEventPublisher {\n    private final class LocalEventSubscription<S: Subscriber<Output, Failure>>: Subscription {\n        var subscriber: S?\n        let monitor: LocalEventMonitor\n\n        init(mask: NSEvent.EventTypeMask, subscriber: S) {\n            self.subscriber = subscriber\n            self.monitor = LocalEventMonitor(mask: mask) { event in\n                _ = subscriber.receive(event)\n                return event\n            }\n            monitor.start()\n        }\n\n        func request(_ demand: Subscribers.Demand) { }\n\n        func cancel() {\n            monitor.stop()\n            subscriber = nil\n        }\n    }\n}\n"
  },
  {
    "path": "Ice/Events/EventMonitors/RunLoopLocalEventMonitor.swift",
    "content": "//\n//  RunLoopLocalEventMonitor.swift\n//  Ice\n//\n\nimport Cocoa\nimport Combine\n\nfinal class RunLoopLocalEventMonitor {\n    private let runLoop = CFRunLoopGetCurrent()\n    private let mode: RunLoop.Mode\n    private let handler: (NSEvent) -> NSEvent?\n    private let observer: CFRunLoopObserver\n\n    /// Creates an event monitor with the given event type mask and handler.\n    ///\n    /// - Parameters:\n    ///   - mask: An event type mask specifying which events to monitor.\n    ///   - handler: A handler to execute when the event monitor receives\n    ///     an event corresponding to the event types in `mask`.\n    init(\n        mask: NSEvent.EventTypeMask,\n        mode: RunLoop.Mode,\n        handler: @escaping (_ event: NSEvent) -> NSEvent?\n    ) {\n        self.mode = mode\n        self.handler = handler\n        self.observer = CFRunLoopObserverCreateWithHandler(\n            kCFAllocatorDefault,\n            CFRunLoopActivity.beforeSources.rawValue,\n            true,\n            0\n        ) { _, _ in\n            var events = [NSEvent]()\n\n            while let event = NSApp.nextEvent(matching: .any, until: nil, inMode: .default, dequeue: true) {\n                events.append(event)\n            }\n\n            for event in events {\n                var handledEvent: NSEvent?\n\n                if !mask.contains(NSEvent.EventTypeMask(rawValue: 1 << event.type.rawValue)) {\n                    handledEvent = event\n                } else if let eventFromHandler = handler(event) {\n                    handledEvent = eventFromHandler\n                }\n\n                guard let handledEvent else {\n                    continue\n                }\n\n                NSApp.postEvent(handledEvent, atStart: false)\n            }\n        }\n    }\n\n    deinit {\n        stop()\n    }\n\n    func start() {\n        CFRunLoopAddObserver(\n            runLoop,\n            observer,\n            CFRunLoopMode(mode.rawValue as CFString)\n        )\n    }\n\n    func stop() {\n        CFRunLoopRemoveObserver(\n            runLoop,\n            observer,\n            CFRunLoopMode(mode.rawValue as CFString)\n        )\n    }\n}\n\nextension RunLoopLocalEventMonitor {\n    /// A publisher that emits local events for an event type mask.\n    struct RunLoopLocalEventPublisher: Publisher {\n        typealias Output = NSEvent\n        typealias Failure = Never\n\n        let mask: NSEvent.EventTypeMask\n        let mode: RunLoop.Mode\n\n        func receive<S: Subscriber<Output, Failure>>(subscriber: S) {\n            let subscription = RunLoopLocalEventSubscription(mask: mask, mode: mode, subscriber: subscriber)\n            subscriber.receive(subscription: subscription)\n        }\n    }\n\n    /// Returns a publisher that emits local events for the given event type mask.\n    ///\n    /// - Parameter mask: An event type mask specifying which events to publish.\n    static func publisher(for mask: NSEvent.EventTypeMask, mode: RunLoop.Mode) -> RunLoopLocalEventPublisher {\n        RunLoopLocalEventPublisher(mask: mask, mode: mode)\n    }\n}\n\nextension RunLoopLocalEventMonitor.RunLoopLocalEventPublisher {\n    private final class RunLoopLocalEventSubscription<S: Subscriber<Output, Failure>>: Subscription {\n        var subscriber: S?\n        let monitor: RunLoopLocalEventMonitor\n\n        init(mask: NSEvent.EventTypeMask, mode: RunLoop.Mode, subscriber: S) {\n            self.subscriber = subscriber\n            self.monitor = RunLoopLocalEventMonitor(mask: mask, mode: mode) { event in\n                _ = subscriber.receive(event)\n                return event\n            }\n            monitor.start()\n        }\n\n        func request(_ demand: Subscribers.Demand) { }\n\n        func cancel() {\n            monitor.stop()\n            subscriber = nil\n        }\n    }\n}\n"
  },
  {
    "path": "Ice/Events/EventMonitors/UniversalEventMonitor.swift",
    "content": "//\n//  UniversalEventMonitor.swift\n//  Ice\n//\n\nimport Cocoa\nimport Combine\n\n/// A type that monitors for local and global events.\nfinal class UniversalEventMonitor {\n    private let local: LocalEventMonitor\n    private let global: GlobalEventMonitor\n\n    /// Creates an event monitor with the given event type mask and handler.\n    ///\n    /// - Parameters:\n    ///   - mask: An event type mask specifying which events to monitor.\n    ///   - handler: A handler to execute when the event monitor receives\n    ///     an event corresponding to the event types in `mask`.\n    init(mask: NSEvent.EventTypeMask, handler: @escaping (_ event: NSEvent) -> NSEvent?) {\n        self.local = LocalEventMonitor(mask: mask, handler: handler)\n        self.global = GlobalEventMonitor(mask: mask, handler: { _ = handler($0) })\n    }\n\n    deinit {\n        stop()\n    }\n\n    /// Starts monitoring for events.\n    func start() {\n        local.start()\n        global.start()\n    }\n\n    /// Stops monitoring for events.\n    func stop() {\n        local.stop()\n        global.stop()\n    }\n}\n\nextension UniversalEventMonitor {\n    /// A publisher that emits local and global events for an event type mask.\n    struct UniversalEventPublisher: Publisher {\n        typealias Output = NSEvent\n        typealias Failure = Never\n\n        let mask: NSEvent.EventTypeMask\n\n        func receive<S: Subscriber<Output, Failure>>(subscriber: S) {\n            let subscription = UniversalEventSubscription(mask: mask, subscriber: subscriber)\n            subscriber.receive(subscription: subscription)\n        }\n    }\n\n    /// Returns a publisher that emits local and global events for the given\n    /// event type mask.\n    ///\n    /// - Parameter mask: An event type mask specifying which events to publish.\n    static func publisher(for mask: NSEvent.EventTypeMask) -> UniversalEventPublisher {\n        UniversalEventPublisher(mask: mask)\n    }\n}\n\nextension UniversalEventMonitor.UniversalEventPublisher {\n    private final class UniversalEventSubscription<S: Subscriber<Output, Failure>>: Subscription {\n        var subscriber: S?\n        let monitor: UniversalEventMonitor\n\n        init(mask: NSEvent.EventTypeMask, subscriber: S) {\n            self.subscriber = subscriber\n            self.monitor = UniversalEventMonitor(mask: mask) { event in\n                _ = subscriber.receive(event)\n                return event\n            }\n            monitor.start()\n        }\n\n        func request(_ demand: Subscribers.Demand) { }\n\n        func cancel() {\n            monitor.stop()\n            subscriber = nil\n        }\n    }\n}\n"
  },
  {
    "path": "Ice/Events/EventTap.swift",
    "content": "//\n//  EventTap.swift\n//  Ice\n//\n\nimport Cocoa\n\n/// A type that receives system events from various locations within the\n/// event stream.\n@MainActor\nfinal class EventTap {\n    /// Constants that specify the possible tapping locations for events.\n    enum Location {\n        /// The location where HID system events enter the window server.\n        case hidEventTap\n\n        /// The location where HID system and remote control events enter\n        /// a login session.\n        case sessionEventTap\n\n        /// The location where session events have been annotated to flow\n        /// to an application.\n        case annotatedSessionEventTap\n\n        /// The location where annotated events are delivered to a specific\n        /// process.\n        case pid(pid_t)\n\n        var logString: String {\n            switch self {\n            case .hidEventTap: \"HID event tap\"\n            case .sessionEventTap: \"session event tap\"\n            case .annotatedSessionEventTap: \"annotated session event tap\"\n            case .pid(let pid): \"PID \\(pid)\"\n            }\n        }\n    }\n\n    /// A proxy for an event tap.\n    ///\n    /// Event tap proxies are passed to an event tap's callback, and can be\n    /// used to post additional events to the tap before the callback returns\n    /// or to disable the tap from within the callback.\n    @MainActor\n    struct Proxy {\n        private let tap: EventTap\n        private let pointer: CGEventTapProxy\n\n        /// The label associated with the event tap.\n        var label: String {\n            tap.label\n        }\n\n        /// A Boolean value that indicates whether the event tap is enabled.\n        var isEnabled: Bool {\n            tap.isEnabled\n        }\n\n        fileprivate init(tap: EventTap, pointer: CGEventTapProxy) {\n            self.tap = tap\n            self.pointer = pointer\n        }\n\n        /// Posts an event into the event stream from the location of this tap.\n        func postEvent(_ event: CGEvent) {\n            event.tapPostEvent(pointer)\n        }\n\n        /// Enables the event tap.\n        func enable() {\n            tap.enable()\n        }\n\n        /// Enables the event tap with the given timeout.\n        func enable(timeout: Duration, onTimeout: @escaping () -> Void) {\n            tap.enable(timeout: timeout, onTimeout: onTimeout)\n        }\n\n        /// Disables the event tap.\n        func disable() {\n            tap.disable()\n        }\n    }\n\n    private let runLoop = CFRunLoopGetCurrent()\n    private let mode: CFRunLoopMode = .commonModes\n    private nonisolated let callback: (EventTap, CGEventTapProxy, CGEventType, CGEvent) -> Unmanaged<CGEvent>?\n\n    private var machPort: CFMachPort?\n    private var source: CFRunLoopSource?\n\n    /// The label associated with the event tap.\n    let label: String\n\n    /// A Boolean value that indicates whether the event tap is enabled.\n    var isEnabled: Bool {\n        guard let machPort else {\n            return false\n        }\n        return CGEvent.tapIsEnabled(tap: machPort)\n    }\n\n    /// Creates a new event tap.\n    ///\n    /// - Parameters:\n    ///   - label: The label associated with the tap.\n    ///   - kind: The kind of tap to create.\n    ///   - location: The location to listen for events.\n    ///   - placement: The placement of the tap relative to other active taps.\n    ///   - types: The event types to listen for.\n    ///   - callback: A callback function to perform when the tap receives events.\n    init(\n        label: String = #function,\n        options: CGEventTapOptions,\n        location: Location,\n        place: CGEventTapPlacement,\n        types: [CGEventType],\n        callback: @MainActor @escaping (_ proxy: Proxy, _ type: CGEventType, _ event: CGEvent) -> CGEvent?\n    ) {\n        self.label = label\n        self.callback = { @MainActor tap, pointer, type, event in\n            callback(Proxy(tap: tap, pointer: pointer), type, event).map(Unmanaged.passUnretained)\n        }\n        guard let machPort = Self.createTapMachPort(\n            location: location,\n            place: place,\n            options: options,\n            eventsOfInterest: types.reduce(into: 0) { $0 |= 1 << $1.rawValue },\n            callback: handleEvent,\n            userInfo: Unmanaged.passUnretained(self).toOpaque()\n        ) else {\n            Logger.eventTap.error(\"Error creating mach port for event tap \\\"\\(self.label)\\\"\")\n            return\n        }\n        guard let source = CFMachPortCreateRunLoopSource(nil, machPort, 0) else {\n            Logger.eventTap.error(\"Error creating run loop source for event tap \\\"\\(self.label)\\\"\")\n            return\n        }\n        self.machPort = machPort\n        self.source = source\n    }\n\n    deinit {\n        guard let machPort else {\n            return\n        }\n        CFRunLoopRemoveSource(runLoop, source, mode)\n        CGEvent.tapEnable(tap: machPort, enable: false)\n        CFMachPortInvalidate(machPort)\n    }\n\n    fileprivate nonisolated static func performCallback(\n        for eventTap: EventTap,\n        proxy: CGEventTapProxy,\n        type: CGEventType,\n        event: CGEvent\n    ) -> Unmanaged<CGEvent>? {\n        let callback = eventTap.callback\n        return callback(eventTap, proxy, type, event)\n    }\n\n    private static func createTapMachPort(\n        location: Location,\n        place: CGEventTapPlacement,\n        options: CGEventTapOptions,\n        eventsOfInterest: CGEventMask,\n        callback: CGEventTapCallBack,\n        userInfo: UnsafeMutableRawPointer?\n    ) -> CFMachPort? {\n        if case .pid(let pid) = location {\n            return CGEvent.tapCreateForPid(\n                pid: pid,\n                place: place,\n                options: options,\n                eventsOfInterest: eventsOfInterest,\n                callback: callback,\n                userInfo: userInfo\n            )\n        }\n\n        let tap: CGEventTapLocation? = switch location {\n        case .hidEventTap: .cghidEventTap\n        case .sessionEventTap: .cgSessionEventTap\n        case .annotatedSessionEventTap: .cgAnnotatedSessionEventTap\n        case .pid: nil\n        }\n\n        guard let tap else {\n            return nil\n        }\n\n        return CGEvent.tapCreate(\n            tap: tap,\n            place: place,\n            options: options,\n            eventsOfInterest: eventsOfInterest,\n            callback: callback,\n            userInfo: userInfo\n        )\n    }\n\n    private func withUnwrappedComponents(body: @MainActor (CFRunLoop, CFRunLoopSource, CFMachPort) -> Void) {\n        guard let runLoop else {\n            Logger.eventTap.error(\"Missing run loop for event tap \\\"\\(self.label)\\\"\")\n            return\n        }\n        guard let source else {\n            Logger.eventTap.error(\"Missing run loop source for event tap \\\"\\(self.label)\\\"\")\n            return\n        }\n        guard let machPort else {\n            Logger.eventTap.error(\"Missing mach port for event tap \\\"\\(self.label)\\\"\")\n            return\n        }\n        body(runLoop, source, machPort)\n    }\n\n    /// Enables the event tap.\n    func enable() {\n        withUnwrappedComponents { runLoop, source, machPort in\n            CFRunLoopAddSource(runLoop, source, mode)\n            CGEvent.tapEnable(tap: machPort, enable: true)\n        }\n    }\n\n    /// Enables the event tap with the given timeout.\n    func enable(timeout: Duration, onTimeout: @escaping () -> Void) {\n        enable()\n        Task { [weak self] in\n            try await Task.sleep(for: timeout)\n            if self?.isEnabled == true {\n                onTimeout()\n            }\n        }\n    }\n\n    /// Disables the event tap.\n    func disable() {\n        withUnwrappedComponents { runLoop, source, machPort in\n            CFRunLoopRemoveSource(runLoop, source, mode)\n            CGEvent.tapEnable(tap: machPort, enable: false)\n        }\n    }\n}\n\n// MARK: - Handle Event\nprivate func handleEvent(\n    proxy: CGEventTapProxy,\n    type: CGEventType,\n    event: CGEvent,\n    refcon: UnsafeMutableRawPointer?\n) -> Unmanaged<CGEvent>? {\n    guard let refcon else {\n        return Unmanaged.passRetained(event)\n    }\n    let eventTap = Unmanaged<EventTap>.fromOpaque(refcon).takeUnretainedValue()\n    return EventTap.performCallback(for: eventTap, proxy: proxy, type: type, event: event)\n}\n\n// MARK: - Logger\nprivate extension Logger {\n    static let eventTap = Logger(category: \"EventTap\")\n}\n"
  },
  {
    "path": "Ice/Hotkeys/Hotkey.swift",
    "content": "//\n//  Hotkey.swift\n//  Ice\n//\n\nimport Combine\n\n/// A combination of a key and modifiers that can be used to\n/// trigger actions on system-wide key-up or key-down events.\nfinal class Hotkey: ObservableObject {\n    private weak var appState: AppState?\n\n    private var listener: Listener?\n\n    let action: HotkeyAction\n\n    @Published var keyCombination: KeyCombination? {\n        didSet {\n            enable()\n        }\n    }\n\n    var isEnabled: Bool {\n        listener != nil\n    }\n\n    init(keyCombination: KeyCombination?, action: HotkeyAction) {\n        self.keyCombination = keyCombination\n        self.action = action\n    }\n\n    func assignAppState(_ appState: AppState) {\n        self.appState = appState\n        enable()\n    }\n\n    func enable() {\n        disable()\n        listener = Listener(hotkey: self, eventKind: .keyDown, appState: appState)\n    }\n\n    func disable() {\n        listener?.invalidate()\n        listener = nil\n    }\n}\n\nextension Hotkey {\n    /// An object that manges the lifetime of a hotkey observation.\n    private final class Listener {\n        private weak var appState: AppState?\n\n        private var id: UInt32?\n\n        var isValid: Bool {\n            id != nil\n        }\n\n        init?(hotkey: Hotkey, eventKind: HotkeyRegistry.EventKind, appState: AppState?) {\n            guard\n                let appState,\n                hotkey.keyCombination != nil\n            else {\n                return nil\n            }\n            let id = appState.hotkeyRegistry.register(\n                hotkey: hotkey,\n                eventKind: eventKind\n            ) { [weak appState] in\n                guard let appState else {\n                    return\n                }\n                Task {\n                    await hotkey.action.perform(appState: appState)\n                }\n            }\n            guard let id else {\n                return nil\n            }\n            self.appState = appState\n            self.id = id\n        }\n\n        deinit {\n            invalidate()\n        }\n\n        func invalidate() {\n            guard isValid else {\n                return\n            }\n            guard let appState else {\n                Logger.hotkey.error(\"Error invalidating hotkey: Missing AppState\")\n                return\n            }\n            defer {\n                id = nil\n            }\n            if let id {\n                appState.hotkeyRegistry.unregister(id)\n            }\n        }\n    }\n}\n\n// MARK: Hotkey: Codable\nextension Hotkey: Codable {\n    private enum CodingKeys: CodingKey {\n        case keyCombination\n        case action\n    }\n\n    convenience init(from decoder: any Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        try self.init(\n            keyCombination: container.decode(KeyCombination?.self, forKey: .keyCombination),\n            action: container.decode(HotkeyAction.self, forKey: .action)\n        )\n    }\n\n    func encode(to encoder: any Encoder) throws {\n        var container = encoder.container(keyedBy: CodingKeys.self)\n        try container.encode(keyCombination, forKey: .keyCombination)\n        try container.encode(action, forKey: .action)\n    }\n}\n\n// MARK: Hotkey: Equatable\nextension Hotkey: Equatable {\n    static func == (lhs: Hotkey, rhs: Hotkey) -> Bool {\n        lhs.keyCombination == rhs.keyCombination &&\n        lhs.action == rhs.action\n    }\n}\n\n// MARK: Hotkey: Hashable\nextension Hotkey: Hashable {\n    func hash(into hasher: inout Hasher) {\n        hasher.combine(keyCombination)\n        hasher.combine(action)\n    }\n}\n\n// MARK: - Logger\nprivate extension Logger {\n    static let hotkey = Logger(category: \"Hotkey\")\n}\n"
  },
  {
    "path": "Ice/Hotkeys/HotkeyAction.swift",
    "content": "//\n//  HotkeyAction.swift\n//  Ice\n//\n\nenum HotkeyAction: String, Codable, CaseIterable {\n    // Menu Bar Sections\n    case toggleHiddenSection = \"ToggleHiddenSection\"\n    case toggleAlwaysHiddenSection = \"ToggleAlwaysHiddenSection\"\n\n    // Menu Bar Items\n    case searchMenuBarItems = \"SearchMenuBarItems\"\n\n    // Other\n    case enableIceBar = \"EnableIceBar\"\n    case showSectionDividers = \"ShowSectionDividers\"\n    case toggleApplicationMenus = \"ToggleApplicationMenus\"\n\n    @MainActor\n    func perform(appState: AppState) async {\n        switch self {\n        case .toggleHiddenSection:\n            guard let section = appState.menuBarManager.section(withName: .hidden) else {\n                return\n            }\n            section.toggle()\n            // Prevent the section from automatically rehiding after mouse movement.\n            if !section.isHidden {\n                appState.preventShowOnHover()\n            }\n        case .toggleAlwaysHiddenSection:\n            guard let section = appState.menuBarManager.section(withName: .alwaysHidden) else {\n                return\n            }\n            section.toggle()\n            // Prevent the section from automatically rehiding after mouse movement.\n            if !section.isHidden {\n                appState.preventShowOnHover()\n            }\n        case .searchMenuBarItems:\n            await appState.menuBarManager.searchPanel.toggle()\n        case .enableIceBar:\n            appState.settingsManager.generalSettingsManager.useIceBar.toggle()\n        case .showSectionDividers:\n            appState.settingsManager.advancedSettingsManager.showSectionDividers.toggle()\n        case .toggleApplicationMenus:\n            appState.menuBarManager.toggleApplicationMenus()\n        }\n    }\n}\n"
  },
  {
    "path": "Ice/Hotkeys/HotkeyRegistry.swift",
    "content": "//\n//  HotkeyRegistry.swift\n//  Ice\n//\n\nimport Carbon.HIToolbox\nimport Cocoa\nimport Combine\n\n/// An object that manages the registration, storage, and unregistration of hotkeys.\nfinal class HotkeyRegistry {\n    /// The event kinds that a hotkey can be registered for.\n    enum EventKind {\n        case keyUp\n        case keyDown\n\n        fileprivate init?(event: EventRef) {\n            switch Int(GetEventKind(event)) {\n            case kEventHotKeyPressed:\n                self = .keyDown\n            case kEventHotKeyReleased:\n                self = .keyUp\n            default:\n                return nil\n            }\n        }\n    }\n\n    /// An object that stores the information needed to cancel a registration.\n    private final class Registration {\n        let eventKind: EventKind\n        let key: KeyCode\n        let modifiers: Modifiers\n        let hotKeyID: EventHotKeyID\n        var hotKeyRef: EventHotKeyRef?\n        let handler: () -> Void\n\n        init(\n            eventKind: EventKind,\n            key: KeyCode,\n            modifiers: Modifiers,\n            hotKeyID: EventHotKeyID,\n            hotKeyRef: EventHotKeyRef,\n            handler: @escaping () -> Void\n        ) {\n            self.eventKind = eventKind\n            self.key = key\n            self.modifiers = modifiers\n            self.hotKeyID = hotKeyID\n            self.hotKeyRef = hotKeyRef\n            self.handler = handler\n        }\n    }\n\n    private let signature = OSType(1231250720) // OSType for Ice\n\n    private var eventHandlerRef: EventHandlerRef?\n\n    private var registrations = [UInt32: Registration]()\n\n    private var cancellables = Set<AnyCancellable>()\n\n    /// Installs the global event handler reference, if it isn't already installed.\n    private func installIfNeeded() -> OSStatus {\n        guard eventHandlerRef == nil else {\n            return noErr\n        }\n\n        NotificationCenter.default\n            .publisher(for: NSMenu.didBeginTrackingNotification)\n            .sink { [weak self] _ in\n                self?.unregisterAndRetainAll()\n            }\n            .store(in: &cancellables)\n\n        NotificationCenter.default\n            .publisher(for: NSMenu.didEndTrackingNotification)\n            .sink { [weak self] _ in\n                self?.registerAllRetained()\n            }\n            .store(in: &cancellables)\n\n        let handler: EventHandlerUPP = { _, event, userData in\n            guard\n                let event,\n                let userData\n            else {\n                return OSStatus(eventNotHandledErr)\n            }\n            let registry = Unmanaged<HotkeyRegistry>.fromOpaque(userData).takeUnretainedValue()\n            return registry.performEventHandler(for: event)\n        }\n\n        let eventTypes: [EventTypeSpec] = [\n            EventTypeSpec(eventClass: OSType(kEventClassKeyboard), eventKind: UInt32(kEventHotKeyPressed)),\n            EventTypeSpec(eventClass: OSType(kEventClassKeyboard), eventKind: UInt32(kEventHotKeyReleased)),\n        ]\n\n        return InstallEventHandler(\n            GetEventDispatcherTarget(),\n            handler,\n            eventTypes.count,\n            eventTypes,\n            Unmanaged.passUnretained(self).toOpaque(),\n            &eventHandlerRef\n        )\n    }\n\n    /// Registers the given hotkey for the given event kind and returns the\n    /// identifier of the registration on success.\n    ///\n    /// The returned identifier can be used to unregister the hotkey using\n    /// the ``unregister(_:)`` function.\n    ///\n    /// - Parameters:\n    ///   - hotkey: The hotkey to register the handler with.\n    ///   - eventKind: The event kind to register the handler with.\n    ///   - handler: The handler to perform when `hotkey` is triggered with\n    ///     the event kind specified by `eventKind`.\n    ///\n    /// - Returns: The registration's identifier on success, `nil` on failure.\n    func register(hotkey: Hotkey, eventKind: EventKind, handler: @escaping () -> Void) -> UInt32? {\n        enum Context {\n            static var currentID: UInt32 = 0\n        }\n\n        defer {\n            Context.currentID += 1\n        }\n\n        guard let keyCombination = hotkey.keyCombination else {\n            Logger.hotkeyRegistry.error(\"Hotkey does not have a valid key combination\")\n            return nil\n        }\n\n        var status = installIfNeeded()\n\n        guard status == noErr else {\n            Logger.hotkeyRegistry.error(\"Hotkey event handler installation failed with status \\(status)\")\n            return nil\n        }\n\n        let id = Context.currentID\n\n        guard registrations[id] == nil else {\n            Logger.hotkeyRegistry.error(\"Hotkey already registered for id \\(id)\")\n            return nil\n        }\n\n        let hotKeyID = EventHotKeyID(signature: signature, id: id)\n        var hotKeyRef: EventHotKeyRef?\n        status = RegisterEventHotKey(\n            UInt32(keyCombination.key.rawValue),\n            UInt32(keyCombination.modifiers.carbonFlags),\n            hotKeyID,\n            GetEventDispatcherTarget(),\n            0,\n            &hotKeyRef\n        )\n\n        guard status == noErr else {\n            Logger.hotkeyRegistry.error(\"Hotkey registration failed with status \\(status)\")\n            return nil\n        }\n\n        guard let hotKeyRef else {\n            Logger.hotkeyRegistry.error(\"Hotkey registration failed due to invalid EventHotKeyRef\")\n            return nil\n        }\n\n        let registration = Registration(\n            eventKind: eventKind,\n            key: keyCombination.key,\n            modifiers: keyCombination.modifiers,\n            hotKeyID: hotKeyID,\n            hotKeyRef: hotKeyRef,\n            handler: handler\n        )\n        registrations[id] = registration\n\n        return id\n    }\n\n    /// Unregisters the key combination with the given identifier, retaining\n    /// its registration in an inactive state.\n    private func retainedUnregister(_ id: UInt32) {\n        guard let registration = registrations[id] else {\n            Logger.hotkeyRegistry.error(\"No registered key combination for id \\(id)\")\n            return\n        }\n        let status = UnregisterEventHotKey(registration.hotKeyRef)\n        guard status == noErr else {\n            Logger.hotkeyRegistry.error(\"Hotkey unregistration failed with status \\(status)\")\n            return\n        }\n        registration.hotKeyRef = nil\n    }\n\n    /// Unregisters the key combination with the given identifier.\n    ///\n    /// - Parameter id: An identifier returned from a call to the\n    ///   ``register(hotkey:eventKind:handler:)`` function.\n    func unregister(_ id: UInt32) {\n        retainedUnregister(id)\n        registrations.removeValue(forKey: id)\n    }\n\n    /// Unregisters all key combinations, retaining their registrations\n    /// in an inactive state.\n    private func unregisterAndRetainAll() {\n        for (id, _) in registrations {\n            retainedUnregister(id)\n        }\n    }\n\n    /// Registers all registrations that were retained during a call to\n    /// ``retainedUnregister(_:)``\n    private func registerAllRetained() {\n        for registration in registrations.values {\n            guard registration.hotKeyRef == nil else {\n                continue\n            }\n\n            var hotKeyRef: EventHotKeyRef?\n            let status = RegisterEventHotKey(\n                UInt32(registration.key.rawValue),\n                UInt32(registration.modifiers.carbonFlags),\n                registration.hotKeyID,\n                GetEventDispatcherTarget(),\n                0,\n                &hotKeyRef\n            )\n\n            guard\n                status == noErr,\n                let hotKeyRef\n            else {\n                registrations.removeValue(forKey: registration.hotKeyID.id)\n                Logger.hotkeyRegistry.error(\"Hotkey registration failed with status \\(status)\")\n                continue\n            }\n\n            registration.hotKeyRef = hotKeyRef\n        }\n    }\n\n    /// Retrieves and performs the event handler stored under the\n    /// identifier for the specified event.\n    private func performEventHandler(for event: EventRef?) -> OSStatus {\n        guard let event else {\n            return OSStatus(eventNotHandledErr)\n        }\n\n        // create a hot key id from the event\n        var hotKeyID = EventHotKeyID()\n        let status = GetEventParameter(\n            event,\n            EventParamName(kEventParamDirectObject),\n            EventParamType(typeEventHotKeyID),\n            nil,\n            MemoryLayout<EventHotKeyID>.size,\n            nil,\n            &hotKeyID\n        )\n\n        // make sure creation was successful\n        guard status == noErr else {\n            return status\n        }\n\n        // make sure the event signature matches our signature and\n        // that an event handler is registered for the event\n        guard\n            hotKeyID.signature == signature,\n            let registration = registrations[hotKeyID.id],\n            registration.eventKind == EventKind(event: event)\n        else {\n            return OSStatus(eventNotHandledErr)\n        }\n\n        // all checks passed; perform the event handler\n        registration.handler()\n\n        return noErr\n    }\n}\n\n// MARK: - Logger\nprivate extension Logger {\n    static let hotkeyRegistry = Logger(category: \"HotkeyRegistry\")\n}\n"
  },
  {
    "path": "Ice/Hotkeys/KeyCode.swift",
    "content": "//\n//  KeyCode.swift\n//  Ice\n//\n\nimport Carbon.HIToolbox\n\n/// Representation of a physical key on a keyboard.\nstruct KeyCode: Codable, Hashable, RawRepresentable {\n    let rawValue: Int\n\n    // MARK: Letters\n\n    static let a = KeyCode(rawValue: kVK_ANSI_A)\n    static let b = KeyCode(rawValue: kVK_ANSI_B)\n    static let c = KeyCode(rawValue: kVK_ANSI_C)\n    static let d = KeyCode(rawValue: kVK_ANSI_D)\n    static let e = KeyCode(rawValue: kVK_ANSI_E)\n    static let f = KeyCode(rawValue: kVK_ANSI_F)\n    static let g = KeyCode(rawValue: kVK_ANSI_G)\n    static let h = KeyCode(rawValue: kVK_ANSI_H)\n    static let i = KeyCode(rawValue: kVK_ANSI_I)\n    static let j = KeyCode(rawValue: kVK_ANSI_J)\n    static let k = KeyCode(rawValue: kVK_ANSI_K)\n    static let l = KeyCode(rawValue: kVK_ANSI_L)\n    static let m = KeyCode(rawValue: kVK_ANSI_M)\n    static let n = KeyCode(rawValue: kVK_ANSI_N)\n    static let o = KeyCode(rawValue: kVK_ANSI_O)\n    static let p = KeyCode(rawValue: kVK_ANSI_P)\n    static let q = KeyCode(rawValue: kVK_ANSI_Q)\n    static let r = KeyCode(rawValue: kVK_ANSI_R)\n    static let s = KeyCode(rawValue: kVK_ANSI_S)\n    static let t = KeyCode(rawValue: kVK_ANSI_T)\n    static let u = KeyCode(rawValue: kVK_ANSI_U)\n    static let v = KeyCode(rawValue: kVK_ANSI_V)\n    static let w = KeyCode(rawValue: kVK_ANSI_W)\n    static let x = KeyCode(rawValue: kVK_ANSI_X)\n    static let y = KeyCode(rawValue: kVK_ANSI_Y)\n    static let z = KeyCode(rawValue: kVK_ANSI_Z)\n\n    // MARK: Numbers\n\n    static let zero = KeyCode(rawValue: kVK_ANSI_0)\n    static let one = KeyCode(rawValue: kVK_ANSI_1)\n    static let two = KeyCode(rawValue: kVK_ANSI_2)\n    static let three = KeyCode(rawValue: kVK_ANSI_3)\n    static let four = KeyCode(rawValue: kVK_ANSI_4)\n    static let five = KeyCode(rawValue: kVK_ANSI_5)\n    static let six = KeyCode(rawValue: kVK_ANSI_6)\n    static let seven = KeyCode(rawValue: kVK_ANSI_7)\n    static let eight = KeyCode(rawValue: kVK_ANSI_8)\n    static let nine = KeyCode(rawValue: kVK_ANSI_9)\n\n    // MARK: Symbols\n\n    static let equal = KeyCode(rawValue: kVK_ANSI_Equal)\n    static let minus = KeyCode(rawValue: kVK_ANSI_Minus)\n    static let rightBracket = KeyCode(rawValue: kVK_ANSI_RightBracket)\n    static let leftBracket = KeyCode(rawValue: kVK_ANSI_LeftBracket)\n    static let quote = KeyCode(rawValue: kVK_ANSI_Quote)\n    static let semicolon = KeyCode(rawValue: kVK_ANSI_Semicolon)\n    static let backslash = KeyCode(rawValue: kVK_ANSI_Backslash)\n    static let comma = KeyCode(rawValue: kVK_ANSI_Comma)\n    static let slash = KeyCode(rawValue: kVK_ANSI_Slash)\n    static let period = KeyCode(rawValue: kVK_ANSI_Period)\n    static let grave = KeyCode(rawValue: kVK_ANSI_Grave)\n\n    // MARK: Keypad\n\n    static let keypad0 = KeyCode(rawValue: kVK_ANSI_Keypad0)\n    static let keypad1 = KeyCode(rawValue: kVK_ANSI_Keypad1)\n    static let keypad2 = KeyCode(rawValue: kVK_ANSI_Keypad2)\n    static let keypad3 = KeyCode(rawValue: kVK_ANSI_Keypad3)\n    static let keypad4 = KeyCode(rawValue: kVK_ANSI_Keypad4)\n    static let keypad5 = KeyCode(rawValue: kVK_ANSI_Keypad5)\n    static let keypad6 = KeyCode(rawValue: kVK_ANSI_Keypad6)\n    static let keypad7 = KeyCode(rawValue: kVK_ANSI_Keypad7)\n    static let keypad8 = KeyCode(rawValue: kVK_ANSI_Keypad8)\n    static let keypad9 = KeyCode(rawValue: kVK_ANSI_Keypad9)\n    static let keypadDecimal = KeyCode(rawValue: kVK_ANSI_KeypadDecimal)\n    static let keypadMultiply = KeyCode(rawValue: kVK_ANSI_KeypadMultiply)\n    static let keypadPlus = KeyCode(rawValue: kVK_ANSI_KeypadPlus)\n    static let keypadClear = KeyCode(rawValue: kVK_ANSI_KeypadClear)\n    static let keypadDivide = KeyCode(rawValue: kVK_ANSI_KeypadDivide)\n    static let keypadEnter = KeyCode(rawValue: kVK_ANSI_KeypadEnter)\n    static let keypadMinus = KeyCode(rawValue: kVK_ANSI_KeypadMinus)\n    static let keypadEquals = KeyCode(rawValue: kVK_ANSI_KeypadEquals)\n\n    // MARK: Editing\n\n    static let space = KeyCode(rawValue: kVK_Space)\n    static let tab = KeyCode(rawValue: kVK_Tab)\n    static let `return` = KeyCode(rawValue: kVK_Return)\n    static let delete = KeyCode(rawValue: kVK_Delete)\n    static let forwardDelete = KeyCode(rawValue: kVK_ForwardDelete)\n\n    // MARK: Modifiers\n\n    static let control = KeyCode(rawValue: kVK_Control)\n    static let option = KeyCode(rawValue: kVK_Option)\n    static let shift = KeyCode(rawValue: kVK_Shift)\n    static let command = KeyCode(rawValue: kVK_Command)\n    static let rightControl = KeyCode(rawValue: kVK_RightControl)\n    static let rightOption = KeyCode(rawValue: kVK_RightOption)\n    static let rightShift = KeyCode(rawValue: kVK_RightShift)\n    static let rightCommand = KeyCode(rawValue: kVK_RightCommand)\n    static let capsLock = KeyCode(rawValue: kVK_CapsLock)\n    static let function = KeyCode(rawValue: kVK_Function)\n\n    // MARK: Function\n\n    static let f1 = KeyCode(rawValue: kVK_F1)\n    static let f2 = KeyCode(rawValue: kVK_F2)\n    static let f3 = KeyCode(rawValue: kVK_F3)\n    static let f4 = KeyCode(rawValue: kVK_F4)\n    static let f5 = KeyCode(rawValue: kVK_F5)\n    static let f6 = KeyCode(rawValue: kVK_F6)\n    static let f7 = KeyCode(rawValue: kVK_F7)\n    static let f8 = KeyCode(rawValue: kVK_F8)\n    static let f9 = KeyCode(rawValue: kVK_F9)\n    static let f10 = KeyCode(rawValue: kVK_F10)\n    static let f11 = KeyCode(rawValue: kVK_F11)\n    static let f12 = KeyCode(rawValue: kVK_F12)\n    static let f13 = KeyCode(rawValue: kVK_F13)\n    static let f14 = KeyCode(rawValue: kVK_F14)\n    static let f15 = KeyCode(rawValue: kVK_F15)\n    static let f16 = KeyCode(rawValue: kVK_F16)\n    static let f17 = KeyCode(rawValue: kVK_F17)\n    static let f18 = KeyCode(rawValue: kVK_F18)\n    static let f19 = KeyCode(rawValue: kVK_F19)\n    static let f20 = KeyCode(rawValue: kVK_F20)\n\n    // MARK: Navigation\n\n    static let pageUp = KeyCode(rawValue: kVK_PageUp)\n    static let pageDown = KeyCode(rawValue: kVK_PageDown)\n    static let home = KeyCode(rawValue: kVK_Home)\n    static let end = KeyCode(rawValue: kVK_End)\n    static let escape = KeyCode(rawValue: kVK_Escape)\n    static let help = KeyCode(rawValue: kVK_Help)\n    static let leftArrow = KeyCode(rawValue: kVK_LeftArrow)\n    static let rightArrow = KeyCode(rawValue: kVK_RightArrow)\n    static let downArrow = KeyCode(rawValue: kVK_DownArrow)\n    static let upArrow = KeyCode(rawValue: kVK_UpArrow)\n\n    // MARK: Media\n\n    static let volumeUp = KeyCode(rawValue: kVK_VolumeUp)\n    static let volumeDown = KeyCode(rawValue: kVK_VolumeDown)\n    static let mute = KeyCode(rawValue: kVK_Mute)\n}\n\n// MARK: Key Equivalent\nextension KeyCode {\n    /// System representation.\n    var keyEquivalent: String {\n        guard\n            let inputSource = TISCopyCurrentASCIICapableKeyboardLayoutInputSource()?.takeRetainedValue(),\n            let layoutData = TISGetInputSourceProperty(inputSource, kTISPropertyUnicodeKeyLayoutData)\n        else {\n            return \"\"\n        }\n\n        let layoutBytes = CFDataGetBytePtr(unsafeBitCast(layoutData, to: CFData.self))\n        let layoutPtr = unsafeBitCast(layoutBytes, to: UnsafePointer<UCKeyboardLayout>.self)\n\n        let modifierKeyState: UInt32 = 0 // empty modifier key state\n        var deadKeyState: UInt32 = 0\n        let maxLength = 4\n        var actualLength = 0\n        var codeUnits = [UniChar](repeating: 0, count: maxLength)\n\n        let status = UCKeyTranslate(\n            layoutPtr,\n            UInt16(rawValue),\n            UInt16(kUCKeyActionDisplay),\n            modifierKeyState,\n            UInt32(LMGetKbdType()),\n            OptionBits(kUCKeyTranslateNoDeadKeysBit),\n            &deadKeyState,\n            maxLength,\n            &actualLength,\n            &codeUnits\n        )\n\n        guard status == noErr else {\n            return \"\"\n        }\n\n        return String(utf16CodeUnits: codeUnits, count: actualLength)\n    }\n}\n\n// MARK: Custom String Mappings\nprivate let customStringMappings = [\n    // standard keys\n    KeyCode.space: \"Space\",\n    KeyCode.tab: \"⇥\",\n    KeyCode.return: \"⏎\",\n    KeyCode.delete: \"⌫\",\n    KeyCode.forwardDelete: \"⌦\",\n    KeyCode.f1: \"F1\",\n    KeyCode.f2: \"F2\",\n    KeyCode.f3: \"F3\",\n    KeyCode.f4: \"F4\",\n    KeyCode.f5: \"F5\",\n    KeyCode.f6: \"F6\",\n    KeyCode.f7: \"F7\",\n    KeyCode.f8: \"F8\",\n    KeyCode.f9: \"F9\",\n    KeyCode.f10: \"F10\",\n    KeyCode.f11: \"F11\",\n    KeyCode.f12: \"F12\",\n    KeyCode.f13: \"F13\",\n    KeyCode.f14: \"F14\",\n    KeyCode.f15: \"F15\",\n    KeyCode.f16: \"F16\",\n    KeyCode.f17: \"F17\",\n    KeyCode.f18: \"F18\",\n    KeyCode.f19: \"F19\",\n    KeyCode.f20: \"F20\",\n    KeyCode.pageUp: \"⇞\",\n    KeyCode.pageDown: \"⇟\",\n    KeyCode.home: \"↖\",\n    KeyCode.end: \"↘\",\n    KeyCode.escape: \"⎋\",\n    KeyCode.leftArrow: \"←\",\n    KeyCode.rightArrow: \"→\",\n    KeyCode.downArrow: \"↓\",\n    KeyCode.upArrow: \"↑\",\n    KeyCode.capsLock: \"⇪\",\n    KeyCode.control: \"⌃\",\n    KeyCode.option: \"⌥\",\n    KeyCode.shift: \"⇧\",\n    KeyCode.command: \"⌘\",\n    KeyCode.rightControl: \"⌃\",\n    KeyCode.rightOption: \"⌥\",\n    KeyCode.rightShift: \"⇧\",\n    KeyCode.rightCommand: \"⌘\",\n    KeyCode.keypadClear: \"⌧\",\n    KeyCode.keypadEnter: \"⌤\",\n    // media keys\n    KeyCode.volumeUp: \"\\u{1F50A}\",   // 'SPEAKER WITH THREE SOUND WAVES'\n    KeyCode.volumeDown: \"\\u{1F509}\", // 'SPEAKER WITH ONE SOUND WAVE'\n    KeyCode.mute: \"\\u{1F507}\",       // 'SPEAKER WITH CANCELLATION STROKE'\n    // keypad keys\n    KeyCode.keypad0: \"0⃣\",\n    KeyCode.keypad1: \"1⃣\",\n    KeyCode.keypad2: \"2⃣\",\n    KeyCode.keypad3: \"3⃣\",\n    KeyCode.keypad4: \"4⃣\",\n    KeyCode.keypad5: \"5⃣\",\n    KeyCode.keypad6: \"6⃣\",\n    KeyCode.keypad7: \"7⃣\",\n    KeyCode.keypad8: \"8⃣\",\n    KeyCode.keypad9: \"9⃣\",\n    KeyCode.keypadDecimal: \".⃣\",\n    KeyCode.keypadDivide: \"/⃣\",\n    KeyCode.keypadEquals: \"=⃣\",\n    KeyCode.keypadMinus: \"-⃣\",\n    KeyCode.keypadMultiply: \"*⃣\",\n    KeyCode.keypadPlus: \"+⃣\",\n    // other keys\n    KeyCode.function: \"🌐︎︎\",\n    KeyCode.help: \"?⃝\",\n]\n\n// MARK: String Value\nextension KeyCode {\n    /// Custom string representation.\n    var stringValue: String {\n        customStringMappings[self, default: keyEquivalent]\n    }\n}\n"
  },
  {
    "path": "Ice/Hotkeys/KeyCombination.swift",
    "content": "//\n//  KeyCombination.swift\n//  Ice\n//\n\nimport Carbon.HIToolbox\nimport Cocoa\n\nstruct KeyCombination: Hashable {\n    let key: KeyCode\n    let modifiers: Modifiers\n\n    var stringValue: String {\n        modifiers.symbolicValue + key.stringValue\n    }\n\n    init(key: KeyCode, modifiers: Modifiers) {\n        self.key = key\n        self.modifiers = modifiers\n    }\n\n    init(event: NSEvent) {\n        let key = KeyCode(rawValue: Int(event.keyCode))\n        let modifiers = Modifiers(nsEventFlags: event.modifierFlags)\n        self.init(key: key, modifiers: modifiers)\n    }\n}\n\nprivate func getSystemReservedKeyCombinations() -> [KeyCombination] {\n    var symbolicHotkeys: Unmanaged<CFArray>?\n    let status = CopySymbolicHotKeys(&symbolicHotkeys)\n\n    guard status == noErr else {\n        Logger.keyCombination.error(\"CopySymbolicHotKeys returned invalid status: \\(status)\")\n        return []\n    }\n    guard let reservedHotkeys = symbolicHotkeys?.takeRetainedValue() as? [[String: Any]] else {\n        Logger.keyCombination.error(\"Failed to serialize symbolic hotkeys\")\n        return []\n    }\n\n    return reservedHotkeys.compactMap { hotkey in\n        guard\n            hotkey[kHISymbolicHotKeyEnabled] as? Bool == true,\n            let keyCode = hotkey[kHISymbolicHotKeyCode] as? Int,\n            let modifiers = hotkey[kHISymbolicHotKeyModifiers] as? Int\n        else {\n            return nil\n        }\n        return KeyCombination(\n            key: KeyCode(rawValue: keyCode),\n            modifiers: Modifiers(carbonFlags: modifiers)\n        )\n    }\n}\n\nextension KeyCombination {\n    /// Returns a Boolean value that indicates whether this key\n    /// combination is reserved for system use.\n    var isReservedBySystem: Bool {\n        getSystemReservedKeyCombinations().contains(self)\n    }\n}\n\nextension KeyCombination: Codable {\n    init(from decoder: any Decoder) throws {\n        var container = try decoder.unkeyedContainer()\n        guard container.count == 2 else {\n            throw DecodingError.dataCorrupted(\n                DecodingError.Context(\n                    codingPath: decoder.codingPath,\n                    debugDescription: \"Expected 2 encoded values, found \\(container.count ?? 0)\"\n                )\n            )\n        }\n        self.key = try KeyCode(rawValue: container.decode(Int.self))\n        self.modifiers = try Modifiers(rawValue: container.decode(Int.self))\n    }\n\n    func encode(to encoder: any Encoder) throws {\n        var container = encoder.unkeyedContainer()\n        try container.encode(key.rawValue)\n        try container.encode(modifiers.rawValue)\n    }\n}\n\n// MARK: - Logger\nprivate extension Logger {\n    static let keyCombination = Logger(category: \"KeyCombination\")\n}\n"
  },
  {
    "path": "Ice/Hotkeys/Modifiers.swift",
    "content": "//\n//  Modifiers.swift\n//  Ice\n//\n\nimport Carbon.HIToolbox\nimport Cocoa\n\n/// A bit mask containing the modifier keys for a hotkey.\nstruct Modifiers: OptionSet, Codable, Hashable {\n    let rawValue: Int\n\n    static let control = Modifiers(rawValue: 1 << 0)\n    static let option = Modifiers(rawValue: 1 << 1)\n    static let shift = Modifiers(rawValue: 1 << 2)\n    static let command = Modifiers(rawValue: 1 << 3)\n}\n\nextension Modifiers {\n    /// All modifiers in the order displayed by the system,\n    /// according to Apple's style guide.\n    static let canonicalOrder = [control, option, shift, command]\n\n    /// A symbolic string representation of the modifiers.\n    var symbolicValue: String {\n        var result = \"\"\n        if contains(.control) {\n            result.append(\"⌃\")\n        }\n        if contains(.option) {\n            result.append(\"⌥\")\n        }\n        if contains(.shift) {\n            result.append(\"⇧\")\n        }\n        if contains(.command) {\n            result.append(\"⌘\")\n        }\n        return result\n    }\n\n    /// A string representation of the modifiers that is\n    /// suitable for display in a label.\n    var labelValue: String {\n        var result = [String]()\n        if contains(.control) {\n            result.append(\"Control\")\n        }\n        if contains(.option) {\n            result.append(\"Option\")\n        }\n        if contains(.shift) {\n            result.append(\"Shift\")\n        }\n        if contains(.command) {\n            result.append(\"Command\")\n        }\n        return result.joined(separator: \" + \")\n    }\n\n    /// A combined string representation of the modifiers\n    /// that is suitable for display.\n    var combinedValue: String {\n        \"\\(labelValue) (\\(symbolicValue))\"\n    }\n\n    /// Cocoa flags.\n    var nsEventFlags: NSEvent.ModifierFlags {\n        var result: NSEvent.ModifierFlags = []\n        if contains(.control) {\n            result.insert(.control)\n        }\n        if contains(.option) {\n            result.insert(.option)\n        }\n        if contains(.shift) {\n            result.insert(.shift)\n        }\n        if contains(.command) {\n            result.insert(.command)\n        }\n        return result\n    }\n\n    /// CoreGraphics flags.\n    var cgEventFlags: CGEventFlags {\n        var result: CGEventFlags = []\n        if contains(.control) {\n            result.insert(.maskControl)\n        }\n        if contains(.option) {\n            result.insert(.maskAlternate)\n        }\n        if contains(.shift) {\n            result.insert(.maskShift)\n        }\n        if contains(.command) {\n            result.insert(.maskCommand)\n        }\n        return result\n    }\n\n    /// Raw Carbon flags.\n    var carbonFlags: Int {\n        var result = 0\n        if contains(.control) {\n            result |= controlKey\n        }\n        if contains(.option) {\n            result |= optionKey\n        }\n        if contains(.shift) {\n            result |= shiftKey\n        }\n        if contains(.command) {\n            result |= cmdKey\n        }\n        return result\n    }\n\n    init(nsEventFlags: NSEvent.ModifierFlags) {\n        self.init()\n        if nsEventFlags.contains(.control) {\n            insert(.control)\n        }\n        if nsEventFlags.contains(.option) {\n            insert(.option)\n        }\n        if nsEventFlags.contains(.shift) {\n            insert(.shift)\n        }\n        if nsEventFlags.contains(.command) {\n            insert(.command)\n        }\n    }\n\n    init(cgEventFlags: CGEventFlags) {\n        self.init()\n        if cgEventFlags.contains(.maskControl) {\n            insert(.control)\n        }\n        if cgEventFlags.contains(.maskAlternate) {\n            insert(.option)\n        }\n        if cgEventFlags.contains(.maskShift) {\n            insert(.shift)\n        }\n        if cgEventFlags.contains(.maskCommand) {\n            insert(.command)\n        }\n    }\n\n    init(carbonFlags: Int) {\n        self.init()\n        if carbonFlags & controlKey == controlKey {\n            insert(.control)\n        }\n        if carbonFlags & optionKey == optionKey {\n            insert(.option)\n        }\n        if carbonFlags & shiftKey == shiftKey {\n            insert(.shift)\n        }\n        if carbonFlags & cmdKey == cmdKey {\n            insert(.command)\n        }\n    }\n}\n"
  },
  {
    "path": "Ice/Ice.entitlements",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>com.apple.security.app-sandbox</key>\n\t<false/>\n\t<key>com.apple.security.files.user-selected.read-only</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "Ice/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>SUFeedURL</key>\n\t<string>https://jordanbaird.github.io/ice-releases/appcast.xml</string>\n\t<key>SUPublicEDKey</key>\n\t<string>3nfIGMOD8DALPE8vIdFo2tUOIVc2MVbzhc+2J9JLn+Q=</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "Ice/Main/AppDelegate.swift",
    "content": "//\n//  AppDelegate.swift\n//  Ice\n//\n\nimport SwiftUI\n\n@MainActor\nfinal class AppDelegate: NSObject, NSApplicationDelegate {\n    private weak var appState: AppState?\n\n    // MARK: NSApplicationDelegate Methods\n\n    func applicationWillFinishLaunching(_ notification: Notification) {\n        guard let appState else {\n            Logger.appDelegate.warning(\"Missing app state in applicationWillFinishLaunching\")\n            return\n        }\n\n        // Assign the delegate to the shared app state.\n        appState.assignAppDelegate(self)\n\n        // Allow the app to set the cursor in the background.\n        appState.setsCursorInBackground = true\n    }\n\n    func applicationDidFinishLaunching(_ notification: Notification) {\n        guard let appState else {\n            Logger.appDelegate.warning(\"Missing app state in applicationDidFinishLaunching\")\n            return\n        }\n\n        // Dismiss the windows.\n        appState.dismissSettingsWindow()\n        appState.dismissPermissionsWindow()\n\n        // Hide the main menu to make more space in the menu bar.\n        if let mainMenu = NSApp.mainMenu {\n            for item in mainMenu.items {\n                item.isHidden = true\n            }\n        }\n\n        // Perform setup after a small delay to ensure that the settings window\n        // has been assigned.\n        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {\n            guard !appState.isPreview else {\n                return\n            }\n            // If we have the required permissions, set up the shared app state.\n            // Otherwise, open the permissions window.\n            switch appState.permissionsManager.permissionsState {\n            case .hasAllPermissions, .hasRequiredPermissions:\n                appState.performSetup()\n            case .missingPermissions:\n                appState.activate(withPolicy: .regular)\n                appState.openPermissionsWindow()\n            }\n        }\n    }\n\n    func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {\n        // Deactivate and set the policy to accessory when all windows are closed.\n        appState?.deactivate(withPolicy: .accessory)\n        return false\n    }\n\n    func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {\n        return true\n    }\n\n    // MARK: Other Methods\n\n    /// Assigns the app state to the delegate.\n    func assignAppState(_ appState: AppState) {\n        guard self.appState == nil else {\n            Logger.appDelegate.warning(\"Multiple attempts made to assign app state\")\n            return\n        }\n        self.appState = appState\n    }\n\n    /// Opens the settings window and activates the app.\n    @objc func openSettingsWindow() {\n        guard let appState else {\n            Logger.appDelegate.error(\"Failed to open settings window\")\n            return\n        }\n        // Small delay makes this more reliable.\n        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {\n            appState.activate(withPolicy: .regular)\n            appState.openSettingsWindow()\n        }\n    }\n}\n\n// MARK: - Logger\nprivate extension Logger {\n    static let appDelegate = Logger(category: \"AppDelegate\")\n}\n"
  },
  {
    "path": "Ice/Main/AppState.swift",
    "content": "//\n//  AppState.swift\n//  Ice\n//\n\nimport Combine\nimport SwiftUI\n\n/// The model for app-wide state.\n@MainActor\nfinal class AppState: ObservableObject {\n    /// A Boolean value that indicates whether the active space is fullscreen.\n    @Published private(set) var isActiveSpaceFullscreen = Bridging.isSpaceFullscreen(Bridging.activeSpaceID)\n\n    /// Manager for the menu bar's appearance.\n    private(set) lazy var appearanceManager = MenuBarAppearanceManager(appState: self)\n\n    /// Manager for events received by the app.\n    private(set) lazy var eventManager = EventManager(appState: self)\n\n    /// Manager for menu bar items.\n    private(set) lazy var itemManager = MenuBarItemManager(appState: self)\n\n    /// Manager for the state of the menu bar.\n    private(set) lazy var menuBarManager = MenuBarManager(appState: self)\n\n    /// Manager for app permissions.\n    private(set) lazy var permissionsManager = PermissionsManager(appState: self)\n\n    /// Manager for the app's settings.\n    private(set) lazy var settingsManager = SettingsManager(appState: self)\n\n    /// Manager for app updates.\n    private(set) lazy var updatesManager = UpdatesManager(appState: self)\n\n    /// Manager for user notifications.\n    private(set) lazy var userNotificationManager = UserNotificationManager(appState: self)\n\n    /// Global cache for menu bar item images.\n    private(set) lazy var imageCache = MenuBarItemImageCache(appState: self)\n\n    /// Manager for menu bar item spacing.\n    let spacingManager = MenuBarItemSpacingManager()\n\n    /// Model for app-wide navigation.\n    let navigationState = AppNavigationState()\n\n    /// The app's hotkey registry.\n    nonisolated let hotkeyRegistry = HotkeyRegistry()\n\n    /// The app's delegate.\n    private(set) weak var appDelegate: AppDelegate?\n\n    /// The window that contains the settings interface.\n    private(set) weak var settingsWindow: NSWindow?\n\n    /// The window that contains the permissions interface.\n    private(set) weak var permissionsWindow: NSWindow?\n\n    /// A Boolean value that indicates whether the \"ShowOnHover\" feature is prevented.\n    private(set) var isShowOnHoverPrevented = false\n\n    /// Storage for internal observers.\n    private var cancellables = Set<AnyCancellable>()\n\n    /// A Boolean value that indicates whether the app is running as a SwiftUI preview.\n    let isPreview: Bool = {\n        #if DEBUG\n        let environment = ProcessInfo.processInfo.environment\n        let key = \"XCODE_RUNNING_FOR_PREVIEWS\"\n        return environment[key] != nil\n        #else\n        return false\n        #endif\n    }()\n\n    /// A Boolean value that indicates whether the application can set the cursor\n    /// in the background.\n    var setsCursorInBackground: Bool {\n        get { Bridging.getConnectionProperty(forKey: \"SetsCursorInBackground\") as? Bool ?? false }\n        set { Bridging.setConnectionProperty(newValue, forKey: \"SetsCursorInBackground\") }\n    }\n\n    /// Configures the internal observers for the app state.\n    private func configureCancellables() {\n        var c = Set<AnyCancellable>()\n\n        Publishers.Merge3(\n            NSWorkspace.shared.notificationCenter\n                .publisher(for: NSWorkspace.activeSpaceDidChangeNotification)\n                .mapToVoid(),\n            // Frontmost application change can indicate a space change from one display to\n            // another, which gets ignored by NSWorkspace.activeSpaceDidChangeNotification.\n            NSWorkspace.shared\n                .publisher(for: \\.frontmostApplication)\n                .mapToVoid(),\n            // Clicking into a fullscreen space from another space is also ignored.\n            UniversalEventMonitor\n                .publisher(for: .leftMouseDown)\n                .delay(for: 0.1, scheduler: DispatchQueue.main)\n                .mapToVoid()\n        )\n        .receive(on: DispatchQueue.main)\n        .sink { [weak self] _ in\n            guard let self else {\n                return\n            }\n            isActiveSpaceFullscreen = Bridging.isSpaceFullscreen(Bridging.activeSpaceID)\n        }\n        .store(in: &c)\n\n        NSWorkspace.shared.publisher(for: \\.frontmostApplication)\n            .receive(on: DispatchQueue.main)\n            .sink { [weak self] frontmostApplication in\n                guard let self else {\n                    return\n                }\n                navigationState.isAppFrontmost = frontmostApplication == .current\n            }\n            .store(in: &c)\n\n        if let settingsWindow {\n            settingsWindow.publisher(for: \\.isVisible)\n                .debounce(for: 0.05, scheduler: DispatchQueue.main)\n                .sink { [weak self] isVisible in\n                    guard let self else {\n                        return\n                    }\n                    navigationState.isSettingsPresented = isVisible\n                }\n                .store(in: &c)\n        } else {\n            Logger.appState.warning(\"No settings window!\")\n        }\n\n        Publishers.Merge(\n            navigationState.$isAppFrontmost,\n            navigationState.$isSettingsPresented\n        )\n        .debounce(for: 0.1, scheduler: DispatchQueue.main)\n        .sink { [weak self] shouldUpdate in\n            guard\n                let self,\n                shouldUpdate\n            else {\n                return\n            }\n            Task.detached {\n                if ScreenCapture.cachedCheckPermissions(reset: true) {\n                    await self.imageCache.updateCacheWithoutChecks(sections: MenuBarSection.Name.allCases)\n                }\n            }\n        }\n        .store(in: &c)\n\n        menuBarManager.objectWillChange\n            .sink { [weak self] in\n                self?.objectWillChange.send()\n            }\n            .store(in: &c)\n        permissionsManager.objectWillChange\n            .sink { [weak self] in\n                self?.objectWillChange.send()\n            }\n            .store(in: &c)\n        settingsManager.objectWillChange\n            .sink { [weak self] in\n                self?.objectWillChange.send()\n            }\n            .store(in: &c)\n        updatesManager.objectWillChange\n            .sink { [weak self] in\n                self?.objectWillChange.send()\n            }\n            .store(in: &c)\n\n        cancellables = c\n    }\n\n    /// Sets up the app state.\n    func performSetup() {\n        configureCancellables()\n        permissionsManager.stopAllChecks()\n        menuBarManager.performSetup()\n        appearanceManager.performSetup()\n        eventManager.performSetup()\n        settingsManager.performSetup()\n        itemManager.performSetup()\n        imageCache.performSetup()\n        updatesManager.performSetup()\n        userNotificationManager.performSetup()\n    }\n\n    /// Assigns the app delegate to the app state.\n    func assignAppDelegate(_ appDelegate: AppDelegate) {\n        guard self.appDelegate == nil else {\n            Logger.appState.warning(\"Multiple attempts made to assign app delegate\")\n            return\n        }\n        self.appDelegate = appDelegate\n    }\n\n    /// Assigns the settings window to the app state.\n    func assignSettingsWindow(_ window: NSWindow) {\n        guard window.identifier?.rawValue == Constants.settingsWindowID else {\n            Logger.appState.warning(\"Window \\(window.identifier?.rawValue ?? \"<NIL>\") is not the settings window!\")\n            return\n        }\n        settingsWindow = window\n        configureCancellables()\n    }\n\n    /// Assigns the permissions window to the app state.\n    func assignPermissionsWindow(_ window: NSWindow) {\n        guard window.identifier?.rawValue == Constants.permissionsWindowID else {\n            Logger.appState.warning(\"Window \\(window.identifier?.rawValue ?? \"<NIL>\") is not the permissions window!\")\n            return\n        }\n        permissionsWindow = window\n        configureCancellables()\n    }\n\n    /// Opens the settings window.\n    func openSettingsWindow() {\n        with(EnvironmentValues()) { environment in\n            environment.openWindow(id: Constants.settingsWindowID)\n        }\n    }\n\n    /// Dismisses the settings window.\n    func dismissSettingsWindow() {\n        with(EnvironmentValues()) { environment in\n            environment.dismissWindow(id: Constants.settingsWindowID)\n        }\n    }\n\n    /// Opens the permissions window.\n    func openPermissionsWindow() {\n        with(EnvironmentValues()) { environment in\n            environment.openWindow(id: Constants.permissionsWindowID)\n        }\n    }\n\n    /// Dismisses the permissions window.\n    func dismissPermissionsWindow() {\n        with(EnvironmentValues()) { environment in\n            environment.dismissWindow(id: Constants.permissionsWindowID)\n        }\n    }\n\n    /// Activates the app and sets its activation policy to the given value.\n    func activate(withPolicy policy: NSApplication.ActivationPolicy) {\n        // Store whether the app has previously activated inside an internal\n        // context to keep it isolated.\n        enum Context {\n            static let hasActivated = ObjectStorage<Bool>()\n        }\n\n        func activate() {\n            if let frontApp = NSWorkspace.shared.frontmostApplication {\n                NSRunningApplication.current.activate(from: frontApp)\n            } else {\n                NSApp.activate()\n            }\n            NSApp.setActivationPolicy(policy)\n        }\n\n        if Context.hasActivated.value(for: self) == true {\n            activate()\n        } else {\n            Context.hasActivated.set(true, for: self)\n            Logger.appState.debug(\"First time activating app, so going through Dock\")\n            // Hack to make sure the app properly activates for the first time.\n            NSRunningApplication.runningApplications(withBundleIdentifier: \"com.apple.dock\").first?.activate()\n            DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {\n                activate()\n            }\n        }\n    }\n\n    /// Deactivates the app and sets its activation policy to the given value.\n    func deactivate(withPolicy policy: NSApplication.ActivationPolicy) {\n        if let nextApp = NSWorkspace.shared.runningApplications.first(where: { $0 != .current }) {\n            NSApp.yieldActivation(to: nextApp)\n        } else {\n            NSApp.deactivate()\n        }\n        NSApp.setActivationPolicy(policy)\n    }\n\n    /// Prevents the \"ShowOnHover\" feature.\n    func preventShowOnHover() {\n        isShowOnHoverPrevented = true\n    }\n\n    /// Allows the \"ShowOnHover\" feature.\n    func allowShowOnHover() {\n        isShowOnHoverPrevented = false\n    }\n}\n\n// MARK: AppState: BindingExposable\nextension AppState: BindingExposable { }\n\n// MARK: - Logger\nprivate extension Logger {\n    /// The logger to use for the app state.\n    static let appState = Logger(category: \"AppState\")\n}\n"
  },
  {
    "path": "Ice/Main/IceApp.swift",
    "content": "//\n//  IceApp.swift\n//  Ice\n//\n\nimport SwiftUI\n\n@main\nstruct IceApp: App {\n    @NSApplicationDelegateAdaptor var appDelegate: AppDelegate\n    @ObservedObject var appState = AppState()\n\n    init() {\n        NSSplitViewItem.swizzle()\n        MigrationManager.migrateAll(appState: appState)\n        appDelegate.assignAppState(appState)\n    }\n\n    var body: some Scene {\n        SettingsWindow(appState: appState)\n        PermissionsWindow(appState: appState)\n    }\n}\n"
  },
  {
    "path": "Ice/Main/Navigation/AppNavigationState.swift",
    "content": "//\n//  AppNavigationState.swift\n//  Ice\n//\n\nimport Combine\n\n/// The model for app-wide navigation.\n@MainActor\nfinal class AppNavigationState: ObservableObject {\n    @Published var isAppFrontmost = false\n    @Published var isSettingsPresented = false\n    @Published var isIceBarPresented = false\n    @Published var isSearchPresented = false\n    @Published var settingsNavigationIdentifier: SettingsNavigationIdentifier = .general\n}\n"
  },
  {
    "path": "Ice/Main/Navigation/NavigationIdentifiers/NavigationIdentifier.swift",
    "content": "//\n//  NavigationIdentifier.swift\n//  Ice\n//\n\nimport SwiftUI\n\n/// A type that represents an identifier used for navigation in a user interface.\nprotocol NavigationIdentifier: CaseIterable, Hashable, Identifiable, RawRepresentable {\n    /// A localized description of the identifier that can be presented to the user.\n    var localized: LocalizedStringKey { get }\n}\n\nextension NavigationIdentifier where ID == Int {\n    var id: Int { hashValue }\n}\n\nextension NavigationIdentifier where RawValue == String {\n    var localized: LocalizedStringKey { LocalizedStringKey(rawValue) }\n}\n"
  },
  {
    "path": "Ice/Main/Navigation/NavigationIdentifiers/SettingsNavigationIdentifier.swift",
    "content": "//\n//  SettingsNavigationIdentifier.swift\n//  Ice\n//\n\n/// An identifier used for navigation in the settings interface.\nenum SettingsNavigationIdentifier: String, NavigationIdentifier {\n    case general = \"General\"\n    case menuBarLayout = \"Menu Bar Layout\"\n    case menuBarAppearance = \"Menu Bar Appearance\"\n    case hotkeys = \"Hotkeys\"\n    case advanced = \"Advanced\"\n    case about = \"About\"\n}\n"
  },
  {
    "path": "Ice/MenuBar/Appearance/Configurations/MenuBarAppearanceConfigurationV1.swift",
    "content": "//\n//  MenuBarAppearanceConfigurationV1.swift\n//  Ice\n//\n\nimport CoreGraphics\nimport Foundation\n\n/// Configuration for the menu bar's appearance.\nstruct MenuBarAppearanceConfigurationV1: Hashable {\n    var hasShadow: Bool\n    var hasBorder: Bool\n    var isInset: Bool\n    var borderColor: CGColor\n    var borderWidth: Double\n    var shapeKind: MenuBarShapeKind\n    var fullShapeInfo: MenuBarFullShapeInfo\n    var splitShapeInfo: MenuBarSplitShapeInfo\n    var tintKind: MenuBarTintKind\n    var tintColor: CGColor\n    var tintGradient: CustomGradient\n\n    var hasRoundedShape: Bool {\n        switch shapeKind {\n        case .none: false\n        case .full: fullShapeInfo.hasRoundedShape\n        case .split: splitShapeInfo.hasRoundedShape\n        }\n    }\n\n    /// Creates a configuration by migrating from the deprecated appearance-related\n    /// keys stored in `UserDefaults`, storing the new configuration and deleting\n    /// the deprecated keys.\n    static func migrate(encoder: JSONEncoder, decoder: JSONDecoder) throws -> Self {\n        // Try to load an already migrated configuration first. Otherwise, load each\n        // value from the deprecated keys.\n        if let data = Defaults.data(forKey: .menuBarAppearanceConfiguration) {\n            return try decoder.decode(Self.self, from: data)\n        } else {\n            var configuration = Self.defaultConfiguration\n\n            Defaults.ifPresent(key: .menuBarHasShadow, assign: &configuration.hasShadow)\n            Defaults.ifPresent(key: .menuBarHasBorder, assign: &configuration.hasBorder)\n            Defaults.ifPresent(key: .menuBarBorderWidth, assign: &configuration.borderWidth)\n            Defaults.ifPresent(key: .menuBarTintKind) { rawValue in\n                if let tintKind = MenuBarTintKind(rawValue: rawValue) {\n                    configuration.tintKind = tintKind\n                }\n            }\n\n            if let borderColorData = Defaults.data(forKey: .menuBarBorderColor) {\n                configuration.borderColor = try decoder.decode(CodableColor.self, from: borderColorData).cgColor\n            }\n            if let tintColorData = Defaults.data(forKey: .menuBarTintColor) {\n                configuration.tintColor = try decoder.decode(CodableColor.self, from: tintColorData).cgColor\n            }\n            if let tintGradientData = Defaults.data(forKey: .menuBarTintGradient) {\n                configuration.tintGradient = try decoder.decode(CustomGradient.self, from: tintGradientData)\n            }\n            if let shapeKindData = Defaults.data(forKey: .menuBarShapeKind) {\n                configuration.shapeKind = try decoder.decode(MenuBarShapeKind.self, from: shapeKindData)\n            }\n            if let fullShapeData = Defaults.data(forKey: .menuBarFullShapeInfo) {\n                configuration.fullShapeInfo = try decoder.decode(MenuBarFullShapeInfo.self, from: fullShapeData)\n            }\n            if let splitShapeData = Defaults.data(forKey: .menuBarSplitShapeInfo) {\n                configuration.splitShapeInfo = try decoder.decode(MenuBarSplitShapeInfo.self, from: splitShapeData)\n            }\n\n            // Store the configuration to complete the migration.\n            let configurationData = try encoder.encode(configuration)\n            Defaults.set(configurationData, forKey: .menuBarAppearanceConfiguration)\n\n            // Remove the deprecated keys.\n            let keys: [Defaults.Key] = [\n                .menuBarHasShadow,\n                .menuBarHasBorder,\n                .menuBarBorderWidth,\n                .menuBarTintKind,\n                .menuBarBorderColor,\n                .menuBarTintColor,\n                .menuBarTintGradient,\n                .menuBarShapeKind,\n                .menuBarFullShapeInfo,\n                .menuBarSplitShapeInfo,\n            ]\n            for key in keys {\n                Defaults.removeObject(forKey: key)\n            }\n\n            return configuration\n        }\n    }\n}\n\n// MARK: Default Configuration\nextension MenuBarAppearanceConfigurationV1 {\n    static let defaultConfiguration = MenuBarAppearanceConfigurationV1(\n        hasShadow: false,\n        hasBorder: false,\n        isInset: true,\n        borderColor: .black,\n        borderWidth: 1,\n        shapeKind: .none,\n        fullShapeInfo: .default,\n        splitShapeInfo: .default,\n        tintKind: .none,\n        tintColor: .black,\n        tintGradient: .defaultMenuBarTint\n    )\n}\n\n// MARK: MenuBarAppearanceConfigurationV1: Codable\nextension MenuBarAppearanceConfigurationV1: Codable {\n    private enum CodingKeys: CodingKey {\n        case hasShadow\n        case hasBorder\n        case isInset\n        case borderColor\n        case borderWidth\n        case shapeKind\n        case fullShapeInfo\n        case splitShapeInfo\n        case tintKind\n        case tintColor\n        case tintGradient\n    }\n\n    init(from decoder: Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        try self.init(\n            hasShadow: container.decodeIfPresent(Bool.self, forKey: .hasShadow) ?? Self.defaultConfiguration.hasShadow,\n            hasBorder: container.decodeIfPresent(Bool.self, forKey: .hasBorder) ?? Self.defaultConfiguration.hasBorder,\n            isInset: container.decodeIfPresent(Bool.self, forKey: .isInset) ?? Self.defaultConfiguration.isInset,\n            borderColor: container.decodeIfPresent(CodableColor.self, forKey: .borderColor)?.cgColor ?? Self.defaultConfiguration.borderColor,\n            borderWidth: container.decodeIfPresent(Double.self, forKey: .borderWidth) ?? Self.defaultConfiguration.borderWidth,\n            shapeKind: container.decodeIfPresent(MenuBarShapeKind.self, forKey: .shapeKind) ?? Self.defaultConfiguration.shapeKind,\n            fullShapeInfo: container.decodeIfPresent(MenuBarFullShapeInfo.self, forKey: .fullShapeInfo) ?? Self.defaultConfiguration.fullShapeInfo,\n            splitShapeInfo: container.decodeIfPresent(MenuBarSplitShapeInfo.self, forKey: .splitShapeInfo) ?? Self.defaultConfiguration.splitShapeInfo,\n            tintKind: container.decodeIfPresent(MenuBarTintKind.self, forKey: .tintKind) ?? Self.defaultConfiguration.tintKind,\n            tintColor: container.decodeIfPresent(CodableColor.self, forKey: .tintColor)?.cgColor ?? Self.defaultConfiguration.tintColor,\n            tintGradient: container.decodeIfPresent(CustomGradient.self, forKey: .tintGradient) ?? Self.defaultConfiguration.tintGradient\n        )\n    }\n\n    func encode(to encoder: Encoder) throws {\n        var container = encoder.container(keyedBy: CodingKeys.self)\n        try container.encode(hasShadow, forKey: .hasShadow)\n        try container.encode(hasBorder, forKey: .hasBorder)\n        try container.encode(isInset, forKey: .isInset)\n        try container.encode(CodableColor(cgColor: borderColor), forKey: .borderColor)\n        try container.encode(borderWidth, forKey: .borderWidth)\n        try container.encode(shapeKind, forKey: .shapeKind)\n        try container.encode(fullShapeInfo, forKey: .fullShapeInfo)\n        try container.encode(splitShapeInfo, forKey: .splitShapeInfo)\n        try container.encode(tintKind, forKey: .tintKind)\n        try container.encode(CodableColor(cgColor: tintColor), forKey: .tintColor)\n        try container.encode(tintGradient, forKey: .tintGradient)\n    }\n}\n"
  },
  {
    "path": "Ice/MenuBar/Appearance/Configurations/MenuBarAppearanceConfigurationV2.swift",
    "content": "//\n//  MenuBarAppearanceConfigurationV2.swift\n//  Ice\n//\n\nimport CoreGraphics\nimport Foundation\n\nstruct MenuBarAppearanceConfigurationV2: Hashable {\n    var lightModeConfiguration: MenuBarAppearancePartialConfiguration\n    var darkModeConfiguration: MenuBarAppearancePartialConfiguration\n    var staticConfiguration: MenuBarAppearancePartialConfiguration\n    var shapeKind: MenuBarShapeKind\n    var fullShapeInfo: MenuBarFullShapeInfo\n    var splitShapeInfo: MenuBarSplitShapeInfo\n    var isInset: Bool\n    var isDynamic: Bool\n\n    var hasRoundedShape: Bool {\n        switch shapeKind {\n        case .none: false\n        case .full: fullShapeInfo.hasRoundedShape\n        case .split: splitShapeInfo.hasRoundedShape\n        }\n    }\n\n    var current: MenuBarAppearancePartialConfiguration {\n        if isDynamic {\n            switch SystemAppearance.current {\n            case .light: lightModeConfiguration\n            case .dark: darkModeConfiguration\n            }\n        } else {\n            staticConfiguration\n        }\n    }\n}\n\n// MARK: Default Configuration\nextension MenuBarAppearanceConfigurationV2 {\n    static let defaultConfiguration = MenuBarAppearanceConfigurationV2(\n        lightModeConfiguration: .defaultConfiguration,\n        darkModeConfiguration: .defaultConfiguration,\n        staticConfiguration: .defaultConfiguration,\n        shapeKind: .none,\n        fullShapeInfo: .default,\n        splitShapeInfo: .default,\n        isInset: true,\n        isDynamic: false\n    )\n}\n\nextension MenuBarAppearanceConfigurationV2: Codable {\n    private enum CodingKeys: CodingKey {\n        case lightModeConfiguration\n        case darkModeConfiguration\n        case staticConfiguration\n        case shapeKind\n        case fullShapeInfo\n        case splitShapeInfo\n        case isInset\n        case isDynamic\n    }\n\n    init(from decoder: any Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        try self.init(\n            lightModeConfiguration: container.decodeIfPresent(MenuBarAppearancePartialConfiguration.self, forKey: .lightModeConfiguration) ?? Self.defaultConfiguration.lightModeConfiguration,\n            darkModeConfiguration: container.decodeIfPresent(MenuBarAppearancePartialConfiguration.self, forKey: .darkModeConfiguration) ?? Self.defaultConfiguration.darkModeConfiguration,\n            staticConfiguration: container.decodeIfPresent(MenuBarAppearancePartialConfiguration.self, forKey: .staticConfiguration) ?? Self.defaultConfiguration.staticConfiguration,\n            shapeKind: container.decodeIfPresent(MenuBarShapeKind.self, forKey: .shapeKind) ?? Self.defaultConfiguration.shapeKind,\n            fullShapeInfo: container.decodeIfPresent(MenuBarFullShapeInfo.self, forKey: .fullShapeInfo) ?? Self.defaultConfiguration.fullShapeInfo,\n            splitShapeInfo: container.decodeIfPresent(MenuBarSplitShapeInfo.self, forKey: .splitShapeInfo) ?? Self.defaultConfiguration.splitShapeInfo,\n            isInset: container.decodeIfPresent(Bool.self, forKey: .isInset) ?? Self.defaultConfiguration.isInset,\n            isDynamic: container.decodeIfPresent(Bool.self, forKey: .isDynamic) ?? Self.defaultConfiguration.isDynamic\n        )\n    }\n\n    func encode(to encoder: any Encoder) throws {\n        var container = encoder.container(keyedBy: CodingKeys.self)\n        try container.encode(lightModeConfiguration, forKey: .lightModeConfiguration)\n        try container.encode(darkModeConfiguration, forKey: .darkModeConfiguration)\n        try container.encode(staticConfiguration, forKey: .staticConfiguration)\n        try container.encode(shapeKind, forKey: .shapeKind)\n        try container.encode(fullShapeInfo, forKey: .fullShapeInfo)\n        try container.encode(splitShapeInfo, forKey: .splitShapeInfo)\n        try container.encode(isInset, forKey: .isInset)\n        try container.encode(isDynamic, forKey: .isDynamic)\n    }\n}\n\n// MARK: - MenuBarAppearancePartialConfiguration\n\nstruct MenuBarAppearancePartialConfiguration: Hashable {\n    var hasShadow: Bool\n    var hasBorder: Bool\n    var borderColor: CGColor\n    var borderWidth: Double\n    var tintKind: MenuBarTintKind\n    var tintColor: CGColor\n    var tintGradient: CustomGradient\n}\n\n// MARK: Default Partial Configuration\nextension MenuBarAppearancePartialConfiguration {\n    static let defaultConfiguration = MenuBarAppearancePartialConfiguration(\n        hasShadow: false,\n        hasBorder: false,\n        borderColor: .black,\n        borderWidth: 1,\n        tintKind: .none,\n        tintColor: .black,\n        tintGradient: .defaultMenuBarTint\n    )\n}\n\n// MARK: MenuBarAppearancePartialConfiguration: Codable\nextension MenuBarAppearancePartialConfiguration: Codable {\n    private enum CodingKeys: CodingKey {\n        case hasShadow\n        case hasBorder\n        case borderColor\n        case borderWidth\n        case shapeKind\n        case fullShapeInfo\n        case splitShapeInfo\n        case tintKind\n        case tintColor\n        case tintGradient\n    }\n\n    init(from decoder: Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        try self.init(\n            hasShadow: container.decodeIfPresent(Bool.self, forKey: .hasShadow) ?? Self.defaultConfiguration.hasShadow,\n            hasBorder: container.decodeIfPresent(Bool.self, forKey: .hasBorder) ?? Self.defaultConfiguration.hasBorder,\n            borderColor: container.decodeIfPresent(CodableColor.self, forKey: .borderColor)?.cgColor ?? Self.defaultConfiguration.borderColor,\n            borderWidth: container.decodeIfPresent(Double.self, forKey: .borderWidth) ?? Self.defaultConfiguration.borderWidth,\n            tintKind: container.decodeIfPresent(MenuBarTintKind.self, forKey: .tintKind) ?? Self.defaultConfiguration.tintKind,\n            tintColor: container.decodeIfPresent(CodableColor.self, forKey: .tintColor)?.cgColor ?? Self.defaultConfiguration.tintColor,\n            tintGradient: container.decodeIfPresent(CustomGradient.self, forKey: .tintGradient) ?? Self.defaultConfiguration.tintGradient\n        )\n    }\n\n    func encode(to encoder: Encoder) throws {\n        var container = encoder.container(keyedBy: CodingKeys.self)\n        try container.encode(hasShadow, forKey: .hasShadow)\n        try container.encode(hasBorder, forKey: .hasBorder)\n        try container.encode(CodableColor(cgColor: borderColor), forKey: .borderColor)\n        try container.encode(borderWidth, forKey: .borderWidth)\n        try container.encode(tintKind, forKey: .tintKind)\n        try container.encode(CodableColor(cgColor: tintColor), forKey: .tintColor)\n        try container.encode(tintGradient, forKey: .tintGradient)\n    }\n}\n"
  },
  {
    "path": "Ice/MenuBar/Appearance/MenuBarAppearanceEditor/MenuBarAppearanceEditor.swift",
    "content": "//\n//  MenuBarAppearanceEditor.swift\n//  Ice\n//\n\nimport SwiftUI\n\nstruct MenuBarAppearanceEditor: View {\n    enum Location {\n        case settings\n        case popover(closePopover: () -> Void)\n    }\n\n    @EnvironmentObject var appState: AppState\n    @EnvironmentObject var appearanceManager: MenuBarAppearanceManager\n\n    let location: Location\n\n    private var mainFormPadding: EdgeInsets {\n        with(EdgeInsets(all: 20)) { insets in\n            switch location {\n            case .settings: break\n            case .popover: insets.top = 0\n            }\n        }\n    }\n\n    var body: some View {\n        VStack(alignment: .leading, spacing: 0) {\n            stackHeader\n            stackBody\n        }\n    }\n\n    @ViewBuilder\n    private var stackHeader: some View {\n        if case .popover(let closePopover) = location {\n            ZStack {\n                Text(\"Menu Bar Appearance\")\n                    .font(.title2)\n                    .frame(maxWidth: .infinity, alignment: .center)\n                Button(\"Done\", action: closePopover)\n                    .controlSize(.large)\n                    .frame(maxWidth: .infinity, alignment: .trailing)\n            }\n            .padding(20)\n        }\n    }\n\n    @ViewBuilder\n    private var stackBody: some View {\n        if appState.menuBarManager.isMenuBarHiddenBySystemUserDefaults {\n            cannotEdit\n        } else {\n            mainForm\n        }\n    }\n\n    @ViewBuilder\n    private var mainForm: some View {\n        IceForm(padding: mainFormPadding) {\n            IceSection {\n                isDynamicToggle\n            }\n            if appearanceManager.configuration.isDynamic {\n                LabeledPartialEditor(appearance: .light)\n                LabeledPartialEditor(appearance: .dark)\n            } else {\n                StaticPartialEditor()\n            }\n            IceSection(\"Menu Bar Shape\") {\n                shapePicker\n                isInset\n            }\n            if case .settings = location {\n                IceGroupBox {\n                    AnnotationView(\n                        alignment: .center,\n                        font: .callout.bold()\n                    ) {\n                        Label {\n                            Text(\"Tip: you can also edit these settings by right-clicking in an empty area of the menu bar\")\n                        } icon: {\n                            Image(systemName: \"lightbulb\")\n                        }\n                    }\n                }\n            }\n            if\n                !appState.menuBarManager.isMenuBarHiddenBySystemUserDefaults,\n                appearanceManager.configuration != .defaultConfiguration\n            {\n                Button(\"Reset\") {\n                    appearanceManager.configuration = .defaultConfiguration\n                }\n                .controlSize(.large)\n                .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomLeading)\n            }\n        }\n    }\n\n    @ViewBuilder\n    private var isDynamicToggle: some View {\n        Toggle(\"Use dynamic appearance\", isOn: appearanceManager.bindings.configuration.isDynamic)\n            .annotation(\"Apply different settings based on the current system appearance\")\n    }\n\n    @ViewBuilder\n    private var cannotEdit: some View {\n        Text(\"Ice cannot edit the appearance of automatically hidden menu bars\")\n            .font(.title3)\n            .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)\n    }\n\n    @ViewBuilder\n    private var shapePicker: some View {\n        MenuBarShapePicker()\n            .fixedSize(horizontal: false, vertical: true)\n    }\n\n    @ViewBuilder\n    private var isInset: some View {\n        if appearanceManager.configuration.shapeKind != .none {\n            Toggle(\n                \"Use inset shape on screens with notch\",\n                isOn: appearanceManager.bindings.configuration.isInset\n            )\n        }\n    }\n}\n\nprivate struct UnlabeledPartialEditor: View {\n    @Binding var configuration: MenuBarAppearancePartialConfiguration\n\n    var body: some View {\n        IceSection {\n            tintPicker\n            shadowToggle\n        }\n        IceSection {\n            borderToggle\n            borderColor\n            borderWidth\n        }\n    }\n\n    @ViewBuilder\n    private var tintPicker: some View {\n        IceLabeledContent(\"Tint\") {\n            HStack {\n                IcePicker(\"Tint\", selection: $configuration.tintKind) {\n                    ForEach(MenuBarTintKind.allCases) { tintKind in\n                        Text(tintKind.localized).tag(tintKind)\n                    }\n                }\n                .labelsHidden()\n\n                switch configuration.tintKind {\n                case .none:\n                    EmptyView()\n                case .solid:\n                    CustomColorPicker(\n                        selection: $configuration.tintColor,\n                        supportsOpacity: false,\n                        mode: .crayon\n                    )\n                case .gradient:\n                    CustomGradientPicker(\n                        gradient: $configuration.tintGradient,\n                        supportsOpacity: false,\n                        allowsEmptySelections: false,\n                        mode: .crayon\n                    )\n                }\n            }\n            .frame(height: 24)\n        }\n    }\n\n    @ViewBuilder\n    private var shadowToggle: some View {\n        Toggle(\"Shadow\", isOn: $configuration.hasShadow)\n    }\n\n    @ViewBuilder\n    private var borderToggle: some View {\n        Toggle(\"Border\", isOn: $configuration.hasBorder)\n    }\n\n    @ViewBuilder\n    private var borderColor: some View {\n        if configuration.hasBorder {\n            IceLabeledContent(\"Border Color\") {\n                CustomColorPicker(\n                    selection: $configuration.borderColor,\n                    supportsOpacity: true,\n                    mode: .crayon\n                )\n            }\n        }\n    }\n\n    @ViewBuilder\n    private var borderWidth: some View {\n        if configuration.hasBorder {\n            IcePicker(\n                \"Border Width\",\n                selection: $configuration.borderWidth\n            ) {\n                Text(\"1\").tag(1.0)\n                Text(\"2\").tag(2.0)\n                Text(\"3\").tag(3.0)\n            }\n        }\n    }\n}\n\nprivate struct LabeledPartialEditor: View {\n    @EnvironmentObject var appearanceManager: MenuBarAppearanceManager\n    @State private var currentAppearance = SystemAppearance.current\n    @State private var textFrame = CGRect.zero\n\n    let appearance: SystemAppearance\n\n    var body: some View {\n        IceSection(options: .plain) {\n            labelStack\n        } content: {\n            partialEditor\n        }\n        .onReceive(NSApp.publisher(for: \\.effectiveAppearance)) { _ in\n            currentAppearance = .current\n        }\n    }\n\n    @ViewBuilder\n    private var labelStack: some View {\n        HStack {\n            Text(appearance.titleKey)\n                .font(.headline)\n                .onFrameChange(update: $textFrame)\n\n            if currentAppearance != appearance {\n                previewButton\n            }\n        }\n        .frame(height: textFrame.height)\n    }\n\n    @ViewBuilder\n    private var previewButton: some View {\n        switch appearance {\n        case .light:\n            PreviewButton(configuration: appearanceManager.configuration.lightModeConfiguration)\n        case .dark:\n            PreviewButton(configuration: appearanceManager.configuration.darkModeConfiguration)\n        }\n    }\n\n    @ViewBuilder\n    private var partialEditor: some View {\n        switch appearance {\n        case .light:\n            UnlabeledPartialEditor(configuration: appearanceManager.bindings.configuration.lightModeConfiguration)\n        case .dark:\n            UnlabeledPartialEditor(configuration: appearanceManager.bindings.configuration.darkModeConfiguration)\n        }\n    }\n}\n\nprivate struct StaticPartialEditor: View {\n    @EnvironmentObject var appearanceManager: MenuBarAppearanceManager\n\n    var body: some View {\n        UnlabeledPartialEditor(configuration: appearanceManager.bindings.configuration.staticConfiguration)\n    }\n}\n\nprivate struct PreviewButton: View {\n    private struct DummyButton: NSViewRepresentable {\n        @Binding var isPressed: Bool\n\n        func makeNSView(context: Context) -> NSButton {\n            let button = NSButton()\n            button.title = \"\"\n            button.bezelStyle = .accessoryBarAction\n            return button\n        }\n\n        func updateNSView(_ nsView: NSButton, context: Context) {\n            nsView.isHighlighted = isPressed\n        }\n    }\n\n    @EnvironmentObject var appearanceManager: MenuBarAppearanceManager\n\n    @State private var frame = CGRect.zero\n    @State private var isPressed = false\n\n    let configuration: MenuBarAppearancePartialConfiguration\n\n    var body: some View {\n        ZStack {\n            DummyButton(isPressed: $isPressed)\n                .allowsHitTesting(false)\n            Text(\"Hold to Preview\")\n                .baselineOffset(1.5)\n                .padding(.horizontal, 10)\n                .contentShape(Rectangle())\n        }\n        .fixedSize()\n        .simultaneousGesture(\n            DragGesture(minimumDistance: 0)\n                .onChanged { value in\n                    isPressed = frame.contains(value.location)\n                }\n                .onEnded { _ in\n                    isPressed = false\n                }\n        )\n        .onChange(of: isPressed) { _, newValue in\n            appearanceManager.previewConfiguration = newValue ? configuration : nil\n        }\n        .onFrameChange(update: $frame)\n    }\n}\n"
  },
  {
    "path": "Ice/MenuBar/Appearance/MenuBarAppearanceEditor/MenuBarAppearanceEditorPanel.swift",
    "content": "//\n//  MenuBarAppearanceEditorPanel.swift\n//  Ice\n//\n\nimport Combine\nimport SwiftUI\n\n// MARK: - MenuBarAppearanceEditorPanel\n\n/// A panel that manages the appearance editor popover.\nfinal class MenuBarAppearanceEditorPanel: NSPanel {\n    /// The shared app state.\n    private weak var appState: AppState?\n\n    /// Storage for internal observers.\n    private var cancellables = Set<AnyCancellable>()\n\n    init(appState: AppState) {\n        super.init(\n            contentRect: CGRect(x: 0, y: 0, width: 1, height: 1),\n            styleMask: [.borderless, .nonactivatingPanel],\n            backing: .buffered,\n            defer: false\n        )\n        self.appState = appState\n        self.isFloatingPanel = true\n        self.backgroundColor = .clear\n        configureCancellables()\n    }\n\n    private func configureCancellables() {\n        var c = Set<AnyCancellable>()\n\n        NSWorkspace.shared.notificationCenter\n            .publisher(for: NSWorkspace.activeSpaceDidChangeNotification)\n            .sink { [weak self] _ in\n                self?.orderOut(self)\n                NSColorPanel.shared.close()\n                NSColorPanel.shared.hidesOnDeactivate = true\n            }\n            .store(in: &c)\n\n        cancellables = c\n    }\n\n    /// Shows the appearance editor popover.\n    func showAppearanceEditorPopover() {\n        guard\n            let appState,\n            let contentView,\n            let screen = NSScreen.screens.first(where: { $0.frame.contains(NSEvent.mouseLocation) }),\n            let menuBarHeight = NSApp.mainMenu?.menuBarHeight\n        else {\n            return\n        }\n        setFrameOrigin(CGPoint(x: screen.frame.midX - frame.width / 2, y: screen.frame.maxY - menuBarHeight))\n        let popover = MenuBarAppearanceEditorPopover(appState: appState)\n        popover.delegate = self\n        popover.show(relativeTo: .zero, of: contentView, preferredEdge: .minY)\n        popover.contentViewController?.view.window?.makeKey()\n        NSColorPanel.shared.hidesOnDeactivate = false\n    }\n}\n\n// MARK: MenuBarAppearanceEditorPanel: NSPopoverDelegate\nextension MenuBarAppearanceEditorPanel: NSPopoverDelegate {\n    func popoverDidClose(_ notification: Notification) {\n        if let popover = notification.object as? MenuBarAppearanceEditorPopover {\n            popover.mouseDownMonitor.stop()\n            orderOut(popover)\n            NSColorPanel.shared.close()\n            NSColorPanel.shared.hidesOnDeactivate = true\n        }\n    }\n}\n\n// MARK: - MenuBarAppearanceEditorPopover\n\n/// A popover that displays the menu bar appearance editor\n/// at a centered location under the menu bar.\nprivate final class MenuBarAppearanceEditorPopover: NSPopover {\n    private weak var appState: AppState?\n\n    private(set) lazy var mouseDownMonitor = GlobalEventMonitor(mask: .leftMouseDown) { [weak self] _ in\n        self?.performClose(self)\n    }\n\n    @ViewBuilder\n    private var contentView: some View {\n        if let appState {\n            MenuBarAppearanceEditor(\n                location: .popover(closePopover: { [weak self] in\n                    self?.performClose(self)\n                })\n            )\n            .environmentObject(appState)\n            .environmentObject(appState.appearanceManager)\n        }\n    }\n\n    init(appState: AppState) {\n        super.init()\n        self.appState = appState\n        self.contentViewController = NSHostingController(rootView: contentView)\n        self.contentSize = CGSize(width: 550, height: 600)\n        self.behavior = .applicationDefined\n        self.mouseDownMonitor.start()\n    }\n\n    @available(*, unavailable)\n    required init?(coder: NSCoder) {\n        fatalError(\"init(coder:) has not been implemented\")\n    }\n}\n"
  },
  {
    "path": "Ice/MenuBar/Appearance/MenuBarAppearanceEditor/MenuBarShapePicker.swift",
    "content": "//\n//  MenuBarShapePicker.swift\n//  Ice\n//\n\nimport SwiftUI\n\nstruct MenuBarShapePicker: View {\n    @EnvironmentObject var appearanceManager: MenuBarAppearanceManager\n    @Environment(\\.colorScheme) private var colorScheme\n\n    var body: some View {\n        shapeKindPicker\n        exampleView\n    }\n\n    @ViewBuilder\n    private var shapeKindPicker: some View {\n        IcePicker(\"Shape Kind\", selection: appearanceManager.bindings.configuration.shapeKind) {\n            ForEach(MenuBarShapeKind.allCases, id: \\.self) { shape in\n                switch shape {\n                case .none:\n                    Text(\"None\").tag(shape)\n                case .full:\n                    Text(\"Full\").tag(shape)\n                case .split:\n                    Text(\"Split\").tag(shape)\n                }\n            }\n        }\n    }\n\n    @ViewBuilder\n    private var exampleView: some View {\n        switch appearanceManager.configuration.shapeKind {\n        case .none:\n            Text(\"No shape kind selected\")\n                .foregroundStyle(.secondary)\n                .frame(maxWidth: .infinity, alignment: .center)\n        case .full:\n            MenuBarFullShapeExampleView(info: appearanceManager.bindings.configuration.fullShapeInfo)\n                .equatable()\n                .foregroundStyle(colorScheme == .dark ? .primary : .secondary)\n        case .split:\n            MenuBarSplitShapeExampleView(info: appearanceManager.bindings.configuration.splitShapeInfo)\n                .equatable()\n                .foregroundStyle(colorScheme == .dark ? .primary : .secondary)\n        }\n    }\n}\n\nprivate struct MenuBarFullShapeExampleView: View, Equatable {\n    @Binding var info: MenuBarFullShapeInfo\n\n    var body: some View {\n        VStack {\n            pickerStack\n            exampleStack\n        }\n    }\n\n    @ViewBuilder\n    private var pickerStack: some View {\n        HStack(spacing: 0) {\n            leadingEndCapPicker\n            Spacer()\n            trailingEndCapPicker\n        }\n        .labelsHidden()\n        .pickerStyle(.segmented)\n    }\n\n    @ViewBuilder\n    private var exampleStack: some View {\n        HStack(spacing: 0) {\n            leadingEndCapExample\n            Rectangle()\n            trailingEndCapExample\n        }\n        .frame(height: 24)\n    }\n\n    @ViewBuilder\n    private func endCapPickerContentView(endCap: MenuBarEndCap, edge: HorizontalEdge) -> some View {\n        switch endCap {\n        case .square:\n            Image(size: CGSize(width: 12, height: 12)) { context in\n                context.fill(Path(context.clipBoundingRect), with: .foreground)\n            }\n            .resizable()\n            .help(\"Square Cap\")\n            .tag(endCap)\n        case .round:\n            Image(size: CGSize(width: 12, height: 12)) { context in\n                let remainder = context.clipBoundingRect\n                    .divided(atDistance: context.clipBoundingRect.width / 2, from: cgRectEdge(for: edge))\n                    .remainder\n                let path1 = Path(remainder)\n                let path2 = Path(ellipseIn: context.clipBoundingRect)\n                context.fill(path1.union(path2), with: .foreground)\n            }\n            .resizable()\n            .help(\"Round Cap\")\n            .tag(endCap)\n        }\n    }\n\n    @ViewBuilder\n    private var leadingEndCapPicker: some View {\n        Picker(\"Leading End Cap\", selection: $info.leadingEndCap) {\n            ForEach(MenuBarEndCap.allCases.reversed(), id: \\.self) { endCap in\n                endCapPickerContentView(endCap: endCap, edge: .leading)\n            }\n        }\n        .fixedSize()\n    }\n\n    @ViewBuilder\n    private var trailingEndCapPicker: some View {\n        Picker(\"Trailing End Cap\", selection: $info.trailingEndCap) {\n            ForEach(MenuBarEndCap.allCases, id: \\.self) { endCap in\n                endCapPickerContentView(endCap: endCap, edge: .trailing)\n            }\n        }\n        .fixedSize()\n    }\n\n    @ViewBuilder\n    private var leadingEndCapExample: some View {\n        MenuBarEndCapExampleView(\n            endCap: info.leadingEndCap,\n            edge: .leading\n        )\n    }\n\n    @ViewBuilder\n    private var trailingEndCapExample: some View {\n        MenuBarEndCapExampleView(\n            endCap: info.trailingEndCap,\n            edge: .trailing\n        )\n    }\n\n    static func == (lhs: Self, rhs: Self) -> Bool {\n        lhs.info == rhs.info\n    }\n\n    private func cgRectEdge(for edge: HorizontalEdge) -> CGRectEdge {\n        switch edge {\n        case .leading: .minXEdge\n        case .trailing: .maxXEdge\n        }\n    }\n}\n\nprivate struct MenuBarEndCapExampleView: View {\n    @State private var radius: CGFloat = 0\n\n    let endCap: MenuBarEndCap\n    let edge: HorizontalEdge\n\n    var body: some View {\n        switch endCap {\n        case .square:\n            Rectangle()\n        case .round:\n            switch edge {\n            case .leading:\n                UnevenRoundedRectangle(\n                    topLeadingRadius: radius,\n                    bottomLeadingRadius: radius,\n                    style: .circular\n                )\n                .onFrameChange { frame in\n                    radius = frame.height / 2\n                }\n            case .trailing:\n                UnevenRoundedRectangle(\n                    bottomTrailingRadius: radius,\n                    topTrailingRadius: radius,\n                    style: .circular\n                )\n                .onFrameChange { frame in\n                    radius = frame.height / 2\n                }\n            }\n        }\n    }\n}\n\nprivate struct MenuBarSplitShapeExampleView: View, Equatable {\n    @Binding var info: MenuBarSplitShapeInfo\n\n    var body: some View {\n        HStack {\n            MenuBarFullShapeExampleView(info: $info.leading)\n                .equatable()\n            Divider()\n                .padding(.horizontal)\n            MenuBarFullShapeExampleView(info: $info.trailing)\n                .equatable()\n        }\n    }\n\n    static func == (lhs: Self, rhs: Self) -> Bool {\n        lhs.info == rhs.info\n    }\n}\n"
  },
  {
    "path": "Ice/MenuBar/Appearance/MenuBarAppearanceManager.swift",
    "content": "//\n//  MenuBarAppearanceManager.swift\n//  Ice\n//\n\nimport Cocoa\nimport Combine\n\n/// A manager for the appearance of the menu bar.\n@MainActor\nfinal class MenuBarAppearanceManager: ObservableObject {\n    /// The current menu bar appearance configuration.\n    @Published var configuration: MenuBarAppearanceConfigurationV2 = .defaultConfiguration\n\n    /// The currently previewed partial configuration.\n    @Published var previewConfiguration: MenuBarAppearancePartialConfiguration?\n\n    /// The shared app state.\n    private weak var appState: AppState?\n\n    /// Encoder for UserDefaults values.\n    private let encoder = JSONEncoder()\n\n    /// Decoder for UserDefaults values.\n    private let decoder = JSONDecoder()\n\n    /// Storage for internal observers.\n    private var cancellables = Set<AnyCancellable>()\n\n    /// The currently managed menu bar overlay panels.\n    private(set) var overlayPanels = Set<MenuBarOverlayPanel>()\n\n    /// The amount to inset the menu bar if called for by the configuration.\n    let menuBarInsetAmount: CGFloat = 5\n\n    /// Creates a manager with the given app state.\n    init(appState: AppState) {\n        self.appState = appState\n    }\n\n    /// Performs initial setup of the manager.\n    func performSetup() {\n        loadInitialState()\n        configureCancellables()\n    }\n\n    /// Loads the initial values for the configuration.\n    private func loadInitialState() {\n        do {\n            if let data = Defaults.data(forKey: .menuBarAppearanceConfigurationV2) {\n                configuration = try decoder.decode(MenuBarAppearanceConfigurationV2.self, from: data)\n            }\n        } catch {\n            Logger.appearanceManager.error(\"Error decoding configuration: \\(error)\")\n        }\n    }\n\n    /// Configures the internal observers for the manager.\n    private func configureCancellables() {\n        var c = Set<AnyCancellable>()\n\n        NotificationCenter.default\n            .publisher(for: NSApplication.didChangeScreenParametersNotification)\n            .debounce(for: 0.1, scheduler: DispatchQueue.main)\n            .sink { [weak self] _ in\n                guard let self else {\n                    return\n                }\n                while let panel = overlayPanels.popFirst() {\n                    panel.orderOut(self)\n                }\n                if Set(overlayPanels.map { $0.owningScreen }) != Set(NSScreen.screens) {\n                    configureOverlayPanels(with: configuration)\n                }\n            }\n            .store(in: &c)\n\n        $configuration\n            .encode(encoder: encoder)\n            .receive(on: DispatchQueue.main)\n            .sink { completion in\n                if case .failure(let error) = completion {\n                    Logger.appearanceManager.error(\"Error encoding configuration: \\(error)\")\n                }\n            } receiveValue: { data in\n                Defaults.set(data, forKey: .menuBarAppearanceConfigurationV2)\n            }\n            .store(in: &c)\n\n        $configuration\n            .throttle(for: 0.1, scheduler: DispatchQueue.main, latest: true)\n            .sink { [weak self] configuration in\n                guard let self else {\n                    return\n                }\n                // The overlay panels may not have been configured yet. Since some of the\n                // properties on the manager might call for them, try to configure now.\n                if overlayPanels.isEmpty {\n                    configureOverlayPanels(with: configuration)\n                }\n            }\n            .store(in: &c)\n\n        cancellables = c\n    }\n\n    /// Returns a Boolean value that indicates whether a set of overlay panels\n    /// is needed for the given configuration.\n    private func needsOverlayPanels(for configuration: MenuBarAppearanceConfigurationV2) -> Bool {\n        let current = configuration.current\n        if current.hasShadow {\n            return true\n        }\n        if current.hasBorder {\n            return true\n        }\n        if configuration.shapeKind != .none {\n            return true\n        }\n        if current.tintKind != .none {\n            return true\n        }\n        return false\n    }\n\n    /// Configures the manager's overlay panels, if required by the given configuration.\n    private func configureOverlayPanels(with configuration: MenuBarAppearanceConfigurationV2) {\n        guard\n            let appState,\n            needsOverlayPanels(for: configuration)\n        else {\n            while let panel = overlayPanels.popFirst() {\n                panel.close()\n            }\n            return\n        }\n\n        var overlayPanels = Set<MenuBarOverlayPanel>()\n        for screen in NSScreen.screens {\n            let panel = MenuBarOverlayPanel(appState: appState, owningScreen: screen)\n            overlayPanels.insert(panel)\n            panel.needsShow = true\n        }\n\n        self.overlayPanels = overlayPanels\n    }\n\n    /// Sets the value of ``MenuBarOverlayPanel/isDraggingMenuBarItem`` for each\n    /// of the manager's overlay panels.\n    func setIsDraggingMenuBarItem(_ isDragging: Bool) {\n        for panel in overlayPanels {\n            panel.isDraggingMenuBarItem = isDragging\n        }\n    }\n}\n\n// MARK: MenuBarAppearanceManager: BindingExposable\nextension MenuBarAppearanceManager: BindingExposable { }\n\n// MARK: - Logger\nprivate extension Logger {\n    /// The logger to use for the menu bar appearance manager.\n    static let appearanceManager = Logger(category: \"MenuBarAppearanceManager\")\n}\n"
  },
  {
    "path": "Ice/MenuBar/Appearance/MenuBarOverlayPanel.swift",
    "content": "//\n//  MenuBarOverlayPanel.swift\n//  Ice\n//\n\nimport Cocoa\nimport Combine\n\n// MARK: - Overlay Panel\n\n/// A subclass of `NSPanel` that sits atop the menu bar to alter its appearance.\nfinal class MenuBarOverlayPanel: NSPanel {\n    /// Flags representing the updatable components of a panel.\n    enum UpdateFlag: String, CustomStringConvertible {\n        case applicationMenuFrame\n        case desktopWallpaper\n\n        var description: String { rawValue }\n    }\n\n    /// The kind of validation that occurs before an update.\n    private enum ValidationKind {\n        case showing\n        case updates\n    }\n\n    /// A context that manages panel update tasks.\n    private final class UpdateTaskContext {\n        private var tasks = [UpdateFlag: Task<Void, any Error>]()\n\n        /// Sets the task for the given update flag.\n        ///\n        /// Setting the task cancels the previous task for the flag, if there is one.\n        ///\n        /// - Parameters:\n        ///   - flag: The update flag to set the task for.\n        ///   - timeout: The timeout of the task.\n        ///   - operation: The operation for the task to perform.\n        func setTask(for flag: UpdateFlag, timeout: Duration, operation: @escaping () async throws -> Void) {\n            cancelTask(for: flag)\n            tasks[flag] = Task.detached(timeout: timeout) {\n                try await operation()\n            }\n        }\n\n        /// Cancels the task for the given update flag.\n        ///\n        /// - Parameter flag: The update flag to cancel the task for.\n        func cancelTask(for flag: UpdateFlag) {\n            tasks.removeValue(forKey: flag)?.cancel()\n        }\n    }\n\n    /// A Boolean value that indicates whether the panel needs to be shown.\n    @Published var needsShow = false\n\n    /// A Boolean value that indicates whether the user is dragging a menu bar item.\n    @Published var isDraggingMenuBarItem = false\n\n    /// Flags representing the components of the panel currently in need of an update.\n    @Published private(set) var updateFlags = Set<UpdateFlag>()\n\n    /// The frame of the application menu.\n    @Published private(set) var applicationMenuFrame: CGRect?\n\n    /// The current desktop wallpaper, clipped to the bounds of the menu bar.\n    @Published private(set) var desktopWallpaper: CGImage?\n\n    /// Storage for internal observers.\n    private var cancellables = Set<AnyCancellable>()\n\n    /// The context that manages panel update tasks.\n    private let updateTaskContext = UpdateTaskContext()\n\n    /// The shared app state.\n    private(set) weak var appState: AppState?\n\n    /// The screen that owns the panel.\n    let owningScreen: NSScreen\n\n    /// Creates an overlay panel with the given app state and owning screen.\n    init(appState: AppState, owningScreen: NSScreen) {\n        self.appState = appState\n        self.owningScreen = owningScreen\n        super.init(\n            contentRect: .zero,\n            styleMask: [.borderless, .fullSizeContentView, .nonactivatingPanel],\n            backing: .buffered,\n            defer: false\n        )\n        self.level = .statusBar\n        self.title = \"Menu Bar Overlay\"\n        self.backgroundColor = .clear\n        self.hasShadow = false\n        self.ignoresMouseEvents = true\n        self.collectionBehavior = [.fullScreenNone, .ignoresCycle, .moveToActiveSpace]\n        self.contentView = MenuBarOverlayPanelContentView()\n        configureCancellables()\n    }\n\n    private func configureCancellables() {\n        var c = Set<AnyCancellable>()\n\n        // Show the panel on the active space.\n        NSWorkspace.shared.notificationCenter\n            .publisher(for: NSWorkspace.activeSpaceDidChangeNotification)\n            .debounce(for: 0.1, scheduler: DispatchQueue.main)\n            .sink { [weak self] _ in\n                self?.needsShow = true\n            }\n            .store(in: &c)\n\n        // Update when light/dark mode changes.\n        DistributedNotificationCenter.default()\n            .publisher(for: DistributedNotificationCenter.interfaceThemeChangedNotification)\n            .debounce(for: 0.1, scheduler: DispatchQueue.main)\n            .sink { [weak self] _ in\n                guard let self else {\n                    return\n                }\n                updateTaskContext.setTask(for: .desktopWallpaper, timeout: .seconds(5)) {\n                    while true {\n                        try Task.checkCancellation()\n                        self.insertUpdateFlag(.desktopWallpaper)\n                        try await Task.sleep(for: .seconds(1))\n                    }\n                }\n            }\n            .store(in: &c)\n\n        // Update application menu frame when the menu bar owning or frontmost app changes.\n        Publishers.Merge(\n            NSWorkspace.shared.publisher(for: \\.menuBarOwningApplication, options: .old)\n                .combineLatest(NSWorkspace.shared.publisher(for: \\.menuBarOwningApplication, options: .new))\n                .compactMap { $0 == $1 ? nil : $0 },\n            NSWorkspace.shared.publisher(for: \\.frontmostApplication, options: .old)\n                .combineLatest(NSWorkspace.shared.publisher(for: \\.frontmostApplication, options: .new))\n                .compactMap { $0 == $1 ? nil : $0 }\n        )\n        .removeDuplicates()\n        .sink { [weak self] _ in\n            guard\n                let self,\n                let appState\n            else {\n                return\n            }\n            let displayID = owningScreen.displayID\n            updateTaskContext.setTask(for: .applicationMenuFrame, timeout: .seconds(10)) {\n                var hasDoneInitialUpdate = false\n                while true {\n                    try Task.checkCancellation()\n                    guard\n                        let latestFrame = appState.menuBarManager.getApplicationMenuFrame(for: displayID),\n                        latestFrame != self.applicationMenuFrame\n                    else {\n                        if hasDoneInitialUpdate {\n                            try await Task.sleep(for: .seconds(1))\n                        } else {\n                            try await Task.sleep(for: .milliseconds(1))\n                        }\n                        continue\n                    }\n                    self.insertUpdateFlag(.applicationMenuFrame)\n                    hasDoneInitialUpdate = true\n                }\n            }\n            Task {\n                try? await Task.sleep(for: .milliseconds(100))\n                if self.owningScreen != NSScreen.main {\n                    self.updateTaskContext.cancelTask(for: .applicationMenuFrame)\n                }\n            }\n        }\n        .store(in: &c)\n\n        // Special cases for when the user drags an app onto or clicks into another space.\n        Publishers.Merge(\n            publisher(for: \\.isOnActiveSpace)\n                .receive(on: DispatchQueue.main)\n                .mapToVoid(),\n            UniversalEventMonitor.publisher(for: .leftMouseUp)\n                .filter { [weak self] _ in self?.isOnActiveSpace ?? false }\n                .mapToVoid()\n        )\n        .debounce(for: 0.05, scheduler: DispatchQueue.main)\n        .sink { [weak self] in\n            self?.insertUpdateFlag(.applicationMenuFrame)\n        }\n        .store(in: &c)\n\n        // Continually update the desktop wallpaper. Ideally, we would set up an observer\n        // for a wallpaper change notification, but macOS doesn't post one anymore.\n        Timer.publish(every: 5, on: .main, in: .default)\n            .autoconnect()\n            .sink { [weak self] _ in\n                self?.insertUpdateFlag(.desktopWallpaper)\n            }\n            .store(in: &c)\n\n        Timer.publish(every: 10, on: .main, in: .default)\n            .autoconnect()\n            .sink { [weak self] _ in\n                self?.insertUpdateFlag(.applicationMenuFrame)\n            }\n            .store(in: &c)\n\n        $needsShow\n            .debounce(for: 0.05, scheduler: DispatchQueue.main)\n            .sink { [weak self] needsShow in\n                guard let self, needsShow else {\n                    return\n                }\n                defer {\n                    self.needsShow = false\n                }\n                show()\n            }\n            .store(in: &c)\n\n        $updateFlags\n            .sink { [weak self] flags in\n                guard let self, !flags.isEmpty else {\n                    return\n                }\n                Task {\n                    // Must be run async, or this will not remove the flags.\n                    self.updateFlags.removeAll()\n                }\n                let windows = WindowInfo.getOnScreenWindows()\n                guard let owningDisplay = self.validate(for: .updates, with: windows) else {\n                    return\n                }\n                performUpdates(for: flags, windows: windows, display: owningDisplay)\n            }\n            .store(in: &c)\n\n        if let appState {\n            appState.menuBarManager.$isMenuBarHiddenBySystem\n                .sink { [weak self] isHidden in\n                    self?.alphaValue = isHidden ? 0 : 1\n                }\n                .store(in: &c)\n        }\n\n        cancellables = c\n    }\n\n    /// Inserts the given update flag into the panel's current list of update flags.\n    private func insertUpdateFlag(_ flag: UpdateFlag) {\n        updateFlags.insert(flag)\n    }\n\n    /// Performs validation for the given validation kind. Returns the panel's\n    /// owning display if successful. Returns `nil` on failure.\n    private func validate(for kind: ValidationKind, with windows: [WindowInfo]) -> CGDirectDisplayID? {\n        lazy var actionMessage = switch kind {\n        case .showing: \"Preventing overlay panel from showing.\"\n        case .updates: \"Preventing overlay panel from updating.\"\n        }\n        guard let appState else {\n            Logger.overlayPanel.debug(\"No app state. \\(actionMessage)\")\n            return nil\n        }\n        guard !appState.menuBarManager.isMenuBarHiddenBySystemUserDefaults else {\n            Logger.overlayPanel.debug(\"Menu bar is hidden by system. \\(actionMessage)\")\n            return nil\n        }\n        guard !appState.isActiveSpaceFullscreen else {\n            Logger.overlayPanel.debug(\"Active space is fullscreen. \\(actionMessage)\")\n            return nil\n        }\n        let owningDisplay = owningScreen.displayID\n        guard appState.menuBarManager.hasValidMenuBar(in: windows, for: owningDisplay) else {\n            Logger.overlayPanel.debug(\"No valid menu bar found. \\(actionMessage)\")\n            return nil\n        }\n        return owningDisplay\n    }\n\n    /// Stores the frame of the menu bar's application menu.\n    private func updateApplicationMenuFrame(for display: CGDirectDisplayID) {\n        guard\n            let menuBarManager = appState?.menuBarManager,\n            !menuBarManager.isMenuBarHiddenBySystem\n        else {\n            return\n        }\n        applicationMenuFrame = menuBarManager.getApplicationMenuFrame(for: display)\n    }\n\n    /// Stores the area of the desktop wallpaper that is under the menu bar\n    /// of the given display.\n    private func updateDesktopWallpaper(for display: CGDirectDisplayID, with windows: [WindowInfo]) {\n        guard\n            let wallpaperWindow = WindowInfo.getWallpaperWindow(from: windows, for: display),\n            let menuBarWindow = WindowInfo.getMenuBarWindow(from: windows, for: display)\n        else {\n            return\n        }\n        let wallpaper = ScreenCapture.captureWindow(wallpaperWindow.windowID, screenBounds: menuBarWindow.frame)\n        if desktopWallpaper?.dataProvider?.data != wallpaper?.dataProvider?.data {\n            desktopWallpaper = wallpaper\n        }\n    }\n\n    /// Updates the panel to prepare for display.\n    private func performUpdates(for flags: Set<UpdateFlag>, windows: [WindowInfo], display: CGDirectDisplayID) {\n        if flags.contains(.applicationMenuFrame) {\n            updateApplicationMenuFrame(for: display)\n        }\n        if flags.contains(.desktopWallpaper) {\n            updateDesktopWallpaper(for: display, with: windows)\n        }\n    }\n\n    /// Shows the panel.\n    private func show() {\n        guard\n            let appState,\n            !appState.isPreview\n        else {\n            return\n        }\n\n        guard appState.appearanceManager.overlayPanels.contains(self) else {\n            Logger.overlayPanel.warning(\"Overlay panel \\(self) not retained\")\n            return\n        }\n\n        guard let menuBarHeight = owningScreen.getMenuBarHeight() else {\n            return\n        }\n\n        let newFrame = CGRect(\n            x: owningScreen.frame.minX,\n            y: (owningScreen.frame.maxY - menuBarHeight) - 5,\n            width: owningScreen.frame.width,\n            height: menuBarHeight + 5\n        )\n\n        alphaValue = 0\n        setFrame(newFrame, display: false)\n        orderFrontRegardless()\n\n        updateFlags = [.applicationMenuFrame, .desktopWallpaper]\n\n        if !appState.menuBarManager.isMenuBarHiddenBySystem {\n            animator().alphaValue = 1\n        }\n    }\n\n    override func isAccessibilityElement() -> Bool {\n        return false\n    }\n}\n\n// MARK: - Content View\n\nprivate final class MenuBarOverlayPanelContentView: NSView {\n    @Published private var fullConfiguration: MenuBarAppearanceConfigurationV2 = .defaultConfiguration\n\n    @Published private var previewConfiguration: MenuBarAppearancePartialConfiguration?\n\n    private var cancellables = Set<AnyCancellable>()\n\n    /// The overlay panel that contains the content view.\n    private var overlayPanel: MenuBarOverlayPanel? {\n        window as? MenuBarOverlayPanel\n    }\n\n    /// The currently displayed configuration.\n    private var configuration: MenuBarAppearancePartialConfiguration {\n        previewConfiguration ?? fullConfiguration.current\n    }\n\n    override func viewDidMoveToWindow() {\n        super.viewDidMoveToWindow()\n        configureCancellables()\n    }\n\n    private func configureCancellables() {\n        var c = Set<AnyCancellable>()\n\n        if let overlayPanel {\n            if let appState = overlayPanel.appState {\n                appState.appearanceManager.$configuration\n                    .removeDuplicates()\n                    .assign(to: &$fullConfiguration)\n\n                appState.appearanceManager.$previewConfiguration\n                    .removeDuplicates()\n                    .assign(to: &$previewConfiguration)\n\n                for section in appState.menuBarManager.sections {\n                    // Redraw whenever the window frame of a control item changes.\n                    //\n                    // - NOTE: A previous attempt was made to redraw the view when the\n                    //   section's `isHidden` property was changed. This would be semantically\n                    //   ideal, but the property sometimes changes before the menu bar items\n                    //   are actually updated on-screen. Since the view's drawing process relies\n                    //   on getting an accurate position of each menu bar item, we need to use\n                    //   something that publishes its changes only after the items are updated.\n                    section.controlItem.$windowFrame\n                        .receive(on: DispatchQueue.main)\n                        .sink { [weak self] _ in\n                            self?.needsDisplay = true\n                        }\n                        .store(in: &c)\n\n                    // Redraw whenever the visibility of a control item changes.\n                    //\n                    // - NOTE: If the \"ShowSectionDividers\" setting is disabled, the window\n                    //   frame does not update when the section is hidden or shown, but the\n                    //   visibility does. We observe both to ensure the update occurs.\n                    section.controlItem.$isVisible\n                        .receive(on: DispatchQueue.main)\n                        .sink { [weak self] _ in\n                            self?.needsDisplay = true\n                        }\n                        .store(in: &c)\n                }\n            }\n\n            // Fade out whenever a menu bar item is being dragged.\n            overlayPanel.$isDraggingMenuBarItem\n                .removeDuplicates()\n                .sink { [weak self] isDragging in\n                    if isDragging {\n                        self?.animator().alphaValue = 0\n                    } else {\n                        self?.animator().alphaValue = 1\n                    }\n                }\n                .store(in: &c)\n            // Redraw whenever the application menu frame changes.\n            overlayPanel.$applicationMenuFrame\n                .sink { [weak self] _ in\n                    self?.needsDisplay = true\n                }\n                .store(in: &c)\n            // Redraw whenever the desktop wallpaper changes.\n            overlayPanel.$desktopWallpaper\n                .sink { [weak self] _ in\n                    self?.needsDisplay = true\n                }\n                .store(in: &c)\n        }\n\n        // Redraw whenever the configurations change.\n        $fullConfiguration.mapToVoid()\n            .merge(with: $previewConfiguration.mapToVoid())\n            .sink { [weak self] _ in\n                self?.needsDisplay = true\n            }\n            .store(in: &c)\n\n        cancellables = c\n    }\n\n    /// Returns a path in the given rectangle, with the given end caps,\n    /// and inset by the given amounts.\n    private func shapePath(in rect: CGRect, leadingEndCap: MenuBarEndCap, trailingEndCap: MenuBarEndCap, screen: NSScreen) -> NSBezierPath {\n        let insetRect: CGRect = if !screen.hasNotch {\n            switch (leadingEndCap, trailingEndCap) {\n            case (.square, .square):\n                CGRect(x: rect.origin.x, y: rect.origin.y + 1, width: rect.width, height: rect.height - 2)\n            case (.square, .round):\n                CGRect(x: rect.origin.x, y: rect.origin.y + 1, width: rect.width - 1, height: rect.height - 2)\n            case (.round, .square):\n                CGRect(x: rect.origin.x + 1, y: rect.origin.y + 1, width: rect.width - 1, height: rect.height - 2)\n            case (.round, .round):\n                CGRect(x: rect.origin.x + 1, y: rect.origin.y + 1, width: rect.width - 2, height: rect.height - 2)\n            }\n        } else {\n            rect\n        }\n\n        let shapeBounds = CGRect(\n            x: insetRect.minX + insetRect.height / 2,\n            y: insetRect.minY,\n            width: insetRect.width - insetRect.height,\n            height: insetRect.height\n        )\n        let leadingEndCapBounds = CGRect(\n            x: insetRect.minX,\n            y: insetRect.minY,\n            width: insetRect.height,\n            height: insetRect.height\n        )\n        let trailingEndCapBounds = CGRect(\n            x: insetRect.maxX - insetRect.height,\n            y: insetRect.minY,\n            width: insetRect.height,\n            height: insetRect.height\n        )\n\n        var path = NSBezierPath(rect: shapeBounds)\n\n        path = switch leadingEndCap {\n        case .square: path.union(NSBezierPath(rect: leadingEndCapBounds))\n        case .round: path.union(NSBezierPath(ovalIn: leadingEndCapBounds))\n        }\n\n        path = switch trailingEndCap {\n        case .square: path.union(NSBezierPath(rect: trailingEndCapBounds))\n        case .round: path.union(NSBezierPath(ovalIn: trailingEndCapBounds))\n        }\n\n        return path\n    }\n\n    /// Returns a path for the ``MenuBarShapeKind/full`` shape kind.\n    private func pathForFullShape(in rect: CGRect, info: MenuBarFullShapeInfo, isInset: Bool, screen: NSScreen) -> NSBezierPath {\n        guard let appearanceManager = overlayPanel?.appState?.appearanceManager else {\n            return NSBezierPath()\n        }\n        var rect = rect\n        let shouldInset = isInset && screen.hasNotch\n        if shouldInset {\n            rect = rect.insetBy(dx: 0, dy: appearanceManager.menuBarInsetAmount)\n            if info.leadingEndCap == .round {\n                rect.origin.x += appearanceManager.menuBarInsetAmount\n                rect.size.width -= appearanceManager.menuBarInsetAmount\n            }\n            if info.trailingEndCap == .round {\n                rect.size.width -= appearanceManager.menuBarInsetAmount\n            }\n        }\n        return shapePath(\n            in: rect,\n            leadingEndCap: info.leadingEndCap,\n            trailingEndCap: info.trailingEndCap,\n            screen: screen\n        )\n    }\n\n    /// Returns a path for the ``MenuBarShapeKind/split`` shape kind.\n    private func pathForSplitShape(in rect: CGRect, info: MenuBarSplitShapeInfo, isInset: Bool, screen: NSScreen) -> NSBezierPath {\n        guard let appearanceManager = overlayPanel?.appState?.appearanceManager else {\n            return NSBezierPath()\n        }\n        var rect = rect\n        let shouldInset = isInset && screen.hasNotch\n        if shouldInset {\n            rect = rect.insetBy(dx: 0, dy: appearanceManager.menuBarInsetAmount)\n            if info.leading.leadingEndCap == .round {\n                rect.origin.x += appearanceManager.menuBarInsetAmount\n                rect.size.width -= appearanceManager.menuBarInsetAmount\n            }\n            if info.trailing.trailingEndCap == .round {\n                rect.size.width -= appearanceManager.menuBarInsetAmount\n            }\n        }\n        let leadingPathBounds: CGRect = {\n            guard\n                var maxX = overlayPanel?.applicationMenuFrame?.width,\n                maxX > 0\n            else {\n                return .zero\n            }\n            if shouldInset {\n                maxX += 10\n                if info.leading.leadingEndCap == .square {\n                    maxX += appearanceManager.menuBarInsetAmount\n                }\n            } else {\n                maxX += 20\n            }\n            return CGRect(x: rect.minX, y: rect.minY, width: maxX, height: rect.height)\n        }()\n        let trailingPathBounds: CGRect = {\n            let items = MenuBarItem.getMenuBarItems(on: screen.displayID, onScreenOnly: true, activeSpaceOnly: false)\n            guard !items.isEmpty else {\n                return .zero\n            }\n            let totalWidth = items.reduce(into: 0) { width, item in\n                width += item.frame.width\n            }\n            var position = rect.maxX - totalWidth\n            if shouldInset {\n                position += 4\n                if info.trailing.trailingEndCap == .square {\n                    position -= appearanceManager.menuBarInsetAmount\n                }\n            } else {\n                position -= 7\n            }\n            return CGRect(x: position, y: rect.minY, width: rect.maxX - position, height: rect.height)\n        }()\n\n        if leadingPathBounds == .zero || trailingPathBounds == .zero || leadingPathBounds.intersects(trailingPathBounds) {\n            return shapePath(\n                in: rect,\n                leadingEndCap: info.leading.leadingEndCap,\n                trailingEndCap: info.trailing.trailingEndCap,\n                screen: screen\n            )\n        } else {\n            let leadingPath = shapePath(\n                in: leadingPathBounds,\n                leadingEndCap: info.leading.leadingEndCap,\n                trailingEndCap: info.leading.trailingEndCap,\n                screen: screen\n            )\n            let trailingPath = shapePath(\n                in: trailingPathBounds,\n                leadingEndCap: info.trailing.leadingEndCap,\n                trailingEndCap: info.trailing.trailingEndCap,\n                screen: screen\n            )\n            let path = NSBezierPath()\n            path.append(leadingPath)\n            path.append(trailingPath)\n            return path\n        }\n    }\n\n    /// Returns the bounds that the view's drawn content can occupy.\n    private func getDrawableBounds() -> CGRect {\n        return CGRect(\n            x: bounds.origin.x,\n            y: bounds.origin.y + 5,\n            width: bounds.width,\n            height: bounds.height - 5\n        )\n    }\n\n    /// Draws the tint defined by the given configuration in the given rectangle.\n    private func drawTint(in rect: CGRect) {\n        switch configuration.tintKind {\n        case .none:\n            break\n        case .solid:\n            if let tintColor = NSColor(cgColor: configuration.tintColor)?.withAlphaComponent(0.2) {\n                tintColor.setFill()\n                rect.fill()\n            }\n        case .gradient:\n            if let tintGradient = configuration.tintGradient.withAlphaComponent(0.2).nsGradient {\n                tintGradient.draw(in: rect, angle: 0)\n            }\n        }\n    }\n\n    override func draw(_ dirtyRect: NSRect) {\n        guard\n            let overlayPanel,\n            let context = NSGraphicsContext.current\n        else {\n            return\n        }\n\n        let drawableBounds = getDrawableBounds()\n\n        let shapePath = switch fullConfiguration.shapeKind {\n        case .none:\n            NSBezierPath(rect: drawableBounds)\n        case .full:\n            pathForFullShape(\n                in: drawableBounds,\n                info: fullConfiguration.fullShapeInfo,\n                isInset: fullConfiguration.isInset,\n                screen: overlayPanel.owningScreen\n            )\n        case .split:\n            pathForSplitShape(\n                in: drawableBounds,\n                info: fullConfiguration.splitShapeInfo,\n                isInset: fullConfiguration.isInset,\n                screen: overlayPanel.owningScreen\n            )\n        }\n\n        var hasBorder = false\n\n        switch fullConfiguration.shapeKind {\n        case .none:\n            if configuration.hasShadow {\n                let gradient = NSGradient(\n                    colors: [\n                        NSColor(white: 0.0, alpha: 0.0),\n                        NSColor(white: 0.0, alpha: 0.2),\n                    ]\n                )\n                let shadowBounds = CGRect(\n                    x: bounds.minX,\n                    y: bounds.minY,\n                    width: bounds.width,\n                    height: 5\n                )\n                gradient?.draw(in: shadowBounds, angle: 90)\n            }\n\n            drawTint(in: drawableBounds)\n\n            if configuration.hasBorder {\n                let borderBounds = CGRect(\n                    x: bounds.minX,\n                    y: bounds.minY + 5,\n                    width: bounds.width,\n                    height: configuration.borderWidth\n                )\n                NSColor(cgColor: configuration.borderColor)?.setFill()\n                NSBezierPath(rect: borderBounds).fill()\n            }\n        case .full, .split:\n            if let desktopWallpaper = overlayPanel.desktopWallpaper {\n                context.saveGraphicsState()\n                defer {\n                    context.restoreGraphicsState()\n                }\n\n                let invertedClipPath = NSBezierPath(rect: drawableBounds)\n                invertedClipPath.append(shapePath.reversed)\n                invertedClipPath.setClip()\n\n                context.cgContext.draw(desktopWallpaper, in: drawableBounds)\n            }\n\n            if configuration.hasShadow {\n                context.saveGraphicsState()\n                defer {\n                    context.restoreGraphicsState()\n                }\n\n                let shadowClipPath = NSBezierPath(rect: bounds)\n                shadowClipPath.append(shapePath.reversed)\n                shadowClipPath.setClip()\n\n                shapePath.drawShadow(color: .black.withAlphaComponent(0.5), radius: 5)\n            }\n\n            if configuration.hasBorder {\n                hasBorder = true\n            }\n\n            do {\n                context.saveGraphicsState()\n                defer {\n                    context.restoreGraphicsState()\n                }\n\n                shapePath.setClip()\n\n                drawTint(in: drawableBounds)\n            }\n\n            if\n                hasBorder,\n                let borderColor = NSColor(cgColor: configuration.borderColor)\n            {\n                context.saveGraphicsState()\n                defer {\n                    context.restoreGraphicsState()\n                }\n\n                let borderPath = switch fullConfiguration.shapeKind {\n                case .none:\n                    NSBezierPath(rect: drawableBounds)\n                case .full:\n                    pathForFullShape(\n                        in: drawableBounds,\n                        info: fullConfiguration.fullShapeInfo,\n                        isInset: fullConfiguration.isInset,\n                        screen: overlayPanel.owningScreen\n                    )\n                case .split:\n                    pathForSplitShape(\n                        in: drawableBounds,\n                        info: fullConfiguration.splitShapeInfo,\n                        isInset: fullConfiguration.isInset,\n                        screen: overlayPanel.owningScreen\n                    )\n                }\n\n                // HACK: Insetting a path to get an \"inside\" stroke is surprisingly\n                // difficult. We can fake the correct line width by doubling it, as\n                // anything outside the shape path will be clipped.\n                borderPath.lineWidth = configuration.borderWidth * 2\n                borderPath.setClip()\n\n                borderColor.setStroke()\n                borderPath.stroke()\n            }\n        }\n    }\n}\n\n// MARK: - Logger\nprivate extension Logger {\n    static let overlayPanel = Logger(category: \"MenuBarOverlayPanel\")\n}\n"
  },
  {
    "path": "Ice/MenuBar/Appearance/MenuBarShape.swift",
    "content": "//\n//  MenuBarShape.swift\n//  Ice\n//\n\nimport CoreGraphics\n\n/// An end cap in a menu bar shape.\nenum MenuBarEndCap: Int, Codable, Hashable, CaseIterable {\n    /// An end cap with a square shape.\n    case square = 0\n    /// An end cap with a rounded shape.\n    case round = 1\n}\n\n/// A type that specifies a custom shape kind for the menu bar.\nenum MenuBarShapeKind: Int, Codable, Hashable, CaseIterable {\n    /// The menu bar does not use a custom shape.\n    case none = 0\n    /// A custom shape that takes up the full menu bar.\n    case full = 1\n    /// A custom shape that splits the menu bar between\n    /// its leading and trailing sides.\n    case split = 2\n}\n\n/// Information for the ``MenuBarShapeKind/full`` menu bar\n/// shape kind.\nstruct MenuBarFullShapeInfo: Codable, Hashable {\n    /// The leading end cap of the shape.\n    var leadingEndCap: MenuBarEndCap\n    /// The trailing end cap of the shape.\n    var trailingEndCap: MenuBarEndCap\n}\n\nextension MenuBarFullShapeInfo {\n    var hasRoundedShape: Bool {\n        leadingEndCap == .round || trailingEndCap == .round\n    }\n}\n\nextension MenuBarFullShapeInfo {\n    static let `default` = MenuBarFullShapeInfo(leadingEndCap: .round, trailingEndCap: .round)\n}\n\n/// Information for the ``MenuBarShapeKind/split`` menu bar\n/// shape kind.\nstruct MenuBarSplitShapeInfo: Codable, Hashable {\n    /// The leading information of the shape.\n    var leading: MenuBarFullShapeInfo\n    /// The trailing information of the shape.\n    var trailing: MenuBarFullShapeInfo\n}\n\nextension MenuBarSplitShapeInfo {\n    var hasRoundedShape: Bool {\n        leading.hasRoundedShape || trailing.hasRoundedShape\n    }\n}\n\nextension MenuBarSplitShapeInfo {\n    static let `default` = MenuBarSplitShapeInfo(leading: .default, trailing: .default)\n}\n"
  },
  {
    "path": "Ice/MenuBar/Appearance/MenuBarTintKind.swift",
    "content": "//\n//  MenuBarTintKind.swift\n//  Ice\n//\n\nimport SwiftUI\n\n/// A type that specifies how the menu bar is tinted.\nenum MenuBarTintKind: Int, CaseIterable, Codable, Identifiable {\n    /// The menu bar is not tinted.\n    case none = 0\n    /// The menu bar is tinted with a solid color.\n    case solid = 1\n    /// The menu bar is tinted with a gradient.\n    case gradient = 2\n\n    var id: Int { rawValue }\n\n    /// Localized string key representation.\n    var localized: LocalizedStringKey {\n        switch self {\n        case .none: \"None\"\n        case .solid: \"Solid\"\n        case .gradient: \"Gradient\"\n        }\n    }\n}\n"
  },
  {
    "path": "Ice/MenuBar/ControlItem/ControlItem.swift",
    "content": "//\n//  ControlItem.swift\n//  Ice\n//\n\nimport Cocoa\nimport Combine\n\n/// A status item that controls a section in the menu bar.\n@MainActor\nfinal class ControlItem {\n    /// Possible identifiers for control items.\n    enum Identifier: String, CaseIterable {\n        case iceIcon = \"SItem\"\n        case hidden = \"HItem\"\n        case alwaysHidden = \"AHItem\"\n    }\n\n    /// Possible hiding states for control items.\n    enum HidingState {\n        case hideItems, showItems\n    }\n\n    /// Possible lengths for control items.\n    enum Lengths {\n        static let standard: CGFloat = NSStatusItem.variableLength\n        static let expanded: CGFloat = 10_000\n    }\n\n    /// The control item's hiding state (`@Published`).\n    @Published var state = HidingState.hideItems\n\n    /// A Boolean value that indicates whether the control item is visible (`@Published`).\n    @Published var isVisible = true\n\n    /// The frame of the control item's window (`@Published`).\n    @Published private(set) var windowFrame: CGRect?\n\n    /// The shared app state.\n    private weak var appState: AppState?\n\n    /// The control item's underlying status item.\n    private let statusItem: NSStatusItem\n\n    /// A horizontal constraint for the control item's content view.\n    private let constraint: NSLayoutConstraint?\n\n    /// The control item's identifier.\n    private let identifier: Identifier\n\n    /// Storage for internal observers.\n    private var cancellables = Set<AnyCancellable>()\n\n    /// The menu bar section associated with the control item.\n    private weak var section: MenuBarSection? {\n        appState?.menuBarManager.sections.first { $0.controlItem === self }\n    }\n\n    /// The control item's window.\n    var window: NSWindow? {\n        statusItem.button?.window\n    }\n\n    /// The identifier of the control item's window.\n    var windowID: CGWindowID? {\n        guard let window else {\n            return nil\n        }\n        return CGWindowID(window.windowNumber)\n    }\n\n    /// A Boolean value that indicates whether the control item serves as\n    /// a divider between sections.\n    var isSectionDivider: Bool {\n        identifier != .iceIcon\n    }\n\n    /// A Boolean value that indicates whether the control item is currently\n    /// displayed in the menu bar.\n    var isAddedToMenuBar: Bool {\n        statusItem.isVisible\n    }\n\n    /// Creates a control item with the given identifier and app state.\n    init(identifier: Identifier, appState: AppState) {\n        let autosaveName = identifier.rawValue\n\n        // If the status item doesn't have a preferred position, set it\n        // according to the identifier.\n        if StatusItemDefaults[.preferredPosition, autosaveName] == nil {\n            switch identifier {\n            case .iceIcon:\n                StatusItemDefaults[.preferredPosition, autosaveName] = 0\n            case .hidden:\n                StatusItemDefaults[.preferredPosition, autosaveName] = 1\n            case .alwaysHidden:\n                break\n            }\n        }\n\n        self.statusItem = NSStatusBar.system.statusItem(withLength: 0)\n        self.statusItem.autosaveName = autosaveName\n        self.identifier = identifier\n        self.appState = appState\n\n        // This could break in a new macOS release, but we need this constraint in order to be\n        // able to hide the control item when the `ShowSectionDividers` setting is disabled. A\n        // previous implementation used the status item's `isVisible` property, which was more\n        // robust, but would completely remove the control item. With the current set of\n        // features, we need to be able to accurately retrieve the items for each section, so\n        // we need the control item to always be present to act as a delimiter. The new solution\n        // is to remove the constraint that prevents status items from having a length of zero,\n        // then resize the content view. FIXME: Find a replacement for this.\n        if\n            let button = statusItem.button,\n            let constraints = button.window?.contentView?.constraintsAffectingLayout(for: .horizontal),\n            let constraint = constraints.first(where: Predicates.controlItemConstraint(button: button))\n        {\n            assert(constraints.filter(Predicates.controlItemConstraint(button: button)).count == 1)\n            self.constraint = constraint\n        } else {\n            self.constraint = nil\n        }\n\n        configureStatusItem()\n    }\n\n    /// Removes the status item without clearing its stored position.\n    deinit {\n        // Removing the status item has the unwanted side effect of deleting\n        // the preferredPosition. Cache and restore it.\n        let autosaveName = statusItem.autosaveName as String\n        let cached = StatusItemDefaults[.preferredPosition, autosaveName]\n        NSStatusBar.system.removeStatusItem(statusItem)\n        StatusItemDefaults[.preferredPosition, autosaveName] = cached\n    }\n\n    /// Configures the internal observers for the control item.\n    private func configureCancellables() {\n        var c = Set<AnyCancellable>()\n\n        $state\n            .sink { [weak self] state in\n                self?.updateStatusItem(with: state)\n            }\n            .store(in: &c)\n\n        Publishers.CombineLatest($isVisible, $state)\n            .sink { [weak self] (isVisible, state) in\n                guard\n                    let self,\n                    let section\n                else {\n                    return\n                }\n                if isVisible {\n                    statusItem.length = switch section.name {\n                    case .visible: Lengths.standard\n                    case .hidden, .alwaysHidden:\n                        switch state {\n                        case .hideItems: Lengths.expanded\n                        case .showItems: Lengths.standard\n                        }\n                    }\n                    constraint?.isActive = true\n                } else {\n                    statusItem.length = 0\n                    constraint?.isActive = false\n                    if let window {\n                        var size = window.frame.size\n                        size.width = 1\n                        window.setContentSize(size)\n                    }\n                }\n            }\n            .store(in: &c)\n\n        constraint?.publisher(for: \\.isActive)\n            .removeDuplicates()\n            .sink { [weak self] isActive in\n                self?.isVisible = isActive\n            }\n            .store(in: &c)\n\n        statusItem.publisher(for: \\.isVisible)\n            .receive(on: DispatchQueue.main)\n            .sink { [weak self] isVisible in\n                guard\n                    let self,\n                    let appState,\n                    let section\n                else {\n                    return\n                }\n\n                let manager = appState.settingsManager.hotkeySettingsManager\n\n                let hotkey: Hotkey? = switch section.name {\n                case .visible: nil\n                case .hidden: manager.hotkey(withAction: .toggleHiddenSection)\n                case .alwaysHidden: manager.hotkey(withAction: .toggleAlwaysHiddenSection)\n                }\n\n                guard let hotkey else {\n                    return\n                }\n\n                if isVisible {\n                    hotkey.enable()\n                } else {\n                    hotkey.disable()\n                }\n            }\n            .store(in: &c)\n\n        window?.publisher(for: \\.frame)\n            .sink { [weak self] frame in\n                guard\n                    let self,\n                    let screen = window?.screen,\n                    screen.frame.intersects(frame)\n                else {\n                    return\n                }\n                windowFrame = frame\n            }\n            .store(in: &c)\n\n        if let appState {\n            appState.settingsManager.generalSettingsManager.$showIceIcon\n                .receive(on: DispatchQueue.main)\n                .sink { [weak self] showIceIcon in\n                    guard\n                        let self,\n                        !isSectionDivider\n                    else {\n                        return\n                    }\n                    if showIceIcon {\n                        addToMenuBar()\n                    } else {\n                        removeFromMenuBar()\n                    }\n                }\n                .store(in: &c)\n\n            appState.settingsManager.generalSettingsManager.$iceIcon\n                .receive(on: DispatchQueue.main)\n                .sink { [weak self] _ in\n                    guard let self else {\n                        return\n                    }\n                    updateStatusItem(with: state)\n                }\n                .store(in: &c)\n\n            appState.settingsManager.generalSettingsManager.$customIceIconIsTemplate\n                .receive(on: DispatchQueue.main)\n                .sink { [weak self] _ in\n                    guard let self else {\n                        return\n                    }\n                    updateStatusItem(with: state)\n                }\n                .store(in: &c)\n\n            appState.settingsManager.generalSettingsManager.$useIceBar\n                .receive(on: DispatchQueue.main)\n                .sink { [weak self] useIceBar in\n                    guard\n                        let self,\n                        let button = statusItem.button\n                    else {\n                        return\n                    }\n                    if useIceBar {\n                        button.sendAction(on: [.leftMouseDown, .rightMouseUp])\n                    } else {\n                        button.sendAction(on: [.leftMouseUp, .rightMouseUp])\n                    }\n                }\n                .store(in: &c)\n\n            appState.settingsManager.advancedSettingsManager.$showSectionDividers\n                .receive(on: DispatchQueue.main)\n                .sink { [weak self] shouldShow in\n                    guard\n                        let self,\n                        isSectionDivider,\n                        state == .showItems\n                    else {\n                        return\n                    }\n                    isVisible = shouldShow\n                }\n                .store(in: &c)\n\n            appState.settingsManager.advancedSettingsManager.$enableAlwaysHiddenSection\n                .receive(on: DispatchQueue.main)\n                .sink { [weak self] enable in\n                    guard\n                        let self,\n                        identifier == .alwaysHidden\n                    else {\n                        return\n                    }\n                    if enable {\n                        addToMenuBar()\n                    } else {\n                        removeFromMenuBar()\n                    }\n                }\n                .store(in: &c)\n        }\n\n        cancellables = c\n    }\n\n    /// Sets the initial configuration for the status item.\n    private func configureStatusItem() {\n        defer {\n            configureCancellables()\n            updateStatusItem(with: state)\n        }\n        guard let button = statusItem.button else {\n            return\n        }\n        button.target = self\n        button.action = #selector(performAction)\n    }\n\n    /// Updates the appearance of the status item using the given hiding state.\n    private func updateStatusItem(with state: HidingState) {\n        guard\n            let appState,\n            let section,\n            let button = statusItem.button\n        else {\n            return\n        }\n\n        switch section.name {\n        case .visible:\n            isVisible = true\n            // Enable the cell, as it may have been previously disabled.\n            button.cell?.isEnabled = true\n            let icon = appState.settingsManager.generalSettingsManager.iceIcon\n            // We can usually just set the image directly from the icon.\n            button.image = switch state {\n            case .hideItems: icon.hidden.nsImage(for: appState)\n            case .showItems: icon.visible.nsImage(for: appState)\n            }\n            if\n                case .custom = icon.name,\n                let originalImage = button.image\n            {\n                // Custom icons need to be resized to fit inside the button.\n                let originalWidth = originalImage.size.width\n                let originalHeight = originalImage.size.height\n                let ratio = max(originalWidth / 25, originalHeight / 17)\n                let newSize = CGSize(width: originalWidth / ratio, height: originalHeight / ratio)\n                button.image = originalImage.resized(to: newSize)\n            }\n        case .hidden, .alwaysHidden:\n            switch state {\n            case .hideItems:\n                isVisible = true\n                // Prevent the cell from highlighting while expanded.\n                button.cell?.isEnabled = false\n                // Cell still sometimes briefly flashes on expansion unless manually unhighlighted.\n                button.isHighlighted = false\n                button.image = nil\n            case .showItems:\n                isVisible = appState.settingsManager.advancedSettingsManager.showSectionDividers\n                // Enable the cell, as it may have been previously disabled.\n                button.cell?.isEnabled = true\n                // Set the image based on the section name and the hiding state.\n                switch section.name {\n                case .hidden:\n                    button.image = ControlItemImage.builtin(.chevronLarge).nsImage(for: appState)\n                case .alwaysHidden:\n                    button.image = ControlItemImage.builtin(.chevronSmall).nsImage(for: appState)\n                case .visible: break\n                }\n            }\n        }\n    }\n\n    /// Performs the control item's action.\n    @objc private func performAction() {\n        guard\n            let appState,\n            let event = NSApp.currentEvent\n        else {\n            return\n        }\n        switch event.type {\n        case .leftMouseDown, .leftMouseUp:\n            if NSEvent.modifierFlags == .control {\n                statusItem.showMenu(createMenu(with: appState))\n            } else if\n                NSEvent.modifierFlags == .option,\n                appState.settingsManager.advancedSettingsManager.canToggleAlwaysHiddenSection\n            {\n                if let alwaysHiddenSection = appState.menuBarManager.section(withName: .alwaysHidden) {\n                    alwaysHiddenSection.toggle()\n                }\n            } else {\n                section?.toggle()\n            }\n        case .rightMouseUp:\n            statusItem.showMenu(createMenu(with: appState))\n        default:\n            break\n        }\n    }\n\n    /// Creates a menu to show under the control item.\n    private func createMenu(with appState: AppState) -> NSMenu {\n        func hotkey(withAction action: HotkeyAction) -> Hotkey? {\n            let hotkeySettingsManager = appState.settingsManager.hotkeySettingsManager\n            return hotkeySettingsManager.hotkey(withAction: action)\n        }\n\n        let menu = NSMenu(title: \"Ice\")\n\n        let settingsItem = NSMenuItem(\n            title: \"Ice Settings…\",\n            action: #selector(AppDelegate.openSettingsWindow),\n            keyEquivalent: \",\"\n        )\n        settingsItem.keyEquivalentModifierMask = .command\n        menu.addItem(settingsItem)\n\n        menu.addItem(.separator())\n\n        let searchItem = NSMenuItem(\n            title: \"Search Menu Bar Items\",\n            action: #selector(showSearchPanel),\n            keyEquivalent: \"\"\n        )\n        searchItem.target = self\n        if\n            let hotkey = hotkey(withAction: .searchMenuBarItems),\n            let keyCombination = hotkey.keyCombination\n        {\n            searchItem.keyEquivalent = keyCombination.key.keyEquivalent\n            searchItem.keyEquivalentModifierMask = keyCombination.modifiers.nsEventFlags\n        }\n        menu.addItem(searchItem)\n\n        menu.addItem(.separator())\n\n        // Add menu items to toggle the hidden and always-hidden sections.\n        let sectionNames: [MenuBarSection.Name] = [.hidden, .alwaysHidden]\n        for name in sectionNames {\n            guard\n                let section = appState.menuBarManager.section(withName: name),\n                section.controlItem.isAddedToMenuBar\n            else {\n                // Section doesn't exist, or is disabled.\n                continue\n            }\n            let item = NSMenuItem(\n                title: \"\\(section.isHidden ? \"Show\" : \"Hide\") the \\(name.displayString) Section\",\n                action: #selector(toggleMenuBarSection),\n                keyEquivalent: \"\"\n            )\n            item.target = self\n            Self.sectionStorage.weakSet(section, for: item)\n            switch name {\n            case .visible:\n                break\n            case .hidden:\n                if\n                    let hotkey = hotkey(withAction: .toggleHiddenSection),\n                    let keyCombination = hotkey.keyCombination\n                {\n                    item.keyEquivalent = keyCombination.key.keyEquivalent\n                    item.keyEquivalentModifierMask = keyCombination.modifiers.nsEventFlags\n                }\n            case .alwaysHidden:\n                if\n                    let hotkey = hotkey(withAction: .toggleAlwaysHiddenSection),\n                    let keyCombination = hotkey.keyCombination\n                {\n                    item.keyEquivalent = keyCombination.key.keyEquivalent\n                    item.keyEquivalentModifierMask = keyCombination.modifiers.nsEventFlags\n                }\n            }\n            menu.addItem(item)\n        }\n\n        menu.addItem(.separator())\n\n        let checkForUpdatesItem = NSMenuItem(\n            title: \"Check for Updates…\",\n            action: #selector(checkForUpdates),\n            keyEquivalent: \"\"\n        )\n        checkForUpdatesItem.target = self\n        menu.addItem(checkForUpdatesItem)\n\n        menu.addItem(.separator())\n\n        let quitItem = NSMenuItem(\n            title: \"Quit Ice\",\n            action: #selector(NSApp.terminate),\n            keyEquivalent: \"q\"\n        )\n        quitItem.keyEquivalentModifierMask = .command\n        menu.addItem(quitItem)\n\n        return menu\n    }\n\n    /// Toggles the menu bar section associated with the given menu item.\n    @objc private func toggleMenuBarSection(for menuItem: NSMenuItem) {\n        Self.sectionStorage.value(for: menuItem)?.toggle()\n    }\n\n    /// Opens the menu bar search panel.\n    @objc private func showSearchPanel() {\n        guard\n            let appState,\n            let screen = MenuBarSearchPanel.defaultScreen\n        else {\n            return\n        }\n        Task {\n            await appState.menuBarManager.searchPanel.show(on: screen)\n        }\n    }\n\n    /// Opens the settings window and checks for app updates.\n    @objc private func checkForUpdates() {\n        guard let appState else {\n            return\n        }\n        appState.updatesManager.checkForUpdates()\n    }\n\n    /// Adds the control item to the menu bar.\n    func addToMenuBar() {\n        guard !isAddedToMenuBar else {\n            return\n        }\n        statusItem.isVisible = true\n    }\n\n    /// Removes the control item from the menu bar.\n    func removeFromMenuBar() {\n        guard isAddedToMenuBar else {\n            return\n        }\n        // Setting `statusItem.isVisible` to `false` has the unwanted side\n        // effect of deleting the preferredPosition. Cache and restore it.\n        let autosaveName = statusItem.autosaveName as String\n        let cached = StatusItemDefaults[.preferredPosition, autosaveName]\n        statusItem.isVisible = false\n        StatusItemDefaults[.preferredPosition, autosaveName] = cached\n    }\n}\n\nprivate extension ControlItem {\n    /// Storage for menu items that toggle a menu bar section.\n    ///\n    /// When one of these menu items is created, its section is stored here.\n    /// When its action is invoked, the section is retrieved from storage.\n    static let sectionStorage = ObjectStorage<MenuBarSection>()\n}\n\n// MARK: - Logger\nprivate extension Logger {\n    /// The logger to use for control items.\n    static let controlItem = Logger(category: \"ControlItem\")\n}\n"
  },
  {
    "path": "Ice/MenuBar/ControlItem/ControlItemImage.swift",
    "content": "//\n//  ControlItemImage.swift\n//  Ice\n//\n\nimport Cocoa\n\n/// A Codable image for a control item.\nenum ControlItemImage: Codable, Hashable {\n    /// An image created from drawing code built into the app.\n    case builtin(_ name: ImageBuiltinName)\n    /// A system symbol image.\n    case symbol(_ name: String)\n    /// An image in an asset catalog.\n    case catalog(_ name: String)\n    /// An image stored as data.\n    case data(_ data: Data)\n\n    /// A Cocoa representation of this image.\n    @MainActor\n    func nsImage(for appState: AppState) -> NSImage? {\n        switch self {\n        case .builtin(let name):\n            return switch name {\n            case .chevronLarge: StaticBuiltins.Chevron.large\n            case .chevronSmall: StaticBuiltins.Chevron.small\n            }\n        case .symbol(let name):\n            let image = NSImage(systemSymbolName: name, accessibilityDescription: nil)\n            image?.isTemplate = true\n            return image\n        case .catalog(let name):\n            guard let originalImage = NSImage(named: name) else {\n                return nil\n            }\n            let originalWidth = originalImage.size.width\n            let originalHeight = originalImage.size.height\n            let ratio = max(originalWidth / 25, originalHeight / 17)\n            let newSize = CGSize(width: originalWidth / ratio, height: originalHeight / ratio)\n            return originalImage.resized(to: newSize)\n        case .data(let data):\n            let image = NSImage(data: data)\n            let generalSettingsManager = appState.settingsManager.generalSettingsManager\n            image?.isTemplate = generalSettingsManager.customIceIconIsTemplate\n            return image\n        }\n    }\n}\n\nextension ControlItemImage {\n    /// A name for an image that is created from drawing code in the app.\n    enum ImageBuiltinName: Codable, Hashable {\n        /// A large chevron.\n        case chevronLarge\n        /// A small chevron.\n        case chevronSmall\n    }\n}\n\nextension ControlItemImage {\n    /// A namespace for static builtin images.\n    ///\n    /// - Note: We use the static properties `large` and `small` to avoid repeatedly\n    ///   executing code every time ``nsImage(for:)`` is called.\n    private enum StaticBuiltins {\n        /// A namespace for static builtin chevron images.\n        enum Chevron {\n            /// Creates a chevron image with the given size and line width.\n            private static func chevron(size: CGSize, lineWidth: CGFloat) -> NSImage {\n                let image = NSImage(size: size, flipped: false) { bounds in\n                    let insetBounds = bounds.insetBy(dx: lineWidth / 2, dy: lineWidth / 2)\n                    let path = NSBezierPath()\n                    path.move(to: CGPoint(x: (insetBounds.midX + insetBounds.maxX) / 2, y: insetBounds.maxY))\n                    path.line(to: CGPoint(x: (insetBounds.minX + insetBounds.midX) / 2, y: insetBounds.midY))\n                    path.line(to: CGPoint(x: (insetBounds.midX + insetBounds.maxX) / 2, y: insetBounds.minY))\n                    path.lineWidth = lineWidth\n                    path.lineCapStyle = .butt\n                    NSColor.black.setStroke()\n                    path.stroke()\n                    return true\n                }\n                image.isTemplate = true\n                return image\n            }\n\n            /// A large chevron.\n            static let large = chevron(size: CGSize(width: 12, height: 12), lineWidth: 2)\n\n            /// A small chevron.\n            static let small = chevron(size: CGSize(width: 9, height: 9), lineWidth: 2)\n        }\n    }\n}\n"
  },
  {
    "path": "Ice/MenuBar/ControlItem/ControlItemImageSet.swift",
    "content": "//\n//  ControlItemImageSet.swift\n//  Ice\n//\n\n/// A named set of images that are used by control items.\n///\n/// An image set contains images for a control item in both the hidden and visible states.\nstruct ControlItemImageSet: Codable, Hashable, Identifiable {\n    enum Name: String, Codable, Hashable {\n        case arrow = \"Arrow\"\n        case chevron = \"Chevron\"\n        case door = \"Door\"\n        case dot = \"Dot\"\n        case ellipsis = \"Ellipsis\"\n        case iceCube = \"Ice Cube\"\n        case sunglasses = \"Sunglasses\"\n        case custom = \"Custom\"\n    }\n\n    let name: Name\n    let hidden: ControlItemImage\n    let visible: ControlItemImage\n\n    var id: Int { hashValue }\n\n    init(name: Name, hidden: ControlItemImage, visible: ControlItemImage) {\n        self.name = name\n        self.hidden = hidden\n        self.visible = visible\n    }\n\n    init(name: Name, image: ControlItemImage) {\n        self.init(name: name, hidden: image, visible: image)\n    }\n}\n\nextension ControlItemImageSet {\n    /// The default image set for the Ice icon.\n    static let defaultIceIcon = ControlItemImageSet(\n        name: .dot,\n        hidden: .catalog(\"DotFill\"),\n        visible: .catalog(\"DotStroke\")\n    )\n\n    /// The image sets that the user can choose to display in the Ice icon.\n    static let userSelectableIceIcons = [\n        ControlItemImageSet(\n            name: .arrow,\n            hidden: .symbol(\"arrowshape.left.fill\"),\n            visible: .symbol(\"arrowshape.right.fill\")\n        ),\n        ControlItemImageSet(\n            name: .chevron,\n            hidden: .symbol(\"chevron.left\"),\n            visible: .symbol(\"chevron.right\")\n        ),\n        ControlItemImageSet(\n            name: .door,\n            hidden: .symbol(\"door.left.hand.closed\"),\n            visible: .symbol(\"door.left.hand.open\")\n        ),\n        ControlItemImageSet(\n            name: .dot,\n            hidden: .catalog(\"DotFill\"),\n            visible: .catalog(\"DotStroke\")\n        ),\n        ControlItemImageSet(\n            name: .ellipsis,\n            hidden: .catalog(\"EllipsisFill\"),\n            visible: .catalog(\"EllipsisStroke\")\n        ),\n        ControlItemImageSet(\n            name: .iceCube,\n            hidden: .catalog(\"IceCubeStroke\"),\n            visible: .catalog(\"IceCubeFill\")\n        ),\n        ControlItemImageSet(\n            name: .sunglasses,\n            hidden: .symbol(\"sunglasses.fill\"),\n            visible: .symbol(\"sunglasses\")\n        ),\n    ]\n}\n"
  },
  {
    "path": "Ice/MenuBar/MenuBarItems/MenuBarItem.swift",
    "content": "//\n//  MenuBarItem.swift\n//  Ice\n//\n\nimport Cocoa\n\n// MARK: - MenuBarItem\n\n/// A representation of an item in the menu bar.\nstruct MenuBarItem {\n    /// The item's window.\n    let window: WindowInfo\n\n    /// The menu bar item info associated with this item.\n    let info: MenuBarItemInfo\n\n    /// The identifier of the item's window.\n    var windowID: CGWindowID {\n        window.windowID\n    }\n\n    /// The frame of the item's window.\n    var frame: CGRect {\n        window.frame\n    }\n\n    /// The title of the item's window.\n    var title: String? {\n        window.title\n    }\n\n    /// A Boolean value that indicates whether the item is on screen.\n    var isOnScreen: Bool {\n        window.isOnScreen\n    }\n\n    /// A Boolean value that indicates whether the item can be moved.\n    var isMovable: Bool {\n        let immovableItems = Set(MenuBarItemInfo.immovableItems)\n        return !immovableItems.contains(info)\n    }\n\n    /// A Boolean value that indicates whether the item can be hidden.\n    var canBeHidden: Bool {\n        let nonHideableItems = Set(MenuBarItemInfo.nonHideableItems)\n        return !nonHideableItems.contains(info)\n    }\n\n    /// The process identifier of the application that owns the item.\n    var ownerPID: pid_t {\n        window.ownerPID\n    }\n\n    /// The name of the application that owns the item.\n    ///\n    /// This may have a value when ``owningApplication`` does not have\n    /// a localized name.\n    var ownerName: String? {\n        window.ownerName\n    }\n\n    /// The application that owns the item.\n    var owningApplication: NSRunningApplication? {\n        window.owningApplication\n    }\n\n    /// A name associated with the item that is suited for display to\n    /// the user.\n    var displayName: String {\n        var fallback: String { \"Unknown\" }\n        guard let owningApplication else {\n            return ownerName ?? title ?? fallback\n        }\n        var bestName: String {\n            owningApplication.localizedName ??\n            ownerName ??\n            owningApplication.bundleIdentifier ??\n            fallback\n        }\n        guard let title else {\n            return bestName\n        }\n        // by default, use the application name, but handle a few special cases\n        return switch MenuBarItemInfo.Namespace(owningApplication.bundleIdentifier) {\n        case .controlCenter:\n            switch title {\n            case \"AccessibilityShortcuts\": \"Accessibility Shortcuts\"\n            case \"BentoBox\": bestName // Control Center\n            case \"FocusModes\": \"Focus\"\n            case \"KeyboardBrightness\": \"Keyboard Brightness\"\n            case \"MusicRecognition\": \"Music Recognition\"\n            case \"NowPlaying\": \"Now Playing\"\n            case \"ScreenMirroring\": \"Screen Mirroring\"\n            case \"StageManager\": \"Stage Manager\"\n            case \"UserSwitcher\": \"Fast User Switching\"\n            case \"WiFi\": \"Wi-Fi\"\n            default: title\n            }\n        case .systemUIServer:\n            switch title {\n            case \"TimeMachine.TMMenuExtraHost\"/*Sonoma*/, \"TimeMachineMenuExtra.TMMenuExtraHost\"/*Sequoia*/: \"Time Machine\"\n            default: title\n            }\n        case MenuBarItemInfo.Namespace(\"com.apple.Passwords.MenuBarExtra\"): \"Passwords\"\n        default:\n            bestName\n        }\n    }\n\n    /// A Boolean value that indicates whether the item is currently\n    /// in the menu bar.\n    var isCurrentlyInMenuBar: Bool {\n        let list = Set(Bridging.getWindowList(option: .menuBarItems))\n        return list.contains(windowID)\n    }\n\n    /// A string to use for logging purposes.\n    var logString: String {\n        String(describing: info)\n    }\n\n    /// Creates a menu bar item from the given window.\n    ///\n    /// This initializer does not perform any checks on the window to ensure that\n    /// it is a valid menu bar item window. Only call this initializer if you are\n    /// certain that the window is valid.\n    private init(uncheckedItemWindow itemWindow: WindowInfo) {\n        self.window = itemWindow\n        self.info = MenuBarItemInfo(uncheckedItemWindow: itemWindow)\n    }\n\n    /// Creates a menu bar item.\n    ///\n    /// The parameters passed into this initializer are verified during the menu\n    /// bar item's creation. If `itemWindow` does not represent a menu bar item,\n    /// the initializer will fail.\n    ///\n    /// - Parameter itemWindow: A window that contains information about the item.\n    init?(itemWindow: WindowInfo) {\n        guard itemWindow.isMenuBarItem else {\n            return nil\n        }\n        self.init(uncheckedItemWindow: itemWindow)\n    }\n\n    /// Creates a menu bar item with the given window identifier.\n    ///\n    /// The parameters passed into this initializer are verified during the menu\n    /// bar item's creation. If `windowID` does not represent a menu bar item,\n    /// the initializer will fail.\n    ///\n    /// - Parameter windowID: An identifier for a window that contains information\n    ///   about the item.\n    init?(windowID: CGWindowID) {\n        guard let window = WindowInfo(windowID: windowID) else {\n            return nil\n        }\n        self.init(itemWindow: window)\n    }\n}\n\n// MARK: MenuBarItem Getters\nextension MenuBarItem {\n    /// Returns an array of the current menu bar items in the menu bar on the given display.\n    ///\n    /// - Parameters:\n    ///   - display: The display to retrieve the menu bar items on. Pass `nil` to return the\n    ///     menu bar items across all displays.\n    ///   - onScreenOnly: A Boolean value that indicates whether only the menu bar items that\n    ///     are on screen should be returned.\n    ///   - activeSpaceOnly: A Boolean value that indicates whether only the menu bar items\n    ///     that are on the active space should be returned.\n    static func getMenuBarItems(on display: CGDirectDisplayID? = nil, onScreenOnly: Bool, activeSpaceOnly: Bool) -> [MenuBarItem] {\n        var option: Bridging.WindowListOption = [.menuBarItems]\n\n        var titlePredicate: (MenuBarItem) -> Bool = { _ in true }\n        var boundsPredicate: (CGWindowID) -> Bool = { _ in true }\n\n        if onScreenOnly {\n            option.insert(.onScreen)\n        }\n        if activeSpaceOnly {\n            option.insert(.activeSpace)\n            titlePredicate = { $0.title != \"\" }\n        }\n        if let display {\n            let displayBounds = CGDisplayBounds(display)\n            boundsPredicate = { windowID in\n                guard let windowFrame = Bridging.getWindowFrame(for: windowID) else {\n                    return false\n                }\n                return displayBounds.intersects(windowFrame)\n            }\n        }\n\n        return Bridging.getWindowList(option: option).lazy\n            .filter(boundsPredicate)\n            .compactMap { windowID in\n                MenuBarItem(windowID: windowID)\n            }\n            .filter(titlePredicate)\n            .sortedByOrderInMenuBar()\n    }\n}\n\n// MARK: MenuBarItem: Equatable\nextension MenuBarItem: Equatable {\n    static func == (lhs: MenuBarItem, rhs: MenuBarItem) -> Bool {\n        lhs.window == rhs.window\n    }\n}\n\n// MARK: MenuBarItem: Hashable\nextension MenuBarItem: Hashable {\n    func hash(into hasher: inout Hasher) {\n        hasher.combine(window)\n    }\n}\n\n// MARK: MenuBarItemInfo Unchecked Item Window Initializer\nprivate extension MenuBarItemInfo {\n    /// Creates a simplified item from the given window.\n    ///\n    /// This initializer does not perform any checks on the window to ensure that\n    /// it is a valid menu bar item window. Only call this initializer if you are\n    /// certain that the window is valid.\n    init(uncheckedItemWindow itemWindow: WindowInfo) {\n        if let bundleIdentifier = itemWindow.owningApplication?.bundleIdentifier {\n            self.namespace = Namespace(bundleIdentifier)\n        } else {\n            self.namespace = .null\n        }\n        if let title = itemWindow.title {\n            self.title = title\n        } else {\n            self.title = \"\"\n        }\n    }\n}\n"
  },
  {
    "path": "Ice/MenuBar/MenuBarItems/MenuBarItemImageCache.swift",
    "content": "//\n//  MenuBarItemImageCache.swift\n//  Ice\n//\n\nimport Cocoa\nimport Combine\n\n/// Cache for menu bar item images.\nfinal class MenuBarItemImageCache: ObservableObject {\n    /// The cached item images.\n    @Published private(set) var images = [MenuBarItemInfo: CGImage]()\n\n    /// The screen of the cached item images.\n    private(set) var screen: NSScreen?\n\n    /// The height of the menu bar of the cached item images.\n    private(set) var menuBarHeight: CGFloat?\n\n    /// The shared app state.\n    private weak var appState: AppState?\n\n    /// Storage for internal observers.\n    private var cancellables = Set<AnyCancellable>()\n\n    /// Creates a cache with the given app state.\n    init(appState: AppState) {\n        self.appState = appState\n    }\n\n    /// Sets up the cache.\n    @MainActor\n    func performSetup() {\n        configureCancellables()\n    }\n\n    /// Configures the internal observers for the cache.\n    @MainActor\n    private func configureCancellables() {\n        var c = Set<AnyCancellable>()\n\n        if let appState {\n            Publishers.Merge3(\n                // Update every 3 seconds at minimum.\n                Timer.publish(every: 3, on: .main, in: .default).autoconnect().mapToVoid(),\n\n                // Update when the active space or screen parameters change.\n                Publishers.Merge(\n                    NSWorkspace.shared.notificationCenter.publisher(for: NSWorkspace.activeSpaceDidChangeNotification),\n                    NotificationCenter.default.publisher(for: NSApplication.didChangeScreenParametersNotification)\n                )\n                .mapToVoid(),\n\n                // Update when the average menu bar color or cached items change.\n                Publishers.Merge(\n                    appState.menuBarManager.$averageColorInfo.removeDuplicates().mapToVoid(),\n                    appState.itemManager.$itemCache.removeDuplicates().mapToVoid()\n                )\n            )\n            .throttle(for: 0.5, scheduler: DispatchQueue.main, latest: false)\n            .sink { [weak self] in\n                guard let self else {\n                    return\n                }\n                Task.detached {\n                    if ScreenCapture.cachedCheckPermissions() {\n                        await self.updateCache()\n                    }\n                }\n            }\n            .store(in: &c)\n        }\n\n        cancellables = c\n    }\n\n    /// Logs a reason for skipping the cache.\n    private func logSkippingCache(reason: String) {\n        Logger.imageCache.debug(\"Skipping menu bar item image cache as \\(reason)\")\n    }\n\n    /// Returns a Boolean value that indicates whether caching menu bar items failed for\n    /// the given section.\n    @MainActor\n    func cacheFailed(for section: MenuBarSection.Name) -> Bool {\n        guard ScreenCapture.cachedCheckPermissions() else {\n            return true\n        }\n        let items = appState?.itemManager.itemCache[section] ?? []\n        guard !items.isEmpty else {\n            return false\n        }\n        let keys = Set(images.keys)\n        for item in items where keys.contains(item.info) {\n            return false\n        }\n        return true\n    }\n\n    /// Captures the images of the current menu bar items and returns a dictionary containing\n    /// the images, keyed by the current menu bar item infos.\n    func createImages(for section: MenuBarSection.Name, screen: NSScreen) async -> [MenuBarItemInfo: CGImage] {\n        guard let appState else {\n            return [:]\n        }\n\n        let items = await appState.itemManager.itemCache[section]\n\n        var images = [MenuBarItemInfo: CGImage]()\n        let backingScaleFactor = screen.backingScaleFactor\n        let displayBounds = CGDisplayBounds(screen.displayID)\n        let option: CGWindowImageOption = [.boundsIgnoreFraming, .bestResolution]\n        let defaultItemThickness = NSStatusBar.system.thickness * backingScaleFactor\n\n        var itemInfos = [CGWindowID: MenuBarItemInfo]()\n        var itemFrames = [CGWindowID: CGRect]()\n        var windowIDs = [CGWindowID]()\n        var frame = CGRect.null\n\n        for item in items {\n            let windowID = item.windowID\n            guard\n                // Use the most up-to-date window frame.\n                let itemFrame = Bridging.getWindowFrame(for: windowID),\n                itemFrame.minY == displayBounds.minY\n            else {\n                continue\n            }\n            itemInfos[windowID] = item.info\n            itemFrames[windowID] = itemFrame\n            windowIDs.append(windowID)\n            frame = frame.union(itemFrame)\n        }\n\n        if\n            let compositeImage = ScreenCapture.captureWindows(windowIDs, option: option),\n            CGFloat(compositeImage.width) == frame.width * backingScaleFactor\n        {\n            for windowID in windowIDs {\n                guard\n                    let itemInfo = itemInfos[windowID],\n                    let itemFrame = itemFrames[windowID]\n                else {\n                    continue\n                }\n\n                let frame = CGRect(\n                    x: (itemFrame.origin.x - frame.origin.x) * backingScaleFactor,\n                    y: (itemFrame.origin.y - frame.origin.y) * backingScaleFactor,\n                    width: itemFrame.width * backingScaleFactor,\n                    height: itemFrame.height * backingScaleFactor\n                )\n\n                guard let itemImage = compositeImage.cropping(to: frame) else {\n                    continue\n                }\n\n                images[itemInfo] = itemImage\n            }\n        } else {\n            Logger.imageCache.warning(\"Composite image capture failed. Attempting to capturing items individually.\")\n\n            for windowID in windowIDs {\n                guard\n                    let itemInfo = itemInfos[windowID],\n                    let itemFrame = itemFrames[windowID]\n                else {\n                    continue\n                }\n\n                let frame = CGRect(\n                    x: 0,\n                    y: ((itemFrame.height * backingScaleFactor) / 2) - (defaultItemThickness / 2),\n                    width: itemFrame.width * backingScaleFactor,\n                    height: defaultItemThickness\n                )\n\n                guard\n                    let itemImage = ScreenCapture.captureWindow(windowID, option: option),\n                    let croppedImage = itemImage.cropping(to: frame)\n                else {\n                    continue\n                }\n\n                images[itemInfo] = croppedImage\n            }\n        }\n\n        return images\n    }\n\n    /// Updates the cache for the given sections, without checking whether caching is necessary.\n    func updateCacheWithoutChecks(sections: [MenuBarSection.Name]) async {\n        guard\n            let appState,\n            let screen = NSScreen.main\n        else {\n            return\n        }\n\n        var newImages = [MenuBarItemInfo: CGImage]()\n\n        for section in sections {\n            guard await !appState.itemManager.itemCache[section].isEmpty else {\n                continue\n            }\n            let sectionImages = await createImages(for: section, screen: screen)\n            guard !sectionImages.isEmpty else {\n                Logger.imageCache.warning(\"Update image cache failed for \\(section.logString)\")\n                continue\n            }\n            newImages.merge(sectionImages) { (_, new) in new }\n        }\n\n        await MainActor.run { [newImages] in\n            images.merge(newImages) { (_, new) in new }\n        }\n\n        self.screen = screen\n        self.menuBarHeight = screen.getMenuBarHeight()\n    }\n\n    /// Updates the cache for the given sections, if necessary.\n    func updateCache(sections: [MenuBarSection.Name]) async {\n        guard let appState else {\n            return\n        }\n\n        let isIceBarPresented = await appState.navigationState.isIceBarPresented\n        let isSearchPresented = await appState.navigationState.isSearchPresented\n\n        if !isIceBarPresented && !isSearchPresented {\n            guard await appState.navigationState.isAppFrontmost else {\n                logSkippingCache(reason: \"Ice Bar not visible, app not frontmost\")\n                return\n            }\n            guard await appState.navigationState.isSettingsPresented else {\n                logSkippingCache(reason: \"Ice Bar not visible, Settings not visible\")\n                return\n            }\n            guard case .menuBarLayout = await appState.navigationState.settingsNavigationIdentifier else {\n                logSkippingCache(reason: \"Ice Bar not visible, Settings visible but not on Menu Bar Layout\")\n                return\n            }\n        }\n\n        guard await !appState.itemManager.isMovingItem else {\n            logSkippingCache(reason: \"an item is currently being moved\")\n            return\n        }\n\n        guard await !appState.itemManager.itemHasRecentlyMoved else {\n            logSkippingCache(reason: \"an item was recently moved\")\n            return\n        }\n\n        await updateCacheWithoutChecks(sections: sections)\n    }\n\n    /// Updates the cache for all sections, if necessary.\n    func updateCache() async {\n        guard let appState else {\n            return\n        }\n\n        let isIceBarPresented = await appState.navigationState.isIceBarPresented\n        let isSearchPresented = await appState.navigationState.isSearchPresented\n        let isSettingsPresented = await appState.navigationState.isSettingsPresented\n\n        var sectionsNeedingDisplay = [MenuBarSection.Name]()\n        if isSettingsPresented || isSearchPresented {\n            sectionsNeedingDisplay = MenuBarSection.Name.allCases\n        } else if\n            isIceBarPresented,\n            let section = await appState.menuBarManager.iceBarPanel.currentSection\n        {\n            sectionsNeedingDisplay.append(section)\n        }\n\n        await updateCache(sections: sectionsNeedingDisplay)\n    }\n}\n\n// MARK: - Logger\n\nprivate extension Logger {\n    static let imageCache = Logger(category: \"MenuBarItemImageCache\")\n}\n"
  },
  {
    "path": "Ice/MenuBar/MenuBarItems/MenuBarItemInfo.swift",
    "content": "//\n//  MenuBarItemInfo.swift\n//  Ice\n//\n\n/// A simplified version of a menu bar item.\nstruct MenuBarItemInfo: Hashable, CustomStringConvertible {\n    /// The namespace of the item.\n    let namespace: Namespace\n\n    /// The title of the item.\n    let title: String\n\n    /// A Boolean value that indicates whether the item is within the\n    /// \"Special\" namespace.\n    var isSpecial: Bool {\n        namespace == .special\n    }\n\n    var description: String {\n        namespace.rawValue + \":\" + title\n    }\n\n    /// Creates a simplified item with the given namespace and title.\n    init(namespace: Namespace, title: String) {\n        self.namespace = namespace\n        self.title = title\n    }\n}\n\n// MARK: MenuBarItemInfo Constants\nextension MenuBarItemInfo {\n    /// An array of items whose movement is prevented by macOS.\n    static let immovableItems = [clock, siri, controlCenter]\n\n    /// An array of items that can be moved, but cannot be hidden.\n    static let nonHideableItems = [audioVideoModule, faceTime, musicRecognition]\n\n    /// Information for an item that represents the Ice icon, a.k.a. the\n    /// control item for the visible section.\n    static let iceIcon = MenuBarItemInfo(\n        namespace: .ice,\n        title: ControlItem.Identifier.iceIcon.rawValue\n    )\n\n    /// Information for an item that represents the control item for the\n    /// hidden section.\n    static let hiddenControlItem = MenuBarItemInfo(\n        namespace: .ice,\n        title: ControlItem.Identifier.hidden.rawValue\n    )\n\n    /// Information for an item that represents the control item for the\n    /// always-hidden section.\n    static let alwaysHiddenControlItem = MenuBarItemInfo(\n        namespace: .ice,\n        title: ControlItem.Identifier.alwaysHidden.rawValue\n    )\n\n    /// Information for the \"Clock\" item.\n    static let clock = MenuBarItemInfo(\n        namespace: .controlCenter,\n        title: \"Clock\"\n    )\n\n    /// Information for the \"Siri\" item.\n    static let siri = MenuBarItemInfo(\n        namespace: .systemUIServer,\n        title: \"Siri\"\n    )\n\n    /// Information for the \"BentoBox\" (a.k.a. \"Control Center\") item.\n    static let controlCenter = MenuBarItemInfo(\n        namespace: .controlCenter,\n        title: \"BentoBox\"\n    )\n\n    /// Information for the item that appears in the menu bar while the\n    /// screen or system audio is being recorded.\n    static let audioVideoModule = MenuBarItemInfo(\n        namespace: .controlCenter,\n        title: \"AudioVideoModule\"\n    )\n\n    /// Information for the \"FaceTime\" item.\n    static let faceTime = MenuBarItemInfo(\n        namespace: .controlCenter,\n        title: \"FaceTime\"\n    )\n\n    /// Information for the \"MusicRecognition\" (a.k.a. \"Shazam\") item.\n    static let musicRecognition = MenuBarItemInfo(\n        namespace: .controlCenter,\n        title: \"MusicRecognition\"\n    )\n\n    /// Information for a special item that indicates the location where\n    /// new menu bar items should appear.\n    static let newItems = MenuBarItemInfo(\n        namespace: .special,\n        title: \"NewItems\"\n    )\n}\n\n// MARK: MenuBarItemInfo: Codable\nextension MenuBarItemInfo: Codable {\n    init(from decoder: any Decoder) throws {\n        let container = try decoder.singleValueContainer()\n        let string = try container.decode(String.self)\n        let components = string.components(separatedBy: \":\")\n        let count = components.count\n        if count > 2 {\n            self.namespace = Namespace(components[0])\n            self.title = components[1...].joined(separator: \":\")\n        } else if count == 2 {\n            self.namespace = Namespace(components[0])\n            self.title = components[1]\n        } else if count == 1 {\n            self.namespace = Namespace(components[0])\n            self.title = \"\"\n        } else {\n            throw DecodingError.dataCorrupted(\n                DecodingError.Context(\n                    codingPath: container.codingPath,\n                    debugDescription: \"Missing namespace component\"\n                )\n            )\n        }\n    }\n\n    func encode(to encoder: any Encoder) throws {\n        var container = encoder.singleValueContainer()\n        try container.encode([namespace.rawValue, title].joined(separator: \":\"))\n    }\n}\n\n// MARK: - MenuBarItemInfo.Namespace\n\nextension MenuBarItemInfo {\n    /// A type that represents a menu bar item namespace.\n    struct Namespace: Codable, Hashable, RawRepresentable, CustomStringConvertible {\n        /// Private representation of a namespace.\n        private enum Kind {\n            case null\n            case rawValue(String)\n        }\n\n        /// The private representation of the namespace.\n        private let kind: Kind\n\n        /// The namespace's raw value.\n        var rawValue: String {\n            switch kind {\n            case .null: \"<null>\"\n            case .rawValue(let rawValue): rawValue\n            }\n        }\n\n        /// A textual representation of the namespace.\n        var description: String {\n            rawValue\n        }\n\n        /// An Optional representation of the namespace that converts the ``null``\n        /// namespace to `nil`.\n        var optional: Namespace? {\n            switch kind {\n            case .null: nil\n            case .rawValue: self\n            }\n        }\n\n        /// Creates a namespace with the given private representation.\n        private init(kind: Kind) {\n            self.kind = kind\n        }\n\n        /// Creates a namespace with the given raw value.\n        ///\n        /// - Parameter rawValue: The raw value of the namespace.\n        init(rawValue: String) {\n            self.init(kind: .rawValue(rawValue))\n        }\n\n        /// Creates a namespace with the given raw value.\n        ///\n        /// - Parameter rawValue: The raw value of the namespace.\n        init(_ rawValue: String) {\n            self.init(rawValue: rawValue)\n        }\n\n        /// Creates a namespace with the given optional value.\n        ///\n        /// If the provided value is `nil`, the namespace is initialized to the ``null``\n        /// namespace.\n        ///\n        /// - Parameter value: An optional value to initialize the namespace with.\n        init(_ value: String?) {\n            self = value.map { Namespace($0) } ?? .null\n        }\n    }\n}\n\n// MARK: MenuBarItemInfo.Namespace Constants\nextension MenuBarItemInfo.Namespace {\n    /// The namespace for menu bar items owned by Ice.\n    static let ice = Self(Constants.bundleIdentifier)\n\n    /// The namespace for menu bar items owned by Control Center.\n    static let controlCenter = Self(\"com.apple.controlcenter\")\n\n    /// The namespace for menu bar items owned by the System UI Server.\n    static let systemUIServer = Self(\"com.apple.systemuiserver\")\n\n    /// The namespace for special items.\n    static let special = Self(\"Special\")\n\n    /// The null namespace.\n    static let null = Self(kind: .null)\n}\n"
  },
  {
    "path": "Ice/MenuBar/MenuBarItems/MenuBarItemManager.swift",
    "content": "//\n//  MenuBarItemManager.swift\n//  Ice\n//\n\nimport Cocoa\nimport Combine\n\n/// Manager for menu bar items.\n@MainActor\nfinal class MenuBarItemManager: ObservableObject {\n    /// Cache for menu bar items.\n    struct ItemCache: Hashable {\n        /// All cached menu bar items, keyed by section.\n        private var items = [MenuBarSection.Name: [MenuBarItem]]()\n\n        /// All cached menu bar items.\n        var allItems: [MenuBarItem] {\n            MenuBarSection.Name.allCases.reduce(into: []) { result, section in\n                result.append(contentsOf: self[section])\n            }\n        }\n\n        /// The cached menu bar items managed by Ice.\n        var managedItems: [MenuBarItem] {\n            MenuBarSection.Name.allCases.reduce(into: []) { result, section in\n                result.append(contentsOf: managedItems(for: section))\n            }\n        }\n\n        /// Clears the cache.\n        mutating func clear() {\n            items.removeAll()\n        }\n\n        /// Returns the cached menu bar items managed by Ice for the given section.\n        func managedItems(for section: MenuBarSection.Name) -> [MenuBarItem] {\n            self[section].filter { item in\n                // Filter out items that can't be hidden.\n                guard item.canBeHidden else {\n                    return false\n                }\n\n                if item.owningApplication == .current {\n                    // Ice icon is the only item owned by Ice that should be included.\n                    guard item.title == ControlItem.Identifier.iceIcon.rawValue else {\n                        return false\n                    }\n                }\n\n                return true\n            }\n        }\n\n        /// Returns the name of the section for the given menu bar item.\n        func section(for item: MenuBarItem) -> MenuBarSection.Name? {\n            for (section, items) in self.items where items.contains(where: { $0.info == item.info }) {\n                return section\n            }\n            return nil\n        }\n\n        /// Accesses the items in the given section.\n        subscript(section: MenuBarSection.Name) -> [MenuBarItem] {\n            get { items[section, default: []] }\n            set { items[section] = newValue }\n        }\n    }\n\n    /// Context for a temporarily shown menu bar item.\n    private struct TempShownItemContext {\n        /// The information associated with the item.\n        let info: MenuBarItemInfo\n\n        /// The destination to return the item to.\n        let returnDestination: MoveDestination\n\n        /// The window of the item's shown interface.\n        let shownInterfaceWindow: WindowInfo?\n\n        /// A Boolean value that indicates whether the menu bar item's interface is showing.\n        var isShowingInterface: Bool {\n            guard let currentWindow = shownInterfaceWindow.flatMap({ WindowInfo(windowID: $0.windowID) }) else {\n                return false\n            }\n            return if\n                currentWindow.layer != CGWindowLevelForKey(.popUpMenuWindow),\n                let owningApplication = currentWindow.owningApplication\n            {\n                owningApplication.isActive && currentWindow.isOnScreen\n            } else {\n                currentWindow.isOnScreen\n            }\n        }\n    }\n\n    /// The manager's menu bar item cache.\n    @Published private(set) var itemCache = ItemCache()\n\n    /// The shared app state.\n    private(set) weak var appState: AppState?\n\n    /// Storage for internal observers.\n    private var cancellables = Set<AnyCancellable>()\n\n    /// Cached window identifiers for the most recent items.\n    private var cachedItemWindowIDs = [CGWindowID]()\n\n    /// Context values for the current temporarily shown items.\n    private var tempShownItemContexts = [TempShownItemContext]()\n\n    /// A timer that determines when to rehide the temporarily shown items.\n    private var tempShownItemsTimer: Timer?\n\n    /// The last time a menu bar item was moved.\n    private var lastItemMoveStartDate: Date?\n\n    /// The last time the mouse was moved.\n    private var lastMouseMoveStartDate: Date?\n\n    /// Counter to determine if a menu bar item, or group of menu bar\n    /// items is being moved.\n    private var itemMoveCount = 0\n\n    /// A Boolean value that indicates whether a mouse button is down.\n    private var isMouseButtonDown = false\n\n    /// Event type mask for tracking mouse events.\n    private let mouseTrackingMask: NSEvent.EventTypeMask = [\n        .mouseMoved,\n        .leftMouseDown,\n        .rightMouseDown,\n        .otherMouseDown,\n        .leftMouseUp,\n        .rightMouseUp,\n        .otherMouseUp,\n    ]\n\n    /// A Boolean value that indicates whether a menu bar item, or\n    /// group of menu bar items is being moved.\n    var isMovingItem: Bool {\n        itemMoveCount > 0\n    }\n\n    /// A Boolean value that indicates whether a menu bar item has\n    /// recently moved.\n    var itemHasRecentlyMoved: Bool {\n        guard let lastItemMoveStartDate else {\n            return false\n        }\n        return Date.now.timeIntervalSince(lastItemMoveStartDate) <= 1\n    }\n\n    /// A Boolean value that indicates whether the mouse has recently moved.\n    var mouseHasRecentlyMoved: Bool {\n        guard let lastMouseMoveStartDate else {\n            return false\n        }\n        return Date.now.timeIntervalSince(lastMouseMoveStartDate) <= 1\n    }\n\n    /// Creates a manager with the given app state.\n    init(appState: AppState) {\n        self.appState = appState\n    }\n\n    /// Sets up the manager.\n    func performSetup() {\n        configureCancellables()\n    }\n\n    /// Configures the internal observers for the manager.\n    private func configureCancellables() {\n        var c = Set<AnyCancellable>()\n\n        Timer.publish(every: 5, on: .main, in: .default)\n            .autoconnect()\n            .merge(with: Just(.now))\n            .sink { [weak self] _ in\n                guard let self else {\n                    return\n                }\n                Task {\n                    await self.cacheItemsIfNeeded()\n                }\n            }\n            .store(in: &c)\n\n        NSWorkspace.shared.publisher(for: \\.runningApplications)\n            .delay(for: 0.25, scheduler: DispatchQueue.main)\n            .sink { [weak self] _ in\n                guard let self else {\n                    return\n                }\n                Task {\n                    await self.cacheItemsIfNeeded()\n                }\n            }\n            .store(in: &c)\n\n        Publishers.Merge(\n            UniversalEventMonitor.publisher(for: mouseTrackingMask),\n            RunLoopLocalEventMonitor.publisher(for: mouseTrackingMask, mode: .eventTracking)\n        )\n        .removeDuplicates()\n        .sink { [weak self] event in\n            guard let self else {\n                return\n            }\n            switch event.type {\n            case .mouseMoved:\n                lastMouseMoveStartDate = .now\n            case .leftMouseDown, .rightMouseDown, .otherMouseDown:\n                isMouseButtonDown = true\n            case .leftMouseUp, .rightMouseUp, .otherMouseUp:\n                isMouseButtonDown = false\n            default:\n                break\n            }\n        }\n        .store(in: &c)\n\n        cancellables = c\n    }\n}\n\n// MARK: - Cache Items\n\nextension MenuBarItemManager {\n    /// Logs a warning that the given menu bar item was not added to the cache.\n    private func logNotCachedWarning(for item: MenuBarItem) {\n        Logger.itemManager.warning(\"\\(item.logString) was not cached\")\n    }\n\n    /// Logs a reason for skipping the cache.\n    private func logSkippingCache(reason: String) {\n        Logger.itemManager.debug(\"Skipping menu bar item cache as \\(reason)\")\n    }\n\n    /// Caches the given menu bar items, without checking whether the control\n    /// items are in the correct order.\n    private func uncheckedCacheItems(\n        hiddenControlItem: MenuBarItem,\n        alwaysHiddenControlItem: MenuBarItem?,\n        otherItems: [MenuBarItem]\n    ) {\n        Logger.itemManager.debug(\"Caching menu bar items\")\n\n        let predicates = Predicates.sectionPredicates(\n            hiddenControlItem: hiddenControlItem,\n            alwaysHiddenControlItem: alwaysHiddenControlItem\n        )\n\n        var cache = ItemCache()\n        var tempShownItems = [(MenuBarItem, MoveDestination)]()\n\n        for item in otherItems {\n            if let context = tempShownItemContexts.first(where: { $0.info == item.info }) {\n                // Keep track of temporarily shown items and their return destinations separately.\n                // We want to cache them as if they were in their original locations. Once all other\n                // items are cached, use the return destinations to insert the items into the cache\n                // at the correct position.\n                tempShownItems.append((item, context.returnDestination))\n            } else if predicates.isInVisibleSection(item) {\n                cache[.visible].append(item)\n            } else if predicates.isInHiddenSection(item) {\n                cache[.hidden].append(item)\n            } else if predicates.isInAlwaysHiddenSection(item) {\n                cache[.alwaysHidden].append(item)\n            } else {\n                logNotCachedWarning(for: item)\n            }\n        }\n\n        for (item, destination) in tempShownItems {\n            switch destination {\n            case .leftOfItem(let targetItem):\n                switch targetItem.info {\n                case .hiddenControlItem:\n                    cache[.hidden].append(item)\n                case .alwaysHiddenControlItem:\n                    cache[.alwaysHidden].append(item)\n                default:\n                    if\n                        let section = cache.section(for: targetItem),\n                        let index = cache[section].firstIndex(matching: targetItem.info)\n                    {\n                        let clampedIndex = index.clamped(to: cache[section].startIndex...cache[section].endIndex)\n                        cache[section].insert(item, at: clampedIndex)\n                    }\n                }\n            case .rightOfItem(let targetItem):\n                switch targetItem.info {\n                case .hiddenControlItem:\n                    cache[.visible].insert(item, at: 0)\n                case .alwaysHiddenControlItem:\n                    cache[.hidden].insert(item, at: 0)\n                default:\n                    if\n                        let section = cache.section(for: targetItem),\n                        let index = cache[section].firstIndex(matching: targetItem.info)\n                    {\n                        let clampedIndex = (index - 1).clamped(to: cache[section].startIndex...cache[section].endIndex)\n                        cache[section].insert(item, at: clampedIndex)\n                    }\n                }\n            }\n        }\n\n        itemCache = cache\n    }\n\n    /// Caches the current menu bar items if needed, ensuring that the control\n    /// items are in the correct order.\n    func cacheItemsIfNeeded() async {\n        do {\n            try await waitForItemsToStopMoving(timeout: .seconds(1))\n        } catch is TaskTimeoutError {\n            logSkippingCache(reason: \"an item is currently being moved\")\n            return\n        } catch {\n            guard !itemHasRecentlyMoved else {\n                logSkippingCache(reason: \"an item was recently moved\")\n                return\n            }\n        }\n\n        let itemWindowIDs = Bridging.getWindowList(option: [.menuBarItems, .activeSpace])\n        if cachedItemWindowIDs == itemWindowIDs {\n            logSkippingCache(reason: \"item windows have not changed\")\n            return\n        } else {\n            cachedItemWindowIDs = itemWindowIDs\n        }\n\n        var items = MenuBarItem.getMenuBarItems(onScreenOnly: false, activeSpaceOnly: true)\n\n        let hiddenControlItem = items.firstIndex(matching: .hiddenControlItem).map { items.remove(at: $0) }\n        let alwaysHiddenControlItem = items.firstIndex(matching: .alwaysHiddenControlItem).map { items.remove(at: $0) }\n\n        guard let hiddenControlItem else {\n            Logger.itemManager.warning(\"Missing control item for hidden section\")\n            Logger.itemManager.debug(\"Clearing menu bar item cache\")\n            itemCache.clear()\n            return\n        }\n\n        do {\n            if let alwaysHiddenControlItem {\n                try await enforceControlItemOrder(\n                    hiddenControlItem: hiddenControlItem,\n                    alwaysHiddenControlItem: alwaysHiddenControlItem\n                )\n            }\n            uncheckedCacheItems(\n                hiddenControlItem: hiddenControlItem,\n                alwaysHiddenControlItem: alwaysHiddenControlItem,\n                otherItems: items\n            )\n        } catch {\n            Logger.itemManager.error(\"Error enforcing control item order: \\(error)\")\n            Logger.itemManager.debug(\"Clearing menu bar item cache\")\n            itemCache.clear()\n        }\n    }\n}\n\n// MARK: - Menu Bar Item Events -\n\nextension MenuBarItemManager {\n    /// An error that can occur during menu bar item event operations.\n    struct EventError: Error, CustomStringConvertible, LocalizedError {\n        /// Error codes within the domain of menu bar item event errors.\n        enum ErrorCode: Int, CustomStringConvertible {\n            /// An operation could not be completed.\n            case couldNotComplete\n\n            /// The creation of a menu bar item event failed.\n            case eventCreationFailure\n\n            /// The shared app state is invalid or could not be found.\n            case invalidAppState\n\n            /// An event source could not be created or is otherwise invalid.\n            case invalidEventSource\n\n            /// The location of the mouse cursor is invalid or could not be found.\n            case invalidCursorLocation\n\n            /// A menu bar item is invalid.\n            case invalidItem\n\n            /// A menu bar item cannot be moved.\n            case notMovable\n\n            /// A menu bar item event operation timed out.\n            case eventOperationTimeout\n\n            /// A menu bar item frame check timed out.\n            case frameCheckTimeout\n\n            /// An operation timed out.\n            case otherTimeout\n\n            /// Description of the code for debugging purposes.\n            var description: String {\n                switch self {\n                case .couldNotComplete: \"couldNotComplete\"\n                case .eventCreationFailure: \"eventCreationFailure\"\n                case .invalidAppState: \"invalidAppState\"\n                case .invalidEventSource: \"invalidEventSource\"\n                case .invalidCursorLocation: \"invalidCursorLocation\"\n                case .invalidItem: \"invalidItem\"\n                case .notMovable: \"notMovable\"\n                case .eventOperationTimeout: \"eventOperationTimeout\"\n                case .frameCheckTimeout: \"frameCheckTimeout\"\n                case .otherTimeout: \"otherTimeout\"\n                }\n            }\n\n            /// A string to use for logging purposes.\n            var logString: String {\n                \"\\(self) (rawValue: \\(rawValue))\"\n            }\n        }\n\n        /// The error code of this error.\n        let code: ErrorCode\n\n        /// The error's menu bar item.\n        let item: MenuBarItem\n\n        /// The message associated with this error.\n        var message: String {\n            switch code {\n            case .couldNotComplete:\n                \"Could not complete event operation for \\\"\\(item.displayName)\\\"\"\n            case .eventCreationFailure:\n                \"Failed to create event for \\\"\\(item.displayName)\\\"\"\n            case .invalidAppState:\n                \"Invalid app state for \\\"\\(item.displayName)\\\"\"\n            case .invalidEventSource:\n                \"Invalid event source for \\\"\\(item.displayName)\\\"\"\n            case .invalidCursorLocation:\n                \"Invalid cursor location for \\\"\\(item.displayName)\\\"\"\n            case .invalidItem:\n                \"\\\"\\(item.displayName)\\\" is invalid\"\n            case .notMovable:\n                \"\\\"\\(item.displayName)\\\" is not movable\"\n            case .eventOperationTimeout:\n                \"Event operation timed out for \\\"\\(item.displayName)\\\"\"\n            case .frameCheckTimeout:\n                \"Frame check timed out for \\\"\\(item.displayName)\\\"\"\n            case .otherTimeout:\n                \"Operation timed out for \\\"\\(item.displayName)\\\"\"\n            }\n        }\n\n        /// Description of the error for debugging purposes.\n        var description: String {\n            var parameters = [String]()\n            parameters.append(\"code: \\(code.logString)\")\n            parameters.append(\"item: \\(item.logString)\")\n            return \"\\(Self.self)(\\(parameters.joined(separator: \", \")))\"\n        }\n\n        /// Description of the error for display purposes.\n        var errorDescription: String? {\n            message\n        }\n\n        /// Suggestion for recovery from the error.\n        var recoverySuggestion: String? {\n            \"Please try again. If the error persists, please file a bug report.\"\n        }\n    }\n}\n\n// MARK: - Async Waiters\n\nextension MenuBarItemManager {\n    /// Waits asynchronously for the given operation to complete.\n    /// \n    /// - Parameters:\n    ///   - timeout: Amount of time to wait before throwing an error.\n    ///   - operation: The operation to perform.\n    private func waitWithTask(timeout: Duration?, operation: @escaping @Sendable () async throws -> Void) async throws {\n        let task = if let timeout {\n            Task(timeout: timeout, operation: operation)\n        } else {\n            Task(operation: operation)\n        }\n        try await task.value\n    }\n\n    /// Waits asynchronously for all menu bar items to stop moving.\n    ///\n    /// - Parameter timeout: Amount of time to wait before throwing an error.\n    func waitForItemsToStopMoving(timeout: Duration? = nil) async throws {\n        try await waitWithTask(timeout: timeout) { [weak self] in\n            guard let self else {\n                return\n            }\n            while await isMovingItem {\n                try Task.checkCancellation()\n                try await Task.sleep(for: .milliseconds(10))\n            }\n        }\n    }\n\n    /// Waits asynchronously for the mouse to stop moving.\n    ///\n    /// - Parameters:\n    ///   - threshold: A threshold to use to determine whether the mouse has stopped moving.\n    ///   - timeout: Amount of time to wait before throwing an error.\n    func waitForMouseToStopMoving(threshold: TimeInterval = 0.1, timeout: Duration? = nil) async throws {\n        try await waitWithTask(timeout: timeout) { [weak self] in\n            guard let self else {\n                return\n            }\n            while true {\n                try Task.checkCancellation()\n                guard let date = await lastMouseMoveStartDate else {\n                    break\n                }\n                if Date.now.timeIntervalSince(date) > threshold {\n                    break\n                }\n                try await Task.sleep(for: .milliseconds(10))\n            }\n        }\n    }\n\n    /// Waits asynchronously until no modifier keys are pressed.\n    ///\n    /// - Parameter timeout: Amount of time to wait before throwing an error.\n    func waitForNoModifiersPressed(timeout: Duration? = nil) async throws {\n        try await waitWithTask(timeout: timeout) {\n            // Return early if no flags are pressed.\n            if NSEvent.modifierFlags.isEmpty {\n                return\n            }\n\n            var cancellable: AnyCancellable?\n\n            await withCheckedContinuation { continuation in\n                cancellable = Publishers.Merge(\n                    UniversalEventMonitor.publisher(for: .flagsChanged),\n                    RunLoopLocalEventMonitor.publisher(for: .flagsChanged, mode: .eventTracking)\n                )\n                .removeDuplicates()\n                .sink { _ in\n                    if NSEvent.modifierFlags.isEmpty {\n                        cancellable?.cancel()\n                        continuation.resume()\n                    }\n                }\n            }\n        }\n    }\n}\n\n// MARK: - Move Items\n\nextension MenuBarItemManager {\n    /// A destination that a menu bar item can be moved to.\n    enum MoveDestination {\n        /// The menu bar item will be moved to the left of the given menu bar item.\n        case leftOfItem(MenuBarItem)\n\n        /// The menu bar item will be moved to the right of the given menu bar item.\n        case rightOfItem(MenuBarItem)\n\n        /// A string to use for logging purposes.\n        var logString: String {\n            switch self {\n            case .leftOfItem(let item): \"left of \\(item.logString)\"\n            case .rightOfItem(let item): \"right of \\(item.logString)\"\n            }\n        }\n    }\n\n    /// Returns the current frame for the given item.\n    ///\n    /// - Parameter item: The item to return the current frame for.\n    private func getCurrentFrame(for item: MenuBarItem) -> CGRect? {\n        guard let frame = Bridging.getWindowFrame(for: item.window.windowID) else {\n            Logger.itemManager.error(\"Couldn't get current frame for \\(item.logString)\")\n            return nil\n        }\n        return frame\n    }\n\n    /// Returns the end point for moving an item to the given destination.\n    ///\n    /// - Parameter destination: The destination to return the end point for.\n    private func getEndPoint(for destination: MoveDestination) throws -> CGPoint {\n        switch destination {\n        case .leftOfItem(let targetItem):\n            guard let currentFrame = getCurrentFrame(for: targetItem) else {\n                throw EventError(code: .invalidItem, item: targetItem)\n            }\n            return CGPoint(x: currentFrame.minX, y: currentFrame.midY)\n        case .rightOfItem(let targetItem):\n            guard let currentFrame = getCurrentFrame(for: targetItem) else {\n                throw EventError(code: .invalidItem, item: targetItem)\n            }\n            return CGPoint(x: currentFrame.maxX, y: currentFrame.midY)\n        }\n    }\n\n    /// Returns the fallback point for returning the given item to its original\n    /// position if a move fails.\n    ///\n    /// - Parameter item: The item to return the fallback point for.\n    private func getFallbackPoint(for item: MenuBarItem) throws -> CGPoint {\n        guard let currentFrame = getCurrentFrame(for: item) else {\n            throw EventError(code: .invalidItem, item: item)\n        }\n        return CGPoint(x: currentFrame.midX, y: currentFrame.midY)\n    }\n\n    /// Returns the target item for the given destination.\n    ///\n    /// - Parameter destination: The destination to get the target item from.\n    private func getTargetItem(for destination: MoveDestination) -> MenuBarItem {\n        switch destination {\n        case .leftOfItem(let targetItem), .rightOfItem(let targetItem): targetItem\n        }\n    }\n\n    /// Returns a Boolean value that indicates whether the given item is in the\n    /// correct position for the given destination.\n    ///\n    /// - Parameters:\n    ///   - item: The item to check the position of.\n    ///   - destination: The destination to compare the item's position against.\n    private func itemHasCorrectPosition(item: MenuBarItem, for destination: MoveDestination) throws -> Bool {\n        guard let currentFrame = getCurrentFrame(for: item) else {\n            throw EventError(code: .invalidItem, item: item)\n        }\n        switch destination {\n        case .leftOfItem(let targetItem):\n            guard let currentTargetFrame = getCurrentFrame(for: targetItem) else {\n                throw EventError(code: .invalidItem, item: targetItem)\n            }\n            return currentFrame.maxX == currentTargetFrame.minX\n        case .rightOfItem(let targetItem):\n            guard let currentTargetFrame = getCurrentFrame(for: targetItem) else {\n                throw EventError(code: .invalidItem, item: targetItem)\n            }\n            return currentFrame.minX == currentTargetFrame.maxX\n        }\n    }\n\n    /// Returns a Boolean value that indicates whether the given events have the\n    /// same values for each integer value field.\n    ///\n    /// - Parameters:\n    ///   - events: The events to compare.\n    ///   - integerFields: An array of integer value fields to compare on each event.\n    private nonisolated func eventsMatch(_ events: [CGEvent], by integerFields: [CGEventField]) -> Bool {\n        var fieldValues = Set<[Int64]>()\n        for event in events {\n            let values = integerFields.map(event.getIntegerValueField)\n            fieldValues.insert(values)\n            if fieldValues.count != 1 {\n                return false\n            }\n        }\n        return true\n    }\n\n    /// Posts an event to the given event tap location.\n    ///\n    /// - Parameters:\n    ///   - event: The event to post.\n    ///   - location: The event tap location to post the event to.\n    private nonisolated func postEvent(_ event: CGEvent, to location: EventTap.Location) {\n        Logger.itemManager.debug(\"Posting \\(event.type.logString) to \\(location.logString)\")\n        switch location {\n        case .hidEventTap:\n            event.post(tap: .cghidEventTap)\n        case .sessionEventTap:\n            event.post(tap: .cgSessionEventTap)\n        case .annotatedSessionEventTap:\n            event.post(tap: .cgAnnotatedSessionEventTap)\n        case .pid(let pid):\n            event.postToPid(pid)\n        }\n    }\n\n    /// Posts an event to the given event tap location and waits until it is\n    /// received before returning.\n    ///\n    /// - Parameters:\n    ///   - event: The event to post.\n    ///   - location: The event tap location to post the event to.\n    ///   - item: The menu bar item that the event affects.\n    private func postEventAndWaitToReceive(\n        _ event: CGEvent,\n        to location: EventTap.Location,\n        item: MenuBarItem\n    ) async throws {\n        return try await withCheckedThrowingContinuation { continuation in\n            let eventTap = EventTap(\n                options: .listenOnly,\n                location: location,\n                place: .tailAppendEventTap,\n                types: [event.type]\n            ) { [weak self] proxy, type, rEvent in\n                guard let self else {\n                    proxy.disable()\n                    return nil\n                }\n\n                // Reenable the tap if disabled by the system.\n                if type == .tapDisabledByUserInput || type == .tapDisabledByTimeout {\n                    proxy.enable()\n                    return nil\n                }\n\n                // Verify that the received event was the sent event.\n                guard eventsMatch([rEvent, event], by: CGEventField.menuBarItemEventFields) else {\n                    return nil\n                }\n\n                // Ensure the tap is enabled, preventing multiple calls to resume().\n                guard proxy.isEnabled else {\n                    Logger.itemManager.debug(\"Event tap \\\"\\(proxy.label)\\\" is disabled (item: \\(item.logString))\")\n                    return nil\n                }\n\n                Logger.itemManager.debug(\"Received \\(type.logString) at \\(location.logString) (item: \\(item.logString))\")\n\n                // Disable the tap and resume the continuation.\n                proxy.disable()\n                continuation.resume()\n\n                return nil\n            }\n\n            eventTap.enable(timeout: .milliseconds(50)) {\n                Logger.itemManager.error(\"Event tap \\\"\\(eventTap.label)\\\" timed out (item: \\(item.logString))\")\n                eventTap.disable()\n                continuation.resume(throwing: EventError(code: .eventOperationTimeout, item: item))\n            }\n\n            // Post the event to the location.\n            postEvent(event, to: location)\n        }\n    }\n\n    /// Does a lot of weird magic to make a menu bar item receive an event.\n    ///\n    /// - Parameters:\n    ///   - event: The event to send.\n    ///   - firstLocation: The first location to send the event to.\n    ///   - secondLocation: The second location to send the event to.\n    ///   - item: The menu bar item that the event affects.\n    private func scrombleEvent(\n        _ event: CGEvent,\n        from firstLocation: EventTap.Location,\n        to secondLocation: EventTap.Location,\n        item: MenuBarItem\n    ) async throws {\n        // Create a null event and assign it unique user data.\n        guard let nullEvent = CGEvent(source: nil) else {\n            throw EventError(code: .eventCreationFailure, item: item)\n        }\n        let nullUserData = Int64(truncatingIfNeeded: Int(bitPattern: ObjectIdentifier(nullEvent)))\n        nullEvent.setIntegerValueField(.eventSourceUserData, value: nullUserData)\n\n        return try await withCheckedThrowingContinuation { continuation in\n            // Create an event tap for the null event at the first location.\n            // This tap throws away all events it receives.\n            let eventTap1 = EventTap(\n                label: \"EventTap 1\",\n                options: .defaultTap,\n                location: firstLocation,\n                place: .tailAppendEventTap,\n                types: [nullEvent.type]\n            ) { [weak self] proxy, type, rEvent in\n                guard let self else {\n                    proxy.disable()\n                    return nil\n                }\n\n                // Reenable the tap if disabled by the system.\n                if type == .tapDisabledByUserInput || type == .tapDisabledByTimeout {\n                    proxy.enable()\n                    return nil\n                }\n\n                // Verify that this is the null event.\n                guard rEvent.getIntegerValueField(.eventSourceUserData) == nullUserData else {\n                    return nil\n                }\n\n                // Disable the tap and post the real event to the second location.\n                proxy.disable()\n                postEvent(event, to: secondLocation)\n\n                return nil\n            }\n\n            // Create an event tap for the real event at the second location.\n            // This tap can listen for events, but cannot alter or discard them.\n            let eventTap2 = EventTap(\n                label: \"EventTap 2\",\n                options: .listenOnly,\n                location: secondLocation,\n                place: .tailAppendEventTap,\n                types: [event.type]\n            ) { [weak self] proxy, type, rEvent in\n                guard let self else {\n                    proxy.disable()\n                    return nil\n                }\n\n                // Reenable the tap if disabled by the system.\n                if type == .tapDisabledByUserInput || type == .tapDisabledByTimeout {\n                    proxy.enable()\n                    return nil\n                }\n\n                // Verify that the received event was the sent event.\n                guard eventsMatch([rEvent, event], by: CGEventField.menuBarItemEventFields) else {\n                    return nil\n                }\n\n                // Ensure the tap is enabled, preventing multiple calls to resume().\n                guard proxy.isEnabled else {\n                    Logger.itemManager.debug(\"Event tap \\\"\\(proxy.label)\\\" is disabled (item: \\(item.logString))\")\n                    return nil\n                }\n\n                // Disable the tap, post the event to the first location, and resume\n                // the continuation.\n                proxy.disable()\n                postEvent(event, to: firstLocation)\n                continuation.resume()\n\n                return nil\n            }\n\n            // Enable both taps, with a timeout on the second tap.\n            eventTap1.enable()\n            eventTap2.enable(timeout: .milliseconds(50)) {\n                Logger.itemManager.error(\"Event tap \\\"\\(eventTap2.label)\\\" timed out (item: \\(item.logString))\")\n                eventTap1.disable()\n                eventTap2.disable()\n                continuation.resume(throwing: EventError(code: .eventOperationTimeout, item: item))\n            }\n\n            // Post the null event to the first location.\n            postEvent(nullEvent, to: firstLocation)\n        }\n    }\n\n    /// Does a lot of weird magic to make a menu bar item receive an event, then\n    /// waits for the frame of the given menu bar item to change before returning.\n    ///\n    /// - Parameters:\n    ///   - event: The event to send.\n    ///   - firstLocation: The first location to send the event to.\n    ///   - secondLocation: The second location to send the event to.\n    ///   - item: The item whose frame should be observed.\n    private func scrombleEvent(\n        _ event: CGEvent,\n        from firstLocation: EventTap.Location,\n        to secondLocation: EventTap.Location,\n        waitingForFrameChangeOf item: MenuBarItem\n    ) async throws {\n        guard let currentFrame = getCurrentFrame(for: item) else {\n            try await scrombleEvent(event, from: firstLocation, to: secondLocation, item: item)\n            Logger.itemManager.warning(\"Couldn't get menu bar item frame for \\(item.logString), so using fixed delay\")\n            // This will be slow, but subsequent events will have a better chance of succeeding.\n            try await Task.sleep(for: .milliseconds(50))\n            return\n        }\n        try await scrombleEvent(event, from: firstLocation, to: secondLocation, item: item)\n        try await waitForFrameChange(of: item, initialFrame: currentFrame, timeout: .milliseconds(50))\n    }\n\n    /// Waits for a menu bar item's frame to change from an initial frame.\n    ///\n    /// - Parameters:\n    ///   - item: The item whose frame should be observed.\n    ///   - initialFrame: An initial frame to compare the item's frame against.\n    ///   - timeout: The amount of time to wait before throwing a timeout error.\n    private func waitForFrameChange(of item: MenuBarItem, initialFrame: CGRect, timeout: Duration) async throws {\n        struct FrameCheckCancellationError: Error { }\n\n        let frameCheckTask = Task(timeout: timeout) {\n            while true {\n                try Task.checkCancellation()\n                guard let currentFrame = await self.getCurrentFrame(for: item) else {\n                    throw FrameCheckCancellationError()\n                }\n                if currentFrame != initialFrame {\n                    Logger.itemManager.debug(\"Menu bar item frame for \\(item.logString) has changed to \\(NSStringFromRect(currentFrame))\")\n                    return\n                }\n            }\n        }\n        do {\n            try await frameCheckTask.value\n        } catch is FrameCheckCancellationError {\n            Logger.itemManager.warning(\"Menu bar item frame check for \\(item.logString) was cancelled, so using fixed delay\")\n            // This will be slow, but subsequent events will have a better chance of succeeding.\n            try await Task.sleep(for: .milliseconds(50))\n        } catch is TaskTimeoutError {\n            throw EventError(code: .frameCheckTimeout, item: item)\n        }\n    }\n\n    /// Permits all events for an event source during the given suppression states,\n    /// suppressing local events for the given interval.\n    private func permitAllEvents(\n        for stateID: CGEventSourceStateID,\n        during states: [CGEventSuppressionState],\n        suppressionInterval: TimeInterval,\n        item: MenuBarItem\n    ) throws {\n        guard let source = CGEventSource(stateID: stateID) else {\n            throw EventError(code: .invalidEventSource, item: item)\n        }\n        for state in states {\n            source.setLocalEventsFilterDuringSuppressionState(.permitAllEvents, state: state)\n        }\n        source.localEventsSuppressionInterval = suppressionInterval\n    }\n\n    /// Tries to wake up the given item if it is not responding to events.\n    private func wakeUpItem(_ item: MenuBarItem) async throws {\n        Logger.itemManager.debug(\"Attempting to wake up \\(item.logString)\")\n\n        guard let source = CGEventSource(stateID: .hidSystemState) else {\n            throw EventError(code: .invalidEventSource, item: item)\n        }\n        guard let currentFrame = getCurrentFrame(for: item) else {\n            throw EventError(code: .invalidItem, item: item)\n        }\n\n        guard\n            let mouseDownEvent = CGEvent.menuBarItemEvent(\n                type: .move(.leftMouseDown),\n                location: CGPoint(x: currentFrame.midX, y: currentFrame.midY),\n                item: item,\n                pid: item.ownerPID,\n                source: source\n            ),\n            let mouseUpEvent = CGEvent.menuBarItemEvent(\n                type: .move(.leftMouseUp),\n                location: CGPoint(x: currentFrame.midX, y: currentFrame.midY),\n                item: item,\n                pid: item.ownerPID,\n                source: source\n            )\n        else {\n            throw EventError(code: .eventCreationFailure, item: item)\n        }\n\n        try await scrombleEvent(\n            mouseDownEvent,\n            from: .pid(item.ownerPID),\n            to: .sessionEventTap,\n            item: item\n        )\n        try await scrombleEvent(\n            mouseUpEvent,\n            from: .pid(item.ownerPID),\n            to: .sessionEventTap,\n            item: item\n        )\n    }\n\n    /// Moves a menu bar item to the given destination, without restoring the mouse\n    /// pointer to its initial location.\n    ///\n    /// - Parameters:\n    ///   - item: A menu bar item to move.\n    ///   - destination: A destination to move the menu bar item.\n    private func moveItemWithoutRestoringMouseLocation(_ item: MenuBarItem, to destination: MoveDestination) async throws {\n        itemMoveCount += 1\n        defer {\n            itemMoveCount -= 1\n        }\n\n        guard item.isMovable else {\n            throw EventError(code: .notMovable, item: item)\n        }\n        guard let source = CGEventSource(stateID: .hidSystemState) else {\n            throw EventError(code: .invalidEventSource, item: item)\n        }\n\n        let startPoint = CGPoint(x: 20_000, y: 20_000)\n        let endPoint = try getEndPoint(for: destination)\n        let fallbackPoint = try getFallbackPoint(for: item)\n        let targetItem = getTargetItem(for: destination)\n\n        guard\n            let mouseDownEvent = CGEvent.menuBarItemEvent(\n                type: .move(.leftMouseDown),\n                location: startPoint,\n                item: item,\n                pid: item.ownerPID,\n                source: source\n            ),\n            let mouseUpEvent = CGEvent.menuBarItemEvent(\n                type: .move(.leftMouseUp),\n                location: endPoint,\n                item: targetItem,\n                pid: item.ownerPID,\n                source: source\n            ),\n            let fallbackEvent = CGEvent.menuBarItemEvent(\n                type: .move(.leftMouseUp),\n                location: fallbackPoint,\n                item: item,\n                pid: item.ownerPID,\n                source: source\n            )\n        else {\n            throw EventError(code: .eventCreationFailure, item: item)\n        }\n\n        try permitAllEvents(\n            for: .combinedSessionState,\n            during: [\n                .eventSuppressionStateRemoteMouseDrag,\n                .eventSuppressionStateSuppressionInterval,\n            ],\n            suppressionInterval: 0,\n            item: item\n        )\n\n        lastItemMoveStartDate = .now\n\n        do {\n            try await scrombleEvent(\n                mouseDownEvent,\n                from: .pid(item.ownerPID),\n                to: .sessionEventTap,\n                waitingForFrameChangeOf: item\n            )\n            try await scrombleEvent(\n                mouseUpEvent,\n                from: .pid(item.ownerPID),\n                to: .sessionEventTap,\n                waitingForFrameChangeOf: item\n            )\n        } catch {\n            do {\n                Logger.itemManager.debug(\"Posting fallback event for moving \\(item.logString)\")\n                // Catch this, as we still want to throw the existing error if the fallback fails.\n                try await postEventAndWaitToReceive(\n                    fallbackEvent,\n                    to: .sessionEventTap,\n                    item: item\n                )\n            } catch {\n                Logger.itemManager.error(\"Failed to post fallback event for moving \\(item.logString)\")\n            }\n            throw error\n        }\n    }\n\n    /// Moves a menu bar item to the given destination.\n    ///\n    /// - Parameters:\n    ///   - item: A menu bar item to move.\n    ///   - destination: A destination to move the menu bar item.\n    func move(item: MenuBarItem, to destination: MoveDestination) async throws {\n        if try itemHasCorrectPosition(item: item, for: destination) {\n            Logger.itemManager.debug(\"\\(item.logString) is already in the correct position\")\n            return\n        }\n\n        do {\n            // Order of these waiters matters, as the modifiers could be released\n            // while the mouse is still moving.\n            try await waitForNoModifiersPressed()\n            try await waitForMouseToStopMoving()\n        } catch {\n            throw EventError(code: .couldNotComplete, item: item)\n        }\n\n        Logger.itemManager.info(\"Moving \\(item.logString) to \\(destination.logString)\")\n\n        guard let appState else {\n            throw EventError(code: .invalidAppState, item: item)\n        }\n        guard let cursorLocation = MouseCursor.locationCoreGraphics else {\n            throw EventError(code: .invalidCursorLocation, item: item)\n        }\n        guard let initialFrame = getCurrentFrame(for: item) else {\n            throw EventError(code: .invalidItem, item: item)\n        }\n\n        appState.eventManager.stopAll()\n        defer {\n            appState.eventManager.startAll()\n        }\n\n        MouseCursor.hide()\n\n        defer {\n            MouseCursor.warp(to: cursorLocation)\n            MouseCursor.show()\n        }\n\n        // Item movement can occasionally fail. Retry up to a total of 5 attempts,\n        // throwing the last attempt's error if it fails.\n        for n in 1...5 {\n            do {\n                try await moveItemWithoutRestoringMouseLocation(item, to: destination)\n                guard let newFrame = getCurrentFrame(for: item) else {\n                    throw EventError(code: .invalidItem, item: item)\n                }\n                if newFrame != initialFrame {\n                    Logger.itemManager.info(\"Successfully moved \\(item.logString)\")\n                    break\n                } else {\n                    throw EventError(code: .couldNotComplete, item: item)\n                }\n            } catch where n < 5 {\n                Logger.itemManager.warning(\"Attempt \\(n) to move \\(item.logString) failed (error: \\(error))\")\n                try await wakeUpItem(item)\n                Logger.itemManager.info(\"Retrying move of \\(item.logString)\")\n                continue\n            }\n        }\n    }\n\n    /// Moves a menu bar item to the given destination and waits until the move\n    /// completes before returning.\n    /// \n    /// - Parameters:\n    ///   - item: A menu bar item to move.\n    ///   - destination: A destination to move the menu bar item.\n    ///   - timeout: Amount of time to wait before throwing an error.\n    func slowMove(item: MenuBarItem, to destination: MoveDestination, timeout: Duration = .seconds(1)) async throws {\n        itemMoveCount += 1\n        defer {\n            itemMoveCount -= 1\n        }\n        try await move(item: item, to: destination)\n        let waitTask = Task(timeout: timeout) {\n            while true {\n                try Task.checkCancellation()\n                if try await self.itemHasCorrectPosition(item: item, for: destination) {\n                    return\n                }\n            }\n        }\n        do {\n            try await waitTask.value\n        } catch is TaskTimeoutError {\n            throw EventError(code: .otherTimeout, item: item)\n        }\n    }\n}\n\n// MARK: - Click Items\n\nextension MenuBarItemManager {\n    /// Clicks the given menu bar item with the given mouse button.\n    func click(item: MenuBarItem, with mouseButton: CGMouseButton) async throws {\n        guard let source = CGEventSource(stateID: .hidSystemState) else {\n            throw EventError(code: .invalidEventSource, item: item)\n        }\n        guard let cursorLocation = MouseCursor.locationCoreGraphics else {\n            throw EventError(code: .invalidCursorLocation, item: item)\n        }\n        guard let currentFrame = getCurrentFrame(for: item) else {\n            throw EventError(code: .invalidItem, item: item)\n        }\n\n        let buttonStates = mouseButton.buttonStates\n        let clickPoint = CGPoint(x: currentFrame.midX, y: currentFrame.midY)\n\n        guard\n            let mouseDownEvent = CGEvent.menuBarItemEvent(\n                type: .click(buttonStates.down),\n                location: clickPoint,\n                item: item,\n                pid: item.ownerPID,\n                source: source\n            ),\n            let mouseUpEvent = CGEvent.menuBarItemEvent(\n                type: .click(buttonStates.up),\n                location: clickPoint,\n                item: item,\n                pid: item.ownerPID,\n                source: source\n            ),\n            let fallbackEvent = CGEvent.menuBarItemEvent(\n                type: .click(buttonStates.up),\n                location: clickPoint,\n                item: item,\n                pid: item.ownerPID,\n                source: source\n            )\n        else {\n            throw EventError(code: .eventCreationFailure, item: item)\n        }\n\n        try permitAllEvents(\n            for: .combinedSessionState,\n            during: [\n                .eventSuppressionStateRemoteMouseDrag,\n                .eventSuppressionStateSuppressionInterval,\n            ],\n            suppressionInterval: 0,\n            item: item\n        )\n\n        MouseCursor.hide()\n\n        defer {\n            MouseCursor.warp(to: cursorLocation)\n            MouseCursor.show()\n        }\n\n        do {\n            Logger.itemManager.info(\"Clicking \\(item.logString) with \\(mouseButton.logString)\")\n            try await postEventAndWaitToReceive(\n                mouseDownEvent,\n                to: .sessionEventTap,\n                item: item\n            )\n            try await postEventAndWaitToReceive(\n                mouseUpEvent,\n                to: .sessionEventTap,\n                item: item\n            )\n        } catch {\n            do {\n                Logger.itemManager.debug(\"Posting fallback event for clicking \\(item.logString)\")\n                // Catch this, as we still want to throw the existing error if the fallback fails.\n                try await postEventAndWaitToReceive(\n                    fallbackEvent,\n                    to: .sessionEventTap,\n                    item: item\n                )\n            } catch {\n                Logger.itemManager.error(\"Failed to post fallback event for clicking \\(item.logString)\")\n            }\n            throw error\n        }\n    }\n}\n\n// MARK: - Temporarily Show Items\n\nextension MenuBarItemManager {\n    /// Gets the destination to return the given item to after it is temporarily shown.\n    private func getReturnDestination(for item: MenuBarItem, in items: [MenuBarItem]) -> MoveDestination? {\n        let info = item.info\n        if let index = items.firstIndex(where: { $0.info == info }) {\n            if items.indices.contains(index + 1) {\n                return .leftOfItem(items[index + 1])\n            } else if items.indices.contains(index - 1) {\n                return .rightOfItem(items[index - 1])\n            }\n        }\n        return nil\n    }\n\n    /// Schedules a timer for the given interval, attempting to rehide the current\n    /// temporarily shown items when the timer fires.\n    private func runTempShownItemTimer(for interval: TimeInterval) {\n        Logger.itemManager.debug(\"Running rehide timer for temporarily shown items with interval: \\(interval)\")\n        tempShownItemsTimer?.invalidate()\n        tempShownItemsTimer = .scheduledTimer(withTimeInterval: interval, repeats: false) { [weak self] timer in\n            guard let self else {\n                timer.invalidate()\n                return\n            }\n            Logger.itemManager.debug(\"Rehide timer fired\")\n            Task {\n                await self.rehideTempShownItems()\n            }\n        }\n    }\n\n    /// Temporarily shows the given item.\n    ///\n    /// The item is cached alongside a destination that it will be automatically returned\n    /// to. If `true` is passed to the `clickWhenFinished` parameter, the item is clicked\n    /// once movement is finished.\n    ///\n    /// - Parameters:\n    ///   - item: An item to show.\n    ///   - clickWhenFinished: A Boolean value that indicates whether the item should be\n    ///     clicked once movement is finished.\n    ///   - mouseButton: The mouse button of the click.\n    func tempShowItem(_ item: MenuBarItem, clickWhenFinished: Bool, mouseButton: CGMouseButton) {\n        if\n            let latest = MenuBarItem(windowID: item.windowID),\n            latest.isOnScreen\n        {\n            if clickWhenFinished {\n                Task {\n                    do {\n                        try await click(item: latest, with: mouseButton)\n                    } catch {\n                        Logger.itemManager.error(\"ERROR: \\(error)\")\n                    }\n                }\n            }\n            return\n        }\n\n        guard\n            let appState,\n            let screen = NSScreen.main,\n            let applicationMenuFrame = appState.menuBarManager.getApplicationMenuFrame(for: screen.displayID)\n        else {\n            Logger.itemManager.warning(\"No application menu frame, so not showing \\(item.logString)\")\n            return\n        }\n\n        Logger.itemManager.info(\"Temporarily showing \\(item.logString)\")\n\n        var items = MenuBarItem.getMenuBarItems(onScreenOnly: false, activeSpaceOnly: true)\n\n        guard let destination = getReturnDestination(for: item, in: items) else {\n            Logger.itemManager.warning(\"No return destination for \\(item.logString)\")\n            return\n        }\n\n        // Remove all items up to the hidden control item.\n        items.trimPrefix { $0.info != .hiddenControlItem }\n        // Remove the hidden control item.\n        items.removeFirst()\n        // Remove all offscreen items.\n        items.trimPrefix { !$0.isOnScreen }\n\n        let maxX = if let rightArea = screen.auxiliaryTopRightArea {\n            max(rightArea.minX + 20, applicationMenuFrame.maxX)\n        } else {\n            applicationMenuFrame.maxX\n        }\n\n        // Remove items until we have enough room to show this item.\n        items.trimPrefix { $0.frame.minX - item.frame.width <= maxX }\n\n        guard let targetItem = items.first else {\n            let alert = NSAlert()\n            alert.messageText = \"Not enough room to show \\\"\\(item.displayName)\\\"\"\n            alert.runModal()\n            return\n        }\n\n        let initialWindows = WindowInfo.getOnScreenWindows()\n\n        Task {\n            if clickWhenFinished {\n                do {\n                    try await slowMove(item: item, to: .leftOfItem(targetItem))\n                    try await click(item: item, with: mouseButton)\n                } catch {\n                    Logger.itemManager.error(\"ERROR: \\(error)\")\n                }\n            } else {\n                do {\n                    try await move(item: item, to: .leftOfItem(targetItem))\n                } catch {\n                    Logger.itemManager.error(\"ERROR: \\(error)\")\n                }\n            }\n\n            try? await Task.sleep(for: .milliseconds(100))\n\n            let currentWindows = WindowInfo.getOnScreenWindows()\n\n            let shownInterfaceWindow = currentWindows.first { currentWindow in\n                currentWindow.ownerPID == item.ownerPID &&\n                !initialWindows.contains { initialWindow in\n                    currentWindow.windowID == initialWindow.windowID\n                }\n            }\n\n            let context = TempShownItemContext(\n                info: item.info,\n                returnDestination: destination,\n                shownInterfaceWindow: shownInterfaceWindow\n            )\n            tempShownItemContexts.append(context)\n            runTempShownItemTimer(for: appState.settingsManager.advancedSettingsManager.tempShowInterval)\n        }\n    }\n\n    /// Rehides all temporarily shown items.\n    ///\n    /// If an item is currently showing its interface, this method waits for the\n    /// interface to close before hiding the items.\n    func rehideTempShownItems() async {\n        itemMoveCount += 1\n        defer {\n            itemMoveCount -= 1\n        }\n\n        guard !tempShownItemContexts.isEmpty else {\n            return\n        }\n\n        guard !isMouseButtonDown else {\n            Logger.itemManager.debug(\"Mouse button is down, so waiting to rehide\")\n            runTempShownItemTimer(for: 3)\n            return\n        }\n        guard !tempShownItemContexts.contains(where: { $0.isShowingInterface }) else {\n            Logger.itemManager.debug(\"Menu bar item interface is shown, so waiting to rehide\")\n            runTempShownItemTimer(for: 3)\n            return\n        }\n\n        Logger.itemManager.info(\"Rehiding temporarily shown items\")\n\n        var failedContexts = [TempShownItemContext]()\n\n        let items = MenuBarItem.getMenuBarItems(onScreenOnly: false, activeSpaceOnly: true)\n\n        MouseCursor.hide()\n\n        defer {\n            MouseCursor.show()\n        }\n\n        while let context = tempShownItemContexts.popLast() {\n            guard let item = items.first(where: { $0.info == context.info }) else {\n                continue\n            }\n            do {\n                try await move(item: item, to: context.returnDestination)\n            } catch {\n                Logger.itemManager.error(\"Failed to rehide \\(item.logString) (error: \\(error))\")\n                failedContexts.append(context)\n            }\n        }\n\n        if failedContexts.isEmpty {\n            tempShownItemsTimer?.invalidate()\n            tempShownItemsTimer = nil\n        } else {\n            tempShownItemContexts = failedContexts\n            Logger.itemManager.warning(\"Some items failed to rehide\")\n            runTempShownItemTimer(for: 3)\n        }\n    }\n\n    /// Removes a temporarily shown item from the cache.\n    ///\n    /// This ensures that the item will _not_ be returned to its previous location.\n    func removeTempShownItemFromCache(with info: MenuBarItemInfo) {\n        tempShownItemContexts.removeAll { $0.info == info }\n    }\n}\n\n// MARK: - Arrange Items\n\nextension MenuBarItemManager {\n    /// Enforces the order of the given control items, ensuring that the always-hidden\n    /// control item stays to the left of the hidden control item.\n    ///\n    /// - Parameters:\n    ///   - hiddenControlItem: A menu bar item that represents the control item for the\n    ///     hidden section.\n    ///   - alwaysHiddenControlItem: A menu bar item that represents the control item\n    ///     for the always-hidden section.\n    func enforceControlItemOrder(hiddenControlItem: MenuBarItem, alwaysHiddenControlItem: MenuBarItem) async throws {\n        guard !isMouseButtonDown else {\n            Logger.itemManager.debug(\"Mouse button is down, so will not enforce control item order\")\n            return\n        }\n        guard !mouseHasRecentlyMoved else {\n            Logger.itemManager.debug(\"Mouse has recently moved, so will not enforce control item order\")\n            return\n        }\n        if hiddenControlItem.frame.maxX <= alwaysHiddenControlItem.frame.minX {\n            Logger.itemManager.info(\"Arranging menu bar items\")\n            try await slowMove(item: alwaysHiddenControlItem, to: .leftOfItem(hiddenControlItem))\n        }\n    }\n}\n\n// MARK: - Menu Bar Item Event Helper Types\n\n/// Button states for menu bar item events.\nprivate enum MenuBarItemEventButtonState {\n    case leftMouseDown\n    case leftMouseUp\n    case rightMouseDown\n    case rightMouseUp\n    case otherMouseDown\n    case otherMouseUp\n}\n\n/// Event types for menu bar item events.\nprivate enum MenuBarItemEventType {\n    /// The event type for moving a menu bar item.\n    case move(MenuBarItemEventButtonState)\n\n    /// The event type for clicking a menu bar item.\n    case click(MenuBarItemEventButtonState)\n\n    /// The button state of this event type.\n    var buttonState: MenuBarItemEventButtonState {\n        switch self {\n        case .move(let state), .click(let state): state\n        }\n    }\n\n    /// This event type's equivalent CGEventType.\n    var cgEventType: CGEventType {\n        switch buttonState {\n        case .leftMouseDown: .leftMouseDown\n        case .leftMouseUp: .leftMouseUp\n        case .rightMouseDown: .rightMouseDown\n        case .rightMouseUp: .rightMouseUp\n        case .otherMouseDown: .otherMouseDown\n        case .otherMouseUp: .otherMouseUp\n        }\n    }\n\n    /// The event flags for this event type.\n    var cgEventFlags: CGEventFlags {\n        switch self {\n        case .move(.leftMouseDown): .maskCommand\n        case .move, .click: []\n        }\n    }\n\n    /// The mouse button for this event type.\n    var mouseButton: CGMouseButton {\n        switch buttonState {\n        case .leftMouseDown, .leftMouseUp: .left\n        case .rightMouseDown, .rightMouseUp: .right\n        case .otherMouseDown, .otherMouseUp: .center\n        }\n    }\n}\n\n// MARK: - CGEventField Helpers\n\nprivate extension CGEventField {\n    /// Key to access a field that contains the event's window identifier.\n    static let windowID = CGEventField(rawValue: 0x33)! // swiftlint:disable:this force_unwrapping\n\n    /// An array of integer event fields that can be used to compare menu bar item events.\n    static let menuBarItemEventFields: [CGEventField] = [\n        .eventSourceUserData,\n        .mouseEventWindowUnderMousePointer,\n        .mouseEventWindowUnderMousePointerThatCanHandleThisEvent,\n        .windowID,\n    ]\n}\n\n// MARK: - CGEventFilterMask Helpers\n\nprivate extension CGEventFilterMask {\n    /// Specifies that all events should be permitted during event suppression states.\n    static let permitAllEvents: CGEventFilterMask = [\n        .permitLocalMouseEvents,\n        .permitLocalKeyboardEvents,\n        .permitSystemDefinedEvents,\n    ]\n}\n\n// MARK: - CGEventType Helpers\n\nprivate extension CGEventType {\n    /// A string to use for logging purposes.\n    var logString: String {\n        switch self {\n        case .null: \"null event\"\n        case .leftMouseDown: \"leftMouseDown event\"\n        case .leftMouseUp: \"leftMouseUp event\"\n        case .rightMouseDown: \"rightMouseDown event\"\n        case .rightMouseUp: \"rightMouseUp event\"\n        case .mouseMoved: \"mouseMoved event\"\n        case .leftMouseDragged: \"leftMouseDragged event\"\n        case .rightMouseDragged: \"rightMouseDragged event\"\n        case .keyDown: \"keyDown event\"\n        case .keyUp: \"keyUp event\"\n        case .flagsChanged: \"flagsChanged event\"\n        case .scrollWheel: \"scrollWheel event\"\n        case .tabletPointer: \"tabletPointer event\"\n        case .tabletProximity: \"tabletProximity event\"\n        case .otherMouseDown: \"otherMouseDown event\"\n        case .otherMouseUp: \"otherMouseUp event\"\n        case .otherMouseDragged: \"otherMouseDragged event\"\n        case .tapDisabledByTimeout: \"tapDisabledByTimeout event\"\n        case .tapDisabledByUserInput: \"tapDisabledByUserInput event\"\n        @unknown default: \"unknown event\"\n        }\n    }\n}\n\n// MARK: - CGMouseButton Helpers\n\nprivate extension CGMouseButton {\n    /// A string to use for logging purposes.\n    var logString: String {\n        switch self {\n        case .left: \"left mouse button\"\n        case .right: \"right mouse button\"\n        case .center: \"center mouse button\"\n        @unknown default: \"unknown mouse button\"\n        }\n    }\n\n    /// The equivalent down and up button states for menu bar item click events.\n    var buttonStates: (down: MenuBarItemEventButtonState, up: MenuBarItemEventButtonState) {\n        switch self {\n        case .left: (.leftMouseDown, .leftMouseUp)\n        case .right: (.rightMouseDown, .rightMouseUp)\n        default: (.otherMouseDown, .otherMouseUp)\n        }\n    }\n}\n\n// MARK: - CGEvent Constructor\n\nprivate extension CGEvent {\n    /// Returns an event that can be sent to the given menu bar item.\n    ///\n    /// - Parameters:\n    ///   - type: The type of the event.\n    ///   - location: The location of the event. Does not need to be within the bounds of the item.\n    ///   - item: The target item of the event.\n    ///   - pid: The target process identifier of the event. Does not need to be the item's `ownerPID`.\n    ///   - source: The source of the event.\n    class func menuBarItemEvent(type: MenuBarItemEventType, location: CGPoint, item: MenuBarItem, pid: pid_t, source: CGEventSource) -> CGEvent? {\n        let mouseType = type.cgEventType\n        let mouseButton = type.mouseButton\n\n        guard let event = CGEvent(mouseEventSource: source, mouseType: mouseType, mouseCursorPosition: location, mouseButton: mouseButton) else {\n            return nil\n        }\n\n        event.flags = type.cgEventFlags\n\n        let targetPID = Int64(pid)\n        let userData = Int64(truncatingIfNeeded: Int(bitPattern: ObjectIdentifier(event)))\n        let windowID = Int64(item.windowID)\n\n        event.setIntegerValueField(.eventTargetUnixProcessID, value: targetPID)\n        event.setIntegerValueField(.eventSourceUserData, value: userData)\n        event.setIntegerValueField(.mouseEventWindowUnderMousePointer, value: windowID)\n        event.setIntegerValueField(.mouseEventWindowUnderMousePointerThatCanHandleThisEvent, value: windowID)\n        event.setIntegerValueField(.windowID, value: windowID)\n\n        if case .click = type {\n            event.setIntegerValueField(.mouseEventClickState, value: 1)\n        }\n\n        return event\n    }\n}\n\n// MARK: - Logger\n\nprivate extension Logger {\n    /// The logger to use for the menu bar item manager.\n    static let itemManager = Logger(category: \"MenuBarItemManager\")\n}\n"
  },
  {
    "path": "Ice/MenuBar/MenuBarManager.swift",
    "content": "//\n//  MenuBarManager.swift\n//  Ice\n//\n\nimport AXSwift\nimport Combine\nimport SwiftUI\n\n/// Manager for the state of the menu bar.\n@MainActor\nfinal class MenuBarManager: ObservableObject {\n    /// Information for the menu bar's average color.\n    @Published private(set) var averageColorInfo: MenuBarAverageColorInfo?\n\n    /// A Boolean value that indicates whether the menu bar is either always hidden\n    /// by the system, or automatically hidden and shown by the system based on the\n    /// location of the mouse.\n    @Published private(set) var isMenuBarHiddenBySystem = false\n\n    /// A Boolean value that indicates whether the menu bar is hidden by the system\n    /// according to a value stored in UserDefaults.\n    @Published private(set) var isMenuBarHiddenBySystemUserDefaults = false\n\n    /// The shared app state.\n    private weak var appState: AppState?\n\n    /// Storage for internal observers.\n    private var cancellables = Set<AnyCancellable>()\n\n    /// A Boolean value that indicates whether the application menus are hidden.\n    private var isHidingApplicationMenus = false\n\n    /// The managed sections in the menu bar.\n    private(set) var sections = [MenuBarSection]()\n\n    /// The panel that contains the Ice Bar interface.\n    let iceBarPanel: IceBarPanel\n\n    /// The panel that contains the menu bar search interface.\n    let searchPanel: MenuBarSearchPanel\n\n    /// A Boolean value that indicates whether the manager can update its stored\n    /// information for the menu bar's average color.\n    private var canUpdateAverageColorInfo: Bool {\n        appState?.settingsWindow?.isVisible == true\n    }\n\n    /// Initializes a new menu bar manager instance.\n    init(appState: AppState) {\n        self.iceBarPanel = IceBarPanel(appState: appState)\n        self.searchPanel = MenuBarSearchPanel(appState: appState)\n        self.appState = appState\n    }\n\n    /// Performs the initial setup of the menu bar manager.\n    func performSetup() {\n        initializeSections()\n        configureCancellables()\n        iceBarPanel.performSetup()\n    }\n\n    /// Performs the initial setup of the menu bar manager's sections.\n    private func initializeSections() {\n        // Make sure initialization can only happen once.\n        guard sections.isEmpty else {\n            Logger.menuBarManager.warning(\"Sections already initialized\")\n            return\n        }\n\n        guard let appState else {\n            Logger.menuBarManager.error(\"Error initializing menu bar sections: Missing app state\")\n            return\n        }\n\n        sections = [\n            MenuBarSection(name: .visible, appState: appState),\n            MenuBarSection(name: .hidden, appState: appState),\n            MenuBarSection(name: .alwaysHidden, appState: appState),\n        ]\n    }\n\n    /// Configures the internal observers for the manager.\n    private func configureCancellables() {\n        var c = Set<AnyCancellable>()\n\n        NSApp.publisher(for: \\.currentSystemPresentationOptions)\n            .receive(on: DispatchQueue.main)\n            .sink { [weak self] options in\n                guard let self else {\n                    return\n                }\n                let hidden = options.contains(.hideMenuBar) || options.contains(.autoHideMenuBar)\n                isMenuBarHiddenBySystem = hidden\n            }\n            .store(in: &c)\n\n        if\n            let hiddenSection = section(withName: .alwaysHidden),\n            let window = hiddenSection.controlItem.window\n        {\n            window.publisher(for: \\.frame)\n                .map { $0.origin.y }\n                .removeDuplicates()\n                .receive(on: DispatchQueue.main)\n                .sink { [weak self] _ in\n                    guard\n                        let self,\n                        let isMenuBarHidden = Defaults.globalDomain[\"_HIHideMenuBar\"] as? Bool\n                    else {\n                        return\n                    }\n                    isMenuBarHiddenBySystemUserDefaults = isMenuBarHidden\n                }\n                .store(in: &c)\n        }\n\n        // Handle the `focusedApp` rehide strategy.\n        NSWorkspace.shared.publisher(for: \\.frontmostApplication)\n            .receive(on: DispatchQueue.main)\n            .sink { [weak self] _ in\n                if\n                    let self,\n                    let appState,\n                    case .focusedApp = appState.settingsManager.generalSettingsManager.rehideStrategy,\n                    let hiddenSection = section(withName: .hidden),\n                    !appState.eventManager.isMouseInsideMenuBar\n                {\n                    Task {\n                        try await Task.sleep(for: .seconds(0.1))\n                        hiddenSection.hide()\n                    }\n                }\n            }\n            .store(in: &c)\n\n        appState?.settingsWindow?.publisher(for: \\.isVisible)\n            .receive(on: DispatchQueue.main)\n            .sink { [weak self] _ in\n                self?.updateAverageColorInfo()\n            }\n            .store(in: &c)\n\n        Timer.publish(every: 5, on: .main, in: .default)\n            .autoconnect()\n            .sink { [weak self] _ in\n                self?.updateAverageColorInfo()\n            }\n            .store(in: &c)\n\n        // Hide application menus when a section is shown (if applicable).\n        Publishers.MergeMany(sections.map { $0.controlItem.$state })\n            .receive(on: DispatchQueue.main)\n            .sink { [weak self] _ in\n                guard\n                    let self,\n                    let appState\n                else {\n                    return\n                }\n\n                // Don't continue if:\n                //   * The \"HideApplicationMenus\" setting isn't enabled.\n                //   * The menu bar is hidden by the system.\n                //   * The active space is fullscreen.\n                //   * The settings window is visible.\n                guard\n                    appState.settingsManager.advancedSettingsManager.hideApplicationMenus,\n                    !isMenuBarHiddenBySystem,\n                    !appState.isActiveSpaceFullscreen,\n                    appState.settingsWindow?.isVisible == false\n                else {\n                    return\n                }\n\n                if sections.contains(where: { $0.controlItem.state == .showItems }) {\n                    guard let screen = NSScreen.main else {\n                        return\n                    }\n\n                    let displayID = screen.displayID\n\n                    // Get the application menu frame for the display.\n                    guard let applicationMenuFrame = getApplicationMenuFrame(for: displayID) else {\n                        return\n                    }\n\n                    // Get all items.\n                    var items = MenuBarItem.getMenuBarItems(on: displayID, onScreenOnly: false, activeSpaceOnly: true)\n\n                    // Filter the items down according to the currently enabled/shown sections.\n                    if\n                        let alwaysHiddenSection = section(withName: .alwaysHidden),\n                        alwaysHiddenSection.isEnabled\n                    {\n                        if alwaysHiddenSection.controlItem.state == .hideItems {\n                            if let alwaysHiddenControlItem = items.firstIndex(matching: .alwaysHiddenControlItem).map({ items.remove(at: $0) }) {\n                                items.trimPrefix { $0.frame.maxX <= alwaysHiddenControlItem.frame.minX }\n                            }\n                        }\n                    } else {\n                        if let hiddenControlItem = items.firstIndex(matching: .hiddenControlItem).map({ items.remove(at: $0) }) {\n                            items.trimPrefix { $0.frame.maxX <= hiddenControlItem.frame.minX }\n                        }\n                    }\n\n                    // Get the leftmost item on the screen.\n                    guard let leftmostItem = items.min(by: { $0.frame.minX < $1.frame.minX }) else {\n                        return\n                    }\n\n                    // If the minX of the item is less than or equal to the maxX of the\n                    // application menu frame, activate the app to hide the menu.\n                    if leftmostItem.frame.minX <= applicationMenuFrame.maxX {\n                        hideApplicationMenus()\n                    }\n                } else if isHidingApplicationMenus {\n                    showApplicationMenus()\n                }\n            }\n            .store(in: &c)\n\n        cancellables = c\n    }\n\n    /// Updates the ``averageColorInfo`` property with the current average color\n    /// of the menu bar.\n    func updateAverageColorInfo() {\n        guard\n            canUpdateAverageColorInfo,\n            let screen = appState?.settingsWindow?.screen\n        else {\n            return\n        }\n\n        let image: CGImage?\n        let source: MenuBarAverageColorInfo.Source\n\n        let windows = WindowInfo.getOnScreenWindows(excludeDesktopWindows: false)\n        let displayID = screen.displayID\n\n        if let window = WindowInfo.getMenuBarWindow(from: windows, for: displayID) {\n            var bounds = window.frame\n            bounds.size.height = 1\n            bounds.origin.x = bounds.maxX - (bounds.width / 4)\n            bounds.size.width /= 4\n\n            image = ScreenCapture.captureWindow(window.windowID, screenBounds: bounds, option: .nominalResolution)\n            source = .menuBarWindow\n        } else if let window = WindowInfo.getWallpaperWindow(from: windows, for: displayID) {\n            var bounds = window.frame\n            bounds.size.height = 1\n            bounds.origin.x = bounds.midX\n            bounds.size.width /= 2\n\n            image = ScreenCapture.captureWindow(window.windowID, screenBounds: bounds, option: .nominalResolution)\n            source = .desktopWallpaper\n        } else {\n            return\n        }\n\n        guard\n            let image,\n            let color = image.averageColor(makeOpaque: true)\n        else {\n            return\n        }\n\n        let info = MenuBarAverageColorInfo(color: color, source: source)\n\n        if averageColorInfo != info {\n            averageColorInfo = info\n        }\n    }\n\n    /// Returns a Boolean value that indicates whether the given display\n    /// has a valid menu bar.\n    func hasValidMenuBar(in windows: [WindowInfo], for display: CGDirectDisplayID) -> Bool {\n        guard let menuBarWindow = WindowInfo.getMenuBarWindow(from: windows, for: display) else {\n            return false\n        }\n        let position = menuBarWindow.frame.origin\n        do {\n            let uiElement = try systemWideElement.elementAtPosition(Float(position.x), Float(position.y))\n            return try uiElement?.role() == .menuBar\n        } catch {\n            return false\n        }\n    }\n\n    /// Returns the frame of the application menu for the given display.\n    func getApplicationMenuFrame(for displayID: CGDirectDisplayID) -> CGRect? {\n        let displayBounds = CGDisplayBounds(displayID)\n\n        guard\n            let menuBar = try? systemWideElement.elementAtPosition(Float(displayBounds.origin.x), Float(displayBounds.origin.y)),\n            let role = try? menuBar.role(),\n            role == .menuBar,\n            let items: [UIElement] = try? menuBar.arrayAttribute(.children)?.filter({ (try? $0.attribute(.enabled)) == true })\n        else {\n            return nil\n        }\n\n        let itemFrames = items.lazy.compactMap { try? $0.attribute(.frame) as CGRect? }\n        let applicationMenuFrame = itemFrames.reduce(.null, CGRectUnion)\n\n        if applicationMenuFrame.width <= 0 {\n            return nil\n        }\n\n        // The Accessibility API returns the menu bar for the active screen, regardless of the\n        // display origin used. This workaround prevents an incorrect frame from being returned\n        // for inactive displays in multi-display setups where one display has a notch.\n        if\n            let mainScreen = NSScreen.main,\n            let thisScreen = NSScreen.screens.first(where: { $0.displayID == displayID }),\n            thisScreen != mainScreen,\n            let notchedScreen = NSScreen.screens.first(where: { $0.hasNotch }),\n            let leftArea = notchedScreen.auxiliaryTopLeftArea,\n            applicationMenuFrame.width >= leftArea.maxX\n        {\n            return nil\n        }\n\n        return applicationMenuFrame\n    }\n\n    /// Shows the right-click menu.\n    func showRightClickMenu(at point: CGPoint) {\n        let menu = NSMenu(title: \"Ice\")\n\n        let editItem = NSMenuItem(\n            title: \"Edit Menu Bar Appearance…\",\n            action: #selector(showAppearanceEditorPopover),\n            keyEquivalent: \"\"\n        )\n        editItem.target = self\n        menu.addItem(editItem)\n\n        menu.addItem(.separator())\n\n        let settingsItem = NSMenuItem(\n            title: \"Ice Settings…\",\n            action: #selector(AppDelegate.openSettingsWindow),\n            keyEquivalent: \",\"\n        )\n        menu.addItem(settingsItem)\n\n        menu.popUp(positioning: nil, at: point, in: nil)\n    }\n\n    /// Hides the application menus.\n    func hideApplicationMenus() {\n        guard let appState else {\n            Logger.menuBarManager.error(\"Error hiding application menus: Missing app state\")\n            return\n        }\n        Logger.menuBarManager.info(\"Hiding application menus\")\n        appState.activate(withPolicy: .regular)\n        isHidingApplicationMenus = true\n    }\n\n    /// Shows the application menus.\n    func showApplicationMenus() {\n        guard let appState else {\n            Logger.menuBarManager.error(\"Error showing application menus: Missing app state\")\n            return\n        }\n        Logger.menuBarManager.info(\"Showing application menus\")\n        appState.deactivate(withPolicy: .accessory)\n        isHidingApplicationMenus = false\n    }\n\n    /// Toggles the visibility of the application menus.\n    func toggleApplicationMenus() {\n        if isHidingApplicationMenus {\n            showApplicationMenus()\n        } else {\n            hideApplicationMenus()\n        }\n    }\n\n    /// Shows the appearance editor popover, centered under the menu bar.\n    @objc private func showAppearanceEditorPopover() {\n        guard let appState else {\n            Logger.menuBarManager.error(\"Error showing appearance editor popover: Missing app state\")\n            return\n        }\n        let panel = MenuBarAppearanceEditorPanel(appState: appState)\n        panel.orderFrontRegardless()\n        panel.showAppearanceEditorPopover()\n    }\n\n    /// Returns the menu bar section with the given name.\n    func section(withName name: MenuBarSection.Name) -> MenuBarSection? {\n        sections.first { $0.name == name }\n    }\n}\n\n// MARK: MenuBarManager: BindingExposable\nextension MenuBarManager: BindingExposable { }\n\n// MARK: - MenuBarAverageColorInfo\n\n/// Information for the menu bar's average color.\nstruct MenuBarAverageColorInfo: Hashable {\n    enum Source: Hashable {\n        case menuBarWindow\n        case desktopWallpaper\n    }\n\n    var color: CGColor\n    var source: Source\n}\n\n// MARK: - Logger\nprivate extension Logger {\n    /// Logger to use for the menu bar manager.\n    static let menuBarManager = Logger(category: \"MenuBarManager\")\n}\n"
  },
  {
    "path": "Ice/MenuBar/MenuBarSection.swift",
    "content": "//\n//  MenuBarSection.swift\n//  Ice\n//\n\nimport Cocoa\n\n/// A representation of a section in a menu bar.\n@MainActor\nfinal class MenuBarSection {\n    /// The name of a menu bar section.\n    enum Name: CaseIterable {\n        case visible\n        case hidden\n        case alwaysHidden\n\n        /// A string to show in the interface.\n        var displayString: String {\n            switch self {\n            case .visible: \"Visible\"\n            case .hidden: \"Hidden\"\n            case .alwaysHidden: \"Always-Hidden\"\n            }\n        }\n\n        /// A string to use for logging purposes.\n        var logString: String {\n            switch self {\n            case .visible: \"visible section\"\n            case .hidden: \"hidden section\"\n            case .alwaysHidden: \"always-hidden section\"\n            }\n        }\n    }\n\n    /// The name of the section.\n    let name: Name\n\n    /// The control item that manages the section.\n    let controlItem: ControlItem\n\n    /// The shared app state.\n    private weak var appState: AppState?\n\n    /// A timer that manages rehiding the section.\n    private var rehideTimer: Timer?\n\n    /// An event monitor that handles starting the rehide timer when the mouse\n    /// is outside of the menu bar.\n    private var rehideMonitor: UniversalEventMonitor?\n\n    /// A Boolean value that indicates whether the Ice Bar should be used.\n    private var useIceBar: Bool {\n        appState?.settingsManager.generalSettingsManager.useIceBar ?? false\n    }\n\n    /// A weak reference to the menu bar manager's Ice Bar panel.\n    private weak var iceBarPanel: IceBarPanel? {\n        appState?.menuBarManager.iceBarPanel\n    }\n\n    /// The best screen to show the Ice Bar on.\n    private weak var screenForIceBar: NSScreen? {\n        guard let appState else {\n            return nil\n        }\n        if appState.isActiveSpaceFullscreen {\n            return NSScreen.screenWithMouse ?? NSScreen.main\n        } else {\n            return NSScreen.main\n        }\n    }\n\n    /// A Boolean value that indicates whether the section is hidden.\n    var isHidden: Bool {\n        if useIceBar {\n            if controlItem.state == .showItems {\n                return false\n            }\n            switch name {\n            case .visible, .hidden:\n                return iceBarPanel?.currentSection != .hidden\n            case .alwaysHidden:\n                return iceBarPanel?.currentSection != .alwaysHidden\n            }\n        }\n        switch name {\n        case .visible, .hidden:\n            if iceBarPanel?.currentSection == .hidden {\n                return false\n            }\n            return controlItem.state == .hideItems\n        case .alwaysHidden:\n            if iceBarPanel?.currentSection == .alwaysHidden {\n                return false\n            }\n            return controlItem.state == .hideItems\n        }\n    }\n\n    /// A Boolean value that indicates whether the section is enabled.\n    var isEnabled: Bool {\n        if case .visible = name {\n            // The visible section should always be enabled.\n            return true\n        }\n        return controlItem.isAddedToMenuBar\n    }\n\n    /// Creates a section with the given name, control item, and app state.\n    init(name: Name, controlItem: ControlItem, appState: AppState) {\n        self.name = name\n        self.controlItem = controlItem\n        self.appState = appState\n    }\n\n    /// Creates a section with the given name and app state.\n    convenience init(name: Name, appState: AppState) {\n        let controlItem = switch name {\n        case .visible:\n            ControlItem(identifier: .iceIcon, appState: appState)\n        case .hidden:\n            ControlItem(identifier: .hidden, appState: appState)\n        case .alwaysHidden:\n            ControlItem(identifier: .alwaysHidden, appState: appState)\n        }\n        self.init(name: name, controlItem: controlItem, appState: appState)\n    }\n\n    /// Shows the section.\n    func show() {\n        guard\n            let appState,\n            isHidden\n        else {\n            return\n        }\n        guard controlItem.isAddedToMenuBar else {\n            // The section is disabled.\n            // TODO: Can we use isEnabled for this check?\n            return\n        }\n        switch name {\n        case .visible where useIceBar, .hidden where useIceBar:\n            Task {\n                if let screenForIceBar {\n                    await iceBarPanel?.show(section: .hidden, on: screenForIceBar)\n                }\n                for section in appState.menuBarManager.sections {\n                    section.controlItem.state = .hideItems\n                }\n            }\n        case .alwaysHidden where useIceBar:\n            Task {\n                if let screenForIceBar {\n                    await iceBarPanel?.show(section: .alwaysHidden, on: screenForIceBar)\n                }\n                for section in appState.menuBarManager.sections {\n                    section.controlItem.state = .hideItems\n                }\n            }\n        case .visible:\n            iceBarPanel?.close()\n            guard let hiddenSection = appState.menuBarManager.section(withName: .hidden) else {\n                return\n            }\n            controlItem.state = .showItems\n            hiddenSection.controlItem.state = .showItems\n        case .hidden:\n            iceBarPanel?.close()\n            guard let visibleSection = appState.menuBarManager.section(withName: .visible) else {\n                return\n            }\n            controlItem.state = .showItems\n            visibleSection.controlItem.state = .showItems\n        case .alwaysHidden:\n            iceBarPanel?.close()\n            guard\n                let hiddenSection = appState.menuBarManager.section(withName: .hidden),\n                let visibleSection = appState.menuBarManager.section(withName: .visible)\n            else {\n                return\n            }\n            controlItem.state = .showItems\n            hiddenSection.controlItem.state = .showItems\n            visibleSection.controlItem.state = .showItems\n        }\n        startRehideChecks()\n    }\n\n    /// Hides the section.\n    func hide() {\n        guard\n            let appState,\n            !isHidden\n        else {\n            return\n        }\n        iceBarPanel?.close()\n        switch name {\n        case _ where useIceBar:\n            for section in appState.menuBarManager.sections {\n                section.controlItem.state = .hideItems\n            }\n        case .visible:\n            guard\n                let hiddenSection = appState.menuBarManager.section(withName: .hidden),\n                let alwaysHiddenSection = appState.menuBarManager.section(withName: .alwaysHidden)\n            else {\n                return\n            }\n            controlItem.state = .hideItems\n            hiddenSection.controlItem.state = .hideItems\n            alwaysHiddenSection.controlItem.state = .hideItems\n        case .hidden:\n            guard\n                let visibleSection = appState.menuBarManager.section(withName: .visible),\n                let alwaysHiddenSection = appState.menuBarManager.section(withName: .alwaysHidden)\n            else {\n                return\n            }\n            controlItem.state = .hideItems\n            visibleSection.controlItem.state = .hideItems\n            alwaysHiddenSection.controlItem.state = .hideItems\n        case .alwaysHidden:\n            controlItem.state = .hideItems\n        }\n        appState.allowShowOnHover()\n        stopRehideChecks()\n    }\n\n    /// Toggles the visibility of the section.\n    func toggle() {\n        if isHidden {\n            show()\n        } else {\n            hide()\n        }\n    }\n\n    /// Starts running checks to determine when to rehide the section.\n    private func startRehideChecks() {\n        rehideTimer?.invalidate()\n        rehideMonitor?.stop()\n\n        guard\n            let appState,\n            appState.settingsManager.generalSettingsManager.autoRehide,\n            case .timed = appState.settingsManager.generalSettingsManager.rehideStrategy\n        else {\n            return\n        }\n\n        rehideMonitor = UniversalEventMonitor(mask: .mouseMoved) { [weak self] event in\n            guard\n                let self,\n                let screen = NSScreen.main\n            else {\n                return event\n            }\n            if NSEvent.mouseLocation.y < screen.visibleFrame.maxY {\n                if rehideTimer == nil {\n                    rehideTimer = .scheduledTimer(\n                        withTimeInterval: appState.settingsManager.generalSettingsManager.rehideInterval,\n                        repeats: false\n                    ) { [weak self] _ in\n                        guard\n                            let self,\n                            let screen = NSScreen.main\n                        else {\n                            return\n                        }\n                        if NSEvent.mouseLocation.y < screen.visibleFrame.maxY {\n                            Task {\n                                await self.hide()\n                            }\n                        } else {\n                            Task {\n                                await self.startRehideChecks()\n                            }\n                        }\n                    }\n                }\n            } else {\n                rehideTimer?.invalidate()\n                rehideTimer = nil\n            }\n            return event\n        }\n\n        rehideMonitor?.start()\n    }\n\n    /// Stops running checks to determine when to rehide the section.\n    private func stopRehideChecks() {\n        rehideTimer?.invalidate()\n        rehideMonitor?.stop()\n        rehideTimer = nil\n        rehideMonitor = nil\n    }\n}\n\n// MARK: MenuBarSection: BindingExposable\nextension MenuBarSection: BindingExposable { }\n\n// MARK: - Logger\nprivate extension Logger {\n    static let menuBarSection = Logger(category: \"MenuBarSection\")\n}\n"
  },
  {
    "path": "Ice/MenuBar/Search/MenuBarSearchPanel.swift",
    "content": "//\n//  MenuBarSearchPanel.swift\n//  Ice\n//\n\nimport Combine\nimport Ifrit\nimport SwiftUI\n\n/// A panel that contains the menu bar search interface.\nfinal class MenuBarSearchPanel: NSPanel {\n    /// The default screen to show the panel on.\n    static var defaultScreen: NSScreen? {\n        NSScreen.screenWithMouse ?? NSScreen.main\n    }\n\n    /// The shared app state.\n    private weak var appState: AppState?\n\n    /// Storage for internal observers.\n    private var cancellables = Set<AnyCancellable>()\n\n    /// Monitor for mouse down events.\n    private lazy var mouseDownMonitor = UniversalEventMonitor(\n        mask: [.leftMouseDown, .rightMouseDown, .otherMouseDown]\n    ) { [weak self, weak appState] event in\n        guard\n            let self,\n            let appState,\n            event.window !== self\n        else {\n            return event\n        }\n        if !appState.itemManager.isMovingItem {\n            close()\n        }\n        return event\n    }\n\n    /// Monitor for key down events.\n    private lazy var keyDownMonitor = UniversalEventMonitor(\n        mask: [.keyDown]\n    ) { [weak self] event in\n        if KeyCode(rawValue: Int(event.keyCode)) == .escape {\n            self?.close()\n            return nil\n        }\n        return event\n    }\n\n    /// Overridden to always be `true`.\n    override var canBecomeKey: Bool { true }\n\n    /// Creates a menu bar search panel with the given app state.\n    init(appState: AppState) {\n        super.init(\n            contentRect: .zero,\n            styleMask: [.titled, .fullSizeContentView, .nonactivatingPanel, .utilityWindow, .hudWindow],\n            backing: .buffered,\n            defer: false\n        )\n        self.appState = appState\n        self.titlebarAppearsTransparent = true\n        self.isMovableByWindowBackground = false\n        self.animationBehavior = .none\n        self.isFloatingPanel = true\n        self.level = .floating\n        self.collectionBehavior = [.fullScreenAuxiliary, .ignoresCycle, .moveToActiveSpace]\n        configureCancellables()\n    }\n\n    /// Configures the internal observers for the panel.\n    private func configureCancellables() {\n        var c = Set<AnyCancellable>()\n\n        NSApp.publisher(for: \\.effectiveAppearance)\n            .sink { [weak self] effectiveAppearance in\n                self?.appearance = effectiveAppearance\n            }\n            .store(in: &c)\n\n        // Close the panel when the active space changes, or when the screen parameters change.\n        Publishers.Merge(\n            NSWorkspace.shared.notificationCenter.publisher(for: NSWorkspace.activeSpaceDidChangeNotification),\n            NotificationCenter.default.publisher(for: NSApplication.didChangeScreenParametersNotification)\n        )\n        .sink { [weak self] _ in\n            self?.close()\n        }\n        .store(in: &c)\n\n        cancellables = c\n    }\n\n    /// Shows the search panel on the given screen.\n    func show(on screen: NSScreen) async {\n        guard let appState else {\n            return\n        }\n\n        // Important that we set the navigation state before updating the cache.\n        appState.navigationState.isSearchPresented = true\n\n        if ScreenCapture.cachedCheckPermissions() {\n            await appState.imageCache.updateCache()\n        }\n\n        let hostingView = MenuBarSearchHostingView(appState: appState, panel: self)\n        hostingView.setFrameSize(hostingView.intrinsicContentSize)\n        setFrame(hostingView.frame, display: true)\n\n        contentView = hostingView\n\n        // Calculate the top left position.\n        let topLeft = CGPoint(\n            x: screen.frame.midX - frame.width / 2,\n            y: screen.frame.midY + (frame.height / 2) + (screen.frame.height / 8)\n        )\n\n        cascadeTopLeft(from: topLeft)\n        makeKeyAndOrderFront(nil)\n\n        mouseDownMonitor.start()\n        keyDownMonitor.start()\n    }\n\n    /// Toggles the panel's visibility.\n    func toggle() async {\n        if isVisible {\n            close()\n        } else if let screen = MenuBarSearchPanel.defaultScreen {\n            await show(on: screen)\n        }\n    }\n\n    /// Dismisses the search panel.\n    override func close() {\n        super.close()\n        contentView = nil\n        mouseDownMonitor.stop()\n        keyDownMonitor.stop()\n        appState?.navigationState.isSearchPresented = false\n    }\n}\n\nprivate final class MenuBarSearchHostingView: NSHostingView<AnyView> {\n    override var safeAreaInsets: NSEdgeInsets {\n        NSEdgeInsets()\n    }\n\n    init(\n        appState: AppState,\n        panel: MenuBarSearchPanel\n    ) {\n        super.init(\n            rootView: MenuBarSearchContentView(closePanel: { [weak panel] in panel?.close() })\n                .environmentObject(appState.itemManager)\n                .environmentObject(appState.imageCache)\n                .erasedToAnyView()\n        )\n    }\n\n    @available(*, unavailable)\n    required init?(coder: NSCoder) {\n        fatalError(\"init(coder:) has not been implemented\")\n    }\n\n    @available(*, unavailable)\n    required init(rootView: AnyView) {\n        fatalError(\"init(rootView:) has not been implemented\")\n    }\n}\n\nprivate struct MenuBarSearchContentView: View {\n    private typealias ListItem = SectionedListItem<ItemID>\n\n    private enum ItemID: Hashable {\n        case header(MenuBarSection.Name)\n        case item(MenuBarItemInfo)\n    }\n\n    @EnvironmentObject var itemManager: MenuBarItemManager\n    @State private var searchText = \"\"\n    @State private var displayedItems = [SectionedListItem<ItemID>]()\n    @State private var selection: ItemID?\n    @FocusState private var searchFieldIsFocused: Bool\n\n    private let fuse = Fuse(threshold: 0.5)\n\n    let closePanel: () -> Void\n\n    var body: some View {\n        VStack(spacing: 0) {\n            TextField(text: $searchText, prompt: Text(\"Search menu bar items…\")) {\n                Text(\"Search menu bar items…\")\n            }\n            .labelsHidden()\n            .textFieldStyle(.plain)\n            .multilineTextAlignment(.leading)\n            .font(.system(size: 18))\n            .padding(15)\n            .focused($searchFieldIsFocused)\n\n            Divider()\n\n            SectionedList(selection: $selection, items: $displayedItems)\n                .contentPadding(8)\n                .scrollContentBackground(.hidden)\n\n            Divider()\n                .offset(y: 1)\n                .zIndex(1)\n\n            HStack {\n                SettingsButton {\n                    closePanel()\n                    itemManager.appState?.appDelegate?.openSettingsWindow()\n                }\n\n                Spacer()\n\n                if\n                    let selection,\n                    let item = menuBarItem(for: selection)\n                {\n                    ShowItemButton(item: item) {\n                        performAction(for: item)\n                    }\n                }\n            }\n            .padding(5)\n            .background(.thinMaterial)\n        }\n        .background {\n            VisualEffectView(material: .sheet, blendingMode: .behindWindow)\n                .opacity(0.5)\n        }\n        .frame(width: 600, height: 400)\n        .fixedSize()\n        .task {\n            searchFieldIsFocused = true\n        }\n        .onChange(of: searchText, initial: true) {\n            updateDisplayedItems()\n            selectFirstDisplayedItem()\n        }\n        .onChange(of: itemManager.itemCache, initial: true) {\n            updateDisplayedItems()\n        }\n    }\n\n    private func selectFirstDisplayedItem() {\n        selection = displayedItems.first { $0.isSelectable }?.id\n    }\n\n    private func updateDisplayedItems() {\n        let searchItems: [(listItem: ListItem, title: String)] = MenuBarSection.Name.allCases.reduce(into: []) { items, section in\n            if itemManager.appState?.menuBarManager.section(withName: section)?.isEnabled == false {\n                return\n            }\n\n            let headerItem = ListItem.header(id: .header(section)) {\n                Text(section.displayString)\n                    .fontWeight(.semibold)\n                    .foregroundStyle(.secondary)\n                    .frame(maxWidth: .infinity, alignment: .leading)\n                    .padding(.vertical, 10)\n            }\n            items.append((headerItem, section.displayString))\n\n            for item in itemManager.itemCache.managedItems(for: section).reversed() {\n                let listItem = ListItem.item(id: .item(item.info)) {\n                    performAction(for: item)\n                } content: {\n                    MenuBarSearchItemView(item: item)\n                }\n                items.append((listItem, item.displayName))\n            }\n        }\n\n        if searchText.isEmpty {\n            displayedItems = searchItems.map { $0.listItem }\n        } else {\n            let selectableItems = searchItems.compactMap { searchItem in\n                if searchItem.listItem.isSelectable {\n                    return searchItem\n                }\n                return nil\n            }\n            let results = fuse.searchSync(searchText, in: selectableItems.map { $0.title })\n            displayedItems = results.map { selectableItems[$0.index].listItem }\n        }\n    }\n\n    private func menuBarItem(for selection: ItemID) -> MenuBarItem? {\n        switch selection {\n        case .item(let info):\n            itemManager.itemCache.managedItems.first { $0.info == info }\n        case .header:\n            nil\n        }\n    }\n\n    private func performAction(for item: MenuBarItem) {\n        closePanel()\n        Task {\n            try await Task.sleep(for: .milliseconds(25))\n            itemManager.tempShowItem(item, clickWhenFinished: true, mouseButton: .left)\n        }\n    }\n}\n\nprivate struct BottomBarButton<Content: View>: View {\n    @State private var frame = CGRect.zero\n    @State private var isHovering = false\n    @State private var isPressed = false\n\n    let content: Content\n    let action: () -> Void\n\n    init(action: @escaping () -> Void, @ViewBuilder content: () -> Content) {\n        self.action = action\n        self.content = content()\n    }\n\n    var body: some View {\n        content\n            .padding(3)\n            .background {\n                RoundedRectangle(cornerRadius: 5, style: .circular)\n                    .fill(.regularMaterial)\n                    .brightness(0.25)\n                    .opacity(isPressed ? 0.5 : isHovering ? 0.25 : 0)\n            }\n            .contentShape(Rectangle())\n            .onHover { hovering in\n                isHovering = hovering\n            }\n            .simultaneousGesture(\n                DragGesture(minimumDistance: 0)\n                    .onChanged { value in\n                        isPressed = frame.contains(value.location)\n                    }\n                    .onEnded { value in\n                        isPressed = false\n                        if frame.contains(value.location) {\n                            action()\n                        }\n                    }\n            )\n            .onFrameChange(update: $frame)\n    }\n}\n\nprivate struct SettingsButton: View {\n    let action: () -> Void\n\n    var body: some View {\n        BottomBarButton(action: action) {\n            Image(.iceCubeStroke)\n                .resizable()\n                .aspectRatio(contentMode: .fit)\n                .frame(width: 18, height: 18)\n                .foregroundStyle(.secondary)\n                .padding(2)\n        }\n    }\n}\n\nprivate struct ShowItemButton: View {\n    let item: MenuBarItem\n    let action: () -> Void\n\n    var body: some View {\n        BottomBarButton(action: action) {\n            HStack {\n                Text(item.isOnScreen ? \"Click item\" : \"Show item\")\n                    .padding(.horizontal, 5)\n\n                Image(systemName: \"return\")\n                    .resizable()\n                    .aspectRatio(contentMode: .fit)\n                    .frame(width: 11, height: 11)\n                    .foregroundStyle(.secondary)\n                    .padding(.horizontal, 7)\n                    .padding(.vertical, 5)\n                    .background {\n                        RoundedRectangle(cornerRadius: 3, style: .circular)\n                            .fill(.regularMaterial)\n                            .brightness(0.25)\n                            .opacity(0.5)\n                    }\n            }\n        }\n    }\n}\n\nprivate let controlCenterIcon: NSImage? = {\n    guard let app = NSRunningApplication.runningApplications(withBundleIdentifier: \"com.apple.controlcenter\").first else {\n        return nil\n    }\n    return app.icon\n}()\n\nprivate struct MenuBarSearchItemView: View {\n    @EnvironmentObject var imageCache: MenuBarItemImageCache\n\n    let item: MenuBarItem\n\n    private var image: NSImage? {\n        guard\n            let image = imageCache.images[item.info]?.trimmingTransparentPixels(around: [.minXEdge, .maxXEdge]),\n            let screen = imageCache.screen\n        else {\n            return nil\n        }\n        let size = CGSize(\n            width: CGFloat(image.width) / screen.backingScaleFactor,\n            height: CGFloat(image.height) / screen.backingScaleFactor\n        )\n        return NSImage(cgImage: image, size: size)\n    }\n\n    private var appIcon: NSImage? {\n        if item.info.namespace == .systemUIServer {\n            controlCenterIcon\n        } else {\n            item.owningApplication?.icon\n        }\n    }\n\n    var body: some View {\n        HStack {\n            if let appIcon {\n                Image(nsImage: appIcon)\n                    .resizable()\n                    .aspectRatio(contentMode: .fit)\n                    .frame(width: 24, height: 24)\n            }\n            Text(item.displayName)\n            Spacer()\n            imageViewWithBackground\n        }\n        .padding(8)\n    }\n\n    @ViewBuilder\n    private var imageViewWithBackground: some View {\n        if let image {\n            ZStack {\n                RoundedRectangle(cornerRadius: 5, style: .circular)\n                    .fill(.regularMaterial)\n                    .brightness(0.25)\n                    .opacity(0.75)\n                    .frame(width: item.frame.width)\n                    .overlay {\n                        RoundedRectangle(cornerRadius: 5, style: .circular)\n                            .inset(by: 0.5)\n                            .stroke(lineWidth: 1)\n                            .foregroundStyle(.white)\n                            .opacity(0.15)\n                    }\n\n                Image(nsImage: image)\n                    .frame(height: 24)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Ice/MenuBar/Spacing/MenuBarItemSpacingManager.swift",
    "content": "//\n//  MenuBarItemSpacingManager.swift\n//  Ice\n//\n\nimport Cocoa\nimport Combine\n\n/// Manager for menu bar item spacing.\n@MainActor\nfinal class MenuBarItemSpacingManager {\n    /// UserDefaults keys.\n    private enum Key: String {\n        case spacing = \"NSStatusItemSpacing\"\n        case padding = \"NSStatusItemSelectionPadding\"\n\n        /// The default value for the key.\n        var defaultValue: Int {\n            switch self {\n            case .spacing: 16\n            case .padding: 16\n            }\n        }\n    }\n\n    /// An error that groups multiple failed app relaunches.\n    private struct GroupedRelaunchError: LocalizedError {\n        let failedApps: [String]\n\n        var errorDescription: String? {\n            \"The following applications failed to quit and were not restarted:\\n\" + failedApps.joined(separator: \"\\n\")\n        }\n\n        var recoverySuggestion: String? {\n            \"You may need to log out for the changes to take effect.\"\n        }\n    }\n\n    /// Delay before force terminating an app.\n    private let forceTerminateDelay = 1\n\n    /// The offset to apply to the default spacing and padding.\n    /// Does not take effect until ``applyOffset()`` is called.\n    var offset = 0\n\n    /// Runs a command with the given arguments.\n    private func runCommand(_ command: String, with arguments: [String]) async throws {\n        let process = Process()\n\n        process.executableURL = URL(filePath: \"/usr/bin/env\")\n        process.arguments = CollectionOfOne(command) + arguments\n\n        let task = Task.detached {\n            try process.run()\n            process.waitUntilExit()\n        }\n\n        return try await task.value\n    }\n\n    /// Removes the value for the specified key.\n    private func removeValue(forKey key: Key) async throws {\n        try await runCommand(\"defaults\", with: [\"-currentHost\", \"delete\", \"-globalDomain\", key.rawValue])\n    }\n\n    /// Sets the value for the specified key to the key's default value plus the given offset.\n    private func setOffset(_ offset: Int, forKey key: Key) async throws {\n        try await runCommand(\"defaults\", with: [\"-currentHost\", \"write\", \"-globalDomain\", key.rawValue, \"-int\", String(key.defaultValue + offset)])\n    }\n\n    /// Returns a log string for the given app.\n    private nonisolated func logString(for app: NSRunningApplication) -> String {\n        app.localizedName ?? app.bundleIdentifier ?? \"<NIL>\"\n    }\n\n    /// Asynchronously signals the given app to quit.\n    private func signalAppToQuit(_ app: NSRunningApplication) async throws {\n        if app.isTerminated {\n            Logger.spacing.debug(\"Application \\\"\\(logString(for: app))\\\" is already terminated\")\n            return\n        } else {\n            Logger.spacing.debug(\"Signaling application \\\"\\(logString(for: app))\\\" to quit\")\n        }\n\n        app.terminate()\n\n        var cancellable: AnyCancellable?\n        return try await withCheckedThrowingContinuation { continuation in\n            let timeoutTask = Task {\n                try await Task.sleep(for: .seconds(forceTerminateDelay))\n                if !app.isTerminated {\n                    Logger.spacing.debug(\"Application \\\"\\(logString(for: app))\\\" did not terminate within \\(forceTerminateDelay) seconds, attempting to force terminate\")\n                    app.forceTerminate()\n                }\n            }\n\n            cancellable = app.publisher(for: \\.isTerminated).sink { [weak self] isTerminated in\n                guard\n                    let self,\n                    isTerminated\n                else {\n                    return\n                }\n                timeoutTask.cancel()\n                cancellable?.cancel()\n                Logger.spacing.debug(\"Application \\\"\\(logString(for: app))\\\" terminated successfully\")\n                continuation.resume()\n            }\n        }\n    }\n\n    /// Asynchronously launches the app at the given URL.\n    private nonisolated func launchApp(at applicationURL: URL, bundleIdentifier: String) async throws {\n        if let app = NSWorkspace.shared.runningApplications.first(where: { $0.bundleIdentifier == bundleIdentifier }) {\n            Logger.spacing.debug(\"Application \\\"\\(logString(for: app))\\\" is already open, so skipping launch\")\n            return\n        }\n        let configuration = NSWorkspace.OpenConfiguration()\n        configuration.activates = false\n        configuration.addsToRecentItems = false\n        configuration.createsNewApplicationInstance = false\n        configuration.promptsUserIfNeeded = false\n        try await NSWorkspace.shared.openApplication(at: applicationURL, configuration: configuration)\n    }\n\n    /// Asynchronously relaunches the given app.\n    private func relaunchApp(_ app: NSRunningApplication) async throws {\n        struct RelaunchError: Error { }\n        guard\n            let url = app.bundleURL,\n            let bundleIdentifier = app.bundleIdentifier\n        else {\n            throw RelaunchError()\n        }\n        try await signalAppToQuit(app)\n        if app.isTerminated {\n            try await launchApp(at: url, bundleIdentifier: bundleIdentifier)\n        } else {\n            throw RelaunchError()\n        }\n    }\n\n    /// Applies the current ``offset``.\n    ///\n    /// - Note: Calling this restarts all apps with a menu bar item.\n    func applyOffset() async throws {\n        if offset == 0 {\n            try await removeValue(forKey: .spacing)\n            try await removeValue(forKey: .padding)\n        } else {\n            try await setOffset(offset, forKey: .spacing)\n            try await setOffset(offset, forKey: .padding)\n        }\n\n        try? await Task.sleep(for: .milliseconds(100))\n\n        let items = MenuBarItem.getMenuBarItems(onScreenOnly: false, activeSpaceOnly: true)\n        let pids = Set(items.map { $0.ownerPID })\n\n        var failedApps = [String]()\n\n        await withTaskGroup(of: Void.self) { group in\n            for pid in pids {\n                guard\n                    let app = NSRunningApplication(processIdentifier: pid),\n                    app.bundleIdentifier != \"com.apple.controlcenter\", // ControlCenter handles its own relaunch, so skip it.\n                    app != .current\n                else {\n                    break\n                }\n                group.addTask { @MainActor in\n                    do {\n                        try await self.relaunchApp(app)\n                    } catch {\n                        guard let name = app.localizedName else {\n                            return\n                        }\n                        if app.bundleIdentifier == \"com.apple.Spotlight\" {\n                            // Spotlight automatically relaunches, so only consider it a failure if it never quit.\n                            if\n                                let latestSpotlightInstance = NSRunningApplication.runningApplications(withBundleIdentifier: \"com.apple.Spotlight\").first,\n                                latestSpotlightInstance.processIdentifier == app.processIdentifier\n                            {\n                                failedApps.append(name)\n                            }\n                        } else {\n                            failedApps.append(name)\n                        }\n                    }\n                }\n            }\n        }\n\n        try? await Task.sleep(for: .milliseconds(100))\n\n        if let app = NSRunningApplication.runningApplications(withBundleIdentifier: \"com.apple.controlcenter\").first {\n            do {\n                try await signalAppToQuit(app)\n            } catch {\n                if let name = app.localizedName {\n                    failedApps.append(name)\n                }\n            }\n        }\n\n        if !failedApps.isEmpty {\n            throw GroupedRelaunchError(failedApps: failedApps)\n        }\n    }\n}\n\n// MARK: - Logger\nprivate extension Logger {\n    static let spacing = Logger(category: \"Spacing\")\n}\n"
  },
  {
    "path": "Ice/Permissions/Permission.swift",
    "content": "//\n//  Permission.swift\n//  Ice\n//\n\nimport AXSwift\nimport Combine\nimport Cocoa\nimport ScreenCaptureKit\n\n// MARK: - Permission\n\n/// An object that encapsulates the behavior of checking for and requesting\n/// a specific permission for the app.\n@MainActor\nclass Permission: ObservableObject, Identifiable {\n    /// A Boolean value that indicates whether the app has this permission.\n    @Published private(set) var hasPermission = false\n\n    /// The title of the permission.\n    let title: String\n    /// Descriptive details for the permission.\n    let details: [String]\n    /// A Boolean value that indicates if the app can work without this permission.\n    let isRequired: Bool\n\n    /// The URL of the settings pane to open.\n    private let settingsURL: URL?\n    /// The function that checks permissions.\n    private let check: () -> Bool\n    /// The function that requests permissions.\n    private let request: () -> Void\n\n    /// Observer that runs on a timer to check permissions.\n    private var timerCancellable: AnyCancellable?\n    /// Observer that observes the ``hasPermission`` property.\n    private var hasPermissionCancellable: AnyCancellable?\n\n    /// Creates a permission.\n    ///\n    /// - Parameters:\n    ///   - title: The title of the permission.\n    ///   - details: Descriptive details for the permission.\n    ///   - isRequired: A Boolean value that indicates if the app can work without this permission.\n    ///   - settingsURL: The URL of the settings pane to open.\n    ///   - check: A function that checks permissions.\n    ///   - request: A function that requests permissions.\n    init(\n        title: String,\n        details: [String],\n        isRequired: Bool,\n        settingsURL: URL?,\n        check: @escaping () -> Bool,\n        request: @escaping () -> Void\n    ) {\n        self.title = title\n        self.details = details\n        self.isRequired = isRequired\n        self.settingsURL = settingsURL\n        self.check = check\n        self.request = request\n        self.hasPermission = check()\n        configureCancellables()\n    }\n\n    /// Sets up the internal observers for the permission.\n    private func configureCancellables() {\n        timerCancellable = Timer.publish(every: 1, on: .main, in: .default)\n            .autoconnect()\n            .merge(with: Just(.now))\n            .sink { [weak self] _ in\n                guard let self else {\n                    return\n                }\n                hasPermission = check()\n            }\n    }\n\n    /// Performs the request and opens the System Settings app to the appropriate pane.\n    func performRequest() {\n        request()\n        if let settingsURL {\n            NSWorkspace.shared.open(settingsURL)\n        }\n    }\n\n    /// Asynchronously waits for the app to be granted this permission.\n    func waitForPermission() async {\n        configureCancellables()\n        guard !hasPermission else {\n            return\n        }\n        return await withCheckedContinuation { continuation in\n            hasPermissionCancellable = $hasPermission.sink { [weak self] hasPermission in\n                guard let self else {\n                    continuation.resume()\n                    return\n                }\n                if hasPermission {\n                    hasPermissionCancellable?.cancel()\n                    continuation.resume()\n                }\n            }\n        }\n    }\n\n    /// Stops running the permission check.\n    func stopCheck() {\n        timerCancellable?.cancel()\n        timerCancellable = nil\n        hasPermissionCancellable?.cancel()\n        hasPermissionCancellable = nil\n    }\n}\n\n// MARK: - AccessibilityPermission\n\nfinal class AccessibilityPermission: Permission {\n    init() {\n        super.init(\n            title: \"Accessibility\",\n            details: [\n                \"Get real-time information about the menu bar.\",\n                \"Arrange menu bar items.\",\n            ],\n            isRequired: true,\n            settingsURL: nil,\n            check: {\n                checkIsProcessTrusted()\n            },\n            request: {\n                checkIsProcessTrusted(prompt: true)\n            }\n        )\n    }\n}\n\n// MARK: - ScreenRecordingPermission\n\nfinal class ScreenRecordingPermission: Permission {\n    init() {\n        super.init(\n            title: \"Screen Recording\",\n            details: [\n                \"Edit the menu bar's appearance.\",\n                \"Display images of individual menu bar items.\",\n            ],\n            isRequired: false,\n            settingsURL: URL(string: \"x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture\"),\n            check: {\n                ScreenCapture.checkPermissions()\n            },\n            request: {\n                ScreenCapture.requestPermissions()\n            }\n        )\n    }\n}\n"
  },
  {
    "path": "Ice/Permissions/PermissionsManager.swift",
    "content": "//\n//  PermissionsManager.swift\n//  Ice\n//\n\nimport Combine\nimport Foundation\n\n/// A type that manages the permissions of the app.\n@MainActor\nfinal class PermissionsManager: ObservableObject {\n    /// The state of the granted permissions for the app.\n    enum PermissionsState {\n        case missingPermissions\n        case hasAllPermissions\n        case hasRequiredPermissions\n    }\n\n    /// The state of the granted permissions for the app.\n    @Published var permissionsState = PermissionsState.missingPermissions\n\n    let accessibilityPermission: AccessibilityPermission\n\n    let screenRecordingPermission: ScreenRecordingPermission\n\n    let allPermissions: [Permission]\n\n    private(set) weak var appState: AppState?\n\n    private var cancellables = Set<AnyCancellable>()\n\n    var requiredPermissions: [Permission] {\n        allPermissions.filter { $0.isRequired }\n    }\n\n    init(appState: AppState) {\n        self.appState = appState\n        self.accessibilityPermission = AccessibilityPermission()\n        self.screenRecordingPermission = ScreenRecordingPermission()\n        self.allPermissions = [\n            accessibilityPermission,\n            screenRecordingPermission,\n        ]\n        configureCancellables()\n    }\n\n    private func configureCancellables() {\n        var c = Set<AnyCancellable>()\n\n        Publishers.Merge(\n            accessibilityPermission.$hasPermission.mapToVoid(),\n            screenRecordingPermission.$hasPermission.mapToVoid()\n        )\n        .receive(on: DispatchQueue.main)\n        .sink { [weak self] in\n            guard let self else {\n                return\n            }\n            if allPermissions.allSatisfy({ $0.hasPermission }) {\n                permissionsState = .hasAllPermissions\n            } else if requiredPermissions.allSatisfy({ $0.hasPermission }) {\n                permissionsState = .hasRequiredPermissions\n            } else {\n                permissionsState = .missingPermissions\n            }\n        }\n        .store(in: &c)\n\n        cancellables = c\n    }\n\n    /// Stops running all permissions checks.\n    func stopAllChecks() {\n        for permission in allPermissions {\n            permission.stopCheck()\n        }\n    }\n}\n"
  },
  {
    "path": "Ice/Permissions/PermissionsView.swift",
    "content": "//\n//  PermissionsView.swift\n//  Ice\n//\n\nimport SwiftUI\n\nstruct PermissionsView: View {\n    @EnvironmentObject var permissionsManager: PermissionsManager\n    @Environment(\\.openWindow) private var openWindow\n\n    private var continueButtonText: LocalizedStringKey {\n        if case .hasRequiredPermissions = permissionsManager.permissionsState {\n            \"Continue in Limited Mode\"\n        } else {\n            \"Continue\"\n        }\n    }\n\n    private var continueButtonForegroundStyle: some ShapeStyle {\n        if case .hasRequiredPermissions = permissionsManager.permissionsState {\n            AnyShapeStyle(.yellow)\n        } else {\n            AnyShapeStyle(.primary)\n        }\n    }\n\n    var body: some View {\n        VStack(spacing: 0) {\n            headerView\n                .padding(.vertical)\n\n            explanationView\n            permissionsGroupStack\n\n            footerView\n                .padding(.vertical)\n        }\n        .padding(.horizontal)\n        .fixedSize()\n        .readWindow { window in\n            guard let window else {\n                return\n            }\n            window.styleMask.remove([.closable, .miniaturizable])\n            if let contentView = window.contentView {\n                with(contentView.safeAreaInsets) { insets in\n                    insets.bottom = -insets.bottom\n                    insets.left = -insets.left\n                    insets.right = -insets.right\n                    insets.top = -insets.top\n                    contentView.additionalSafeAreaInsets = insets\n                }\n            }\n        }\n    }\n\n    @ViewBuilder\n    private var headerView: some View {\n        Label {\n            Text(\"Permissions\")\n                .font(.system(size: 36))\n        } icon: {\n            if let nsImage = NSImage(named: NSImage.applicationIconName) {\n                Image(nsImage: nsImage)\n                    .resizable()\n                    .aspectRatio(contentMode: .fit)\n                    .frame(width: 75, height: 75)\n            }\n        }\n    }\n\n    @ViewBuilder\n    private var explanationView: some View {\n        IceSection {\n            VStack {\n                Text(\"Ice needs permission to manage the menu bar.\")\n                Text(\"Absolutely no personal information is collected or stored.\")\n                    .bold()\n                    .foregroundStyle(.red)\n            }\n            .padding()\n        }\n        .font(.title3)\n        .padding(.bottom, 10)\n    }\n\n    @ViewBuilder\n    private var permissionsGroupStack: some View {\n        VStack(spacing: 7.5) {\n            ForEach(permissionsManager.allPermissions) { permission in\n                permissionBox(permission)\n            }\n        }\n    }\n\n    @ViewBuilder\n    private var footerView: some View {\n        HStack {\n            quitButton\n            continueButton\n        }\n        .controlSize(.large)\n    }\n\n    @ViewBuilder\n    private var quitButton: some View {\n        Button {\n            NSApp.terminate(nil)\n        } label: {\n            Text(\"Quit\")\n                .frame(maxWidth: .infinity)\n        }\n    }\n\n    @ViewBuilder\n    private var continueButton: some View {\n        Button {\n            guard let appState = permissionsManager.appState else {\n                return\n            }\n            appState.performSetup()\n            appState.permissionsWindow?.close()\n            appState.appDelegate?.openSettingsWindow()\n        } label: {\n            Text(continueButtonText)\n                .frame(maxWidth: .infinity)\n                .foregroundStyle(continueButtonForegroundStyle)\n        }\n        .disabled(permissionsManager.permissionsState == .missingPermissions)\n    }\n\n    @ViewBuilder\n    private func permissionBox(_ permission: Permission) -> some View {\n        IceSection {\n            VStack(spacing: 10) {\n                Text(permission.title)\n                    .font(.title)\n                    .underline()\n\n                VStack(spacing: 0) {\n                    Text(\"Ice needs this to:\")\n                        .font(.title3)\n                        .bold()\n\n                    VStack(alignment: .leading) {\n                        ForEach(permission.details, id: \\.self) { detail in\n                            HStack {\n                                Text(\"•\").bold()\n                                Text(detail)\n                            }\n                        }\n                    }\n                }\n\n                Button {\n                    guard let appState = permissionsManager.appState else {\n                        return\n                    }\n                    permission.performRequest()\n                    Task {\n                        await permission.waitForPermission()\n                        appState.activate(withPolicy: .regular)\n                        openWindow(id: Constants.permissionsWindowID)\n                    }\n                } label: {\n                    if permission.hasPermission {\n                        Text(\"Permission Granted\")\n                            .foregroundStyle(.green)\n                    } else {\n                        Text(\"Grant Permission\")\n                    }\n                }\n                .allowsHitTesting(!permission.hasPermission)\n\n                if !permission.isRequired {\n                    IceGroupBox {\n                        AnnotationView(\n                            alignment: .center,\n                            font: .callout.bold()\n                        ) {\n                            Label {\n                                Text(\"Ice can work in a limited mode without this permission.\")\n                            } icon: {\n                                Image(systemName: \"checkmark.shield\")\n                                    .foregroundStyle(.green)\n                            }\n                        }\n                    }\n                }\n            }\n            .padding(10)\n            .frame(maxWidth: .infinity)\n        }\n    }\n}\n"
  },
  {
    "path": "Ice/Permissions/PermissionsWindow.swift",
    "content": "//\n//  PermissionsWindow.swift\n//  Ice\n//\n\nimport SwiftUI\n\nstruct PermissionsWindow: Scene {\n    @ObservedObject var appState: AppState\n\n    var body: some Scene {\n        Window(Constants.permissionsWindowTitle, id: Constants.permissionsWindowID) {\n            PermissionsView()\n                .readWindow { window in\n                    guard let window else {\n                        return\n                    }\n                    appState.assignPermissionsWindow(window)\n                }\n        }\n        .windowResizability(.contentSize)\n        .windowStyle(.hiddenTitleBar)\n        .environmentObject(appState.permissionsManager)\n    }\n}\n"
  },
  {
    "path": "Ice/Resources/Acknowledgements.rtf",
    "content": "{\\rtf1\\ansi\\ansicpg1252\\cocoartf2761\n\\cocoatextscaling0\\cocoaplatform0{\\fonttbl\\f0\\fnil\\fcharset0 HelveticaNeue-Bold;\\f1\\fnil\\fcharset0 HelveticaNeue;}\n{\\colortbl;\\red255\\green255\\blue255;\\red0\\green0\\blue0;}\n{\\*\\expandedcolortbl;;\\cssrgb\\c0\\c1\\c1;}\n\\margl1440\\margr1440\\vieww34360\\viewh20460\\viewkind0\n\\deftab720\n\\pard\\pardeftab720\\partightenfactor0\n\n\\f0\\b\\fs36 \\cf2 \\expnd0\\expndtw0\\kerning0\nAcknowledgements\n\\f1\\b0\\fs24 \\page \\\n\\pard\\pardeftab720\\partightenfactor0\n\n\\f0\\b\\fs28 \\cf2 AXSwift\n\\f1\\b0 \\\n\\pard\\pardeftab720\\partightenfactor0\n{\\field{\\*\\fldinst{HYPERLINK \"https://github.com/tmandry/AXSwift\"}}{\\fldrslt \n\\fs24 \\cf2 \\ul \\ulc2 https://github.com/tmandry/AXSwift}}\n\\fs22 \\\n\\pard\\pardeftab720\\partightenfactor0\n\n\\fs24 \\cf2 \\\nMIT License\\\n\\\nCopyright (c) 2017 Tyler Mandry\\\n\\\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\\\n\\\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\\\n\\\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\\fs22 \\page \n\\fs24 \\\n\\pard\\pardeftab720\\partightenfactor0\n\n\\f0\\b\\fs28 \\cf2 CompactSlider\n\\f1\\b0 \\\n\\pard\\pardeftab720\\partightenfactor0\n{\\field{\\*\\fldinst{HYPERLINK \"https://github.com/buh/CompactSlider\"}}{\\fldrslt \n\\fs24 \\cf2 \\ul \\ulc2 https://github.com/buh/CompactSlider}}\n\\fs22 \\\n\\pard\\pardeftab720\\partightenfactor0\n\n\\fs24 \\cf2 \\\nMIT License\\\n\\\nCopyright (c) 2022 Alexey Bukhtin\\\n\\\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\\\n\\\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\\\n\\\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\\page \\\n\\pard\\pardeftab720\\partightenfactor0\n\n\\f0\\b\\fs28 \\cf2 Ifrit\n\\f1\\b0 \\\n{\\field{\\*\\fldinst{HYPERLINK \"https://github.com/ukushu/Ifrit\"}}{\\fldrslt \n\\fs24 \\ul \\ulc2 https://github.com/ukushu/Ifrit}}\n\\fs22 \\\n\n\\fs24 \\\nMIT License\\\n\\\nCopyright (c) 2024 Andrii Vynnychenko, Kirollos Risk(original \"fuse-swift\" repository code)\\\n\\\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\\\n\\\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\\\n\\\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\\\n\\pard\\pardeftab720\\partightenfactor0\n\\cf2 \\\n\\pard\\pardeftab720\\partightenfactor0\n\n\\f0\\b\\fs28 \\cf2 LaunchAtLogin\n\\f1\\b0 \\\n\\pard\\pardeftab720\\partightenfactor0\n{\\field{\\*\\fldinst{HYPERLINK \"https://github.com/sindresorhus/LaunchAtLogin\"}}{\\fldrslt \n\\fs24 \\cf2 \\ul \\ulc2 https://github.com/sindresorhus/LaunchAtLogin}}\n\\fs24 \\\n\\\nMIT License\\\n\\\nCopyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)\\\n\\\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\\\n\\\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\\\n\\\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\\page \\\n\\pard\\pardeftab720\\partightenfactor0\n\n\\f0\\b\\fs28 \\cf2 Sparkle\n\\f1\\b0 \\\n\\pard\\pardeftab720\\partightenfactor0\n{\\field{\\*\\fldinst{HYPERLINK \"https://github.com/sparkle-project/Sparkle\"}}{\\fldrslt \n\\fs24 \\cf2 \\ul \\ulc2 https://github.com/sparkle-project/Sparkle}}\n\\fs24 \\\n\\\nCopyright (c) 2006-2013 Andy Matuschak.\\\nCopyright (c) 2009-2013 Elgato Systems GmbH.\\\nCopyright (c) 2011-2014 Kornel Lesi\\uc0\\u324 ski.\\\nCopyright (c) 2015-2017 Mayur Pawashe.\\\nCopyright (c) 2014 C.W. Betts.\\\nCopyright (c) 2014 Petroules Corporation.\\\nCopyright (c) 2014 Big Nerd Ranch.\\\nAll rights reserved.\\\n\\\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\\\n\\\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\\\n\\\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\\\n\\\n=================\\\nEXTERNAL LICENSES\\\n=================\\\n\\\nbspatch.c and bsdiff.c, from bsdiff 4.3 <http://www.daemonology.net/bsdiff/>:\\\n\\\nCopyright 2003-2005 Colin Percival\\\nAll rights reserved\\\n\\\nRedistribution and use in source and binary forms, with or without modification, are permitted providing that the following conditions are met:\\\n1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.\\\n2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.\\\n\\\nTHIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\\\n\\\n--\\\n\\\nsais.c and sais.c, from sais-lite (2010/08/07) <https://sites.google.com/site/yuta256/sais>:\\\n\\\nThe sais-lite copyright is as follows:\\\n\\\nCopyright (c) 2008-2010 Yuta Mori All Rights Reserved.\\\n\\\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\\\n\\\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\\\n\\\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\\\n\\\n--\\\n\\\nPortable C implementation of Ed25519, from https://github.com/orlp/ed25519\\\n\\\nCopyright (c) 2015 Orson Peters <orsonpeters@gmail.com>\\\n\\\nThis software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software.\\\n\\\nPermission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:\\\n\\\n1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.\\\n\\\n2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.\\\n\\\n3. This notice may not be removed or altered from any source distribution.\\\n\\\n--\\\n\\\nSUSignatureVerifier.m:\\\n\\\nCopyright (c) 2011 Mark Hamlin.\\\n\\\nAll rights reserved.\\\n\\\nRedistribution and use in source and binary forms, with or without modification, are permitted providing that the following conditions are met:\\\n1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.\\\n2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.\\\n\\\nTHIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.}"
  },
  {
    "path": "Ice/Settings/SettingsManagers/AdvancedSettingsManager.swift",
    "content": "//\n//  AdvancedSettingsManager.swift\n//  Ice\n//\n\nimport Combine\nimport Foundation\n\n@MainActor\nfinal class AdvancedSettingsManager: ObservableObject {\n    /// A Boolean value that indicates whether the application menus\n    /// should be hidden if needed to show all menu bar items.\n    @Published var hideApplicationMenus = true\n\n    /// A Boolean value that indicates whether section divider control\n    /// items should be shown.\n    @Published var showSectionDividers = false\n\n    /// A Boolean value that indicates whether the always-hidden section\n    /// is enabled.\n    @Published var enableAlwaysHiddenSection = false\n\n    /// A Boolean value that indicates whether the always-hidden section\n    /// can be toggled by holding down the Option key.\n    @Published var canToggleAlwaysHiddenSection = true\n\n    /// The delay before showing on hover.\n    @Published var showOnHoverDelay: TimeInterval = 0.2\n\n    /// Time interval to temporarily show items for.\n    @Published var tempShowInterval: TimeInterval = 15\n\n    /// A Boolean value that indicates whether to show all sections when\n    /// the user is dragging items in the menu bar.\n    @Published var showAllSectionsOnUserDrag = true\n\n    @Published var showContextMenuOnRightClick = true\n\n    /// Storage for internal observers.\n    private var cancellables = Set<AnyCancellable>()\n\n    /// The shared app state.\n    private(set) weak var appState: AppState?\n\n    init(appState: AppState) {\n        self.appState = appState\n    }\n\n    func performSetup() {\n        loadInitialState()\n        configureCancellables()\n    }\n\n    private func loadInitialState() {\n        Defaults.ifPresent(key: .hideApplicationMenus, assign: &hideApplicationMenus)\n        Defaults.ifPresent(key: .showSectionDividers, assign: &showSectionDividers)\n        Defaults.ifPresent(key: .enableAlwaysHiddenSection, assign: &enableAlwaysHiddenSection)\n        Defaults.ifPresent(key: .canToggleAlwaysHiddenSection, assign: &canToggleAlwaysHiddenSection)\n        Defaults.ifPresent(key: .showOnHoverDelay, assign: &showOnHoverDelay)\n        Defaults.ifPresent(key: .tempShowInterval, assign: &tempShowInterval)\n        Defaults.ifPresent(key: .showAllSectionsOnUserDrag, assign: &showAllSectionsOnUserDrag)\n        Defaults.ifPresent(key: .showContextMenuOnRightClick, assign: &showContextMenuOnRightClick)\n    }\n\n    private func configureCancellables() {\n        var c = Set<AnyCancellable>()\n\n        $hideApplicationMenus\n            .receive(on: DispatchQueue.main)\n            .sink { shouldHide in\n                Defaults.set(shouldHide, forKey: .hideApplicationMenus)\n            }\n            .store(in: &c)\n\n        $showSectionDividers\n            .receive(on: DispatchQueue.main)\n            .sink { shouldShow in\n                Defaults.set(shouldShow, forKey: .showSectionDividers)\n            }\n            .store(in: &c)\n\n        $enableAlwaysHiddenSection\n            .receive(on: DispatchQueue.main)\n            .sink { enable in\n                Defaults.set(enable, forKey: .enableAlwaysHiddenSection)\n            }\n            .store(in: &c)\n\n        $canToggleAlwaysHiddenSection\n            .receive(on: DispatchQueue.main)\n            .sink { canToggle in\n                Defaults.set(canToggle, forKey: .canToggleAlwaysHiddenSection)\n            }\n            .store(in: &c)\n\n        $showOnHoverDelay\n            .receive(on: DispatchQueue.main)\n            .sink { delay in\n                Defaults.set(delay, forKey: .showOnHoverDelay)\n            }\n            .store(in: &c)\n\n        $tempShowInterval\n            .receive(on: DispatchQueue.main)\n            .sink { interval in\n                Defaults.set(interval, forKey: .tempShowInterval)\n            }\n            .store(in: &c)\n\n        $showAllSectionsOnUserDrag\n            .receive(on: DispatchQueue.main)\n            .sink { showAll in\n                Defaults.set(showAll, forKey: .showAllSectionsOnUserDrag)\n            }\n            .store(in: &c)\n\n        $showContextMenuOnRightClick\n            .receive(on: DispatchQueue.main)\n            .sink { showAll in\n                Defaults.set(showAll, forKey: .showContextMenuOnRightClick)\n            }\n            .store(in: &c)\n\n        cancellables = c\n    }\n}\n\n// MARK: AdvancedSettingsManager: BindingExposable\nextension AdvancedSettingsManager: BindingExposable { }\n"
  },
  {
    "path": "Ice/Settings/SettingsManagers/GeneralSettingsManager.swift",
    "content": "//\n//  GeneralSettingsManager.swift\n//  Ice\n//\n\nimport Combine\nimport Foundation\n\n@MainActor\nfinal class GeneralSettingsManager: ObservableObject {\n    /// A Boolean value that indicates whether the Ice icon\n    /// should be shown.\n    @Published var showIceIcon = true\n\n    /// An icon to show in the menu bar, with a different image\n    /// for when items are visible or hidden.\n    @Published var iceIcon: ControlItemImageSet = .defaultIceIcon\n\n    /// The last user-selected custom Ice icon.\n    @Published var lastCustomIceIcon: ControlItemImageSet?\n\n    /// A Boolean value that indicates whether custom Ice icons\n    /// should be rendered as template images.\n    @Published var customIceIconIsTemplate = false\n\n    /// A Boolean value that indicates whether to show hidden items\n    /// in a separate bar below the menu bar.\n    @Published var useIceBar = false\n\n    /// The location where the Ice Bar appears.\n    @Published var iceBarLocation: IceBarLocation = .dynamic\n\n    /// A Boolean value that indicates whether the hidden section\n    /// should be shown when the mouse pointer clicks in an empty\n    /// area of the menu bar.\n    @Published var showOnClick = true\n\n    /// A Boolean value that indicates whether the hidden section\n    /// should be shown when the mouse pointer hovers over an\n    /// empty area of the menu bar.\n    @Published var showOnHover = false\n\n    /// A Boolean value that indicates whether the hidden section\n    /// should be shown or hidden when the user scrolls in the\n    /// menu bar.\n    @Published var showOnScroll = true\n\n    /// The offset to apply to the menu bar item spacing and padding.\n    @Published var itemSpacingOffset: Double = 0\n\n    /// A Boolean value that indicates whether the hidden section\n    /// should automatically rehide.\n    @Published var autoRehide = true\n\n    /// A strategy that determines how the auto-rehide feature works.\n    @Published var rehideStrategy: RehideStrategy = .smart\n\n    /// A time interval for the auto-rehide feature when its rule\n    /// is ``RehideStrategy/timed``.\n    @Published var rehideInterval: TimeInterval = 15\n\n    /// Encoder for properties.\n    private let encoder = JSONEncoder()\n\n    /// Decoder for properties.\n    private let decoder = JSONDecoder()\n\n    /// Storage for internal observers.\n    private var cancellables = Set<AnyCancellable>()\n\n    /// The shared app state.\n    private(set) weak var appState: AppState?\n\n    init(appState: AppState) {\n        self.appState = appState\n    }\n\n    func performSetup() {\n        loadInitialState()\n        configureCancellables()\n    }\n\n    private func loadInitialState() {\n        Defaults.ifPresent(key: .showIceIcon, assign: &showIceIcon)\n        Defaults.ifPresent(key: .customIceIconIsTemplate, assign: &customIceIconIsTemplate)\n        Defaults.ifPresent(key: .useIceBar, assign: &useIceBar)\n        Defaults.ifPresent(key: .showOnClick, assign: &showOnClick)\n        Defaults.ifPresent(key: .showOnHover, assign: &showOnHover)\n        Defaults.ifPresent(key: .showOnScroll, assign: &showOnScroll)\n        Defaults.ifPresent(key: .itemSpacingOffset, assign: &itemSpacingOffset)\n        Defaults.ifPresent(key: .autoRehide, assign: &autoRehide)\n        Defaults.ifPresent(key: .rehideInterval, assign: &rehideInterval)\n\n        Defaults.ifPresent(key: .iceBarLocation) { rawValue in\n            if let location = IceBarLocation(rawValue: rawValue) {\n                iceBarLocation = location\n            }\n        }\n        Defaults.ifPresent(key: .rehideStrategy) { rawValue in\n            if let strategy = RehideStrategy(rawValue: rawValue) {\n                rehideStrategy = strategy\n            }\n        }\n\n        if let data = Defaults.data(forKey: .iceIcon) {\n            do {\n                iceIcon = try decoder.decode(ControlItemImageSet.self, from: data)\n            } catch {\n                Logger.generalSettingsManager.error(\"Error decoding Ice icon: \\(error)\")\n            }\n            if case .custom = iceIcon.name {\n                lastCustomIceIcon = iceIcon\n            }\n        }\n    }\n\n    private func configureCancellables() {\n        var c = Set<AnyCancellable>()\n\n        $showIceIcon\n            .receive(on: DispatchQueue.main)\n            .sink { showIceIcon in\n                Defaults.set(showIceIcon, forKey: .showIceIcon)\n            }\n            .store(in: &c)\n\n        $iceIcon\n            .receive(on: DispatchQueue.main)\n            .sink { [weak self] iceIcon in\n                guard let self else {\n                    return\n                }\n                if case .custom = iceIcon.name {\n                    lastCustomIceIcon = iceIcon\n                }\n                do {\n                    let data = try encoder.encode(iceIcon)\n                    Defaults.set(data, forKey: .iceIcon)\n                } catch {\n                    Logger.generalSettingsManager.error(\"Error encoding Ice icon: \\(error)\")\n                }\n            }\n            .store(in: &c)\n\n        $customIceIconIsTemplate\n            .receive(on: DispatchQueue.main)\n            .sink { isTemplate in\n                Defaults.set(isTemplate, forKey: .customIceIconIsTemplate)\n            }\n            .store(in: &c)\n\n        $useIceBar\n            .receive(on: DispatchQueue.main)\n            .sink { useIceBar in\n                Defaults.set(useIceBar, forKey: .useIceBar)\n            }\n            .store(in: &c)\n\n        $iceBarLocation\n            .receive(on: DispatchQueue.main)\n            .sink { location in\n                Defaults.set(location.rawValue, forKey: .iceBarLocation)\n            }\n            .store(in: &c)\n\n        $showOnClick\n            .receive(on: DispatchQueue.main)\n            .sink { showOnClick in\n                Defaults.set(showOnClick, forKey: .showOnClick)\n            }\n            .store(in: &c)\n\n        $showOnHover\n            .receive(on: DispatchQueue.main)\n            .sink { showOnHover in\n                Defaults.set(showOnHover, forKey: .showOnHover)\n            }\n            .store(in: &c)\n\n        $showOnScroll\n            .receive(on: DispatchQueue.main)\n            .sink { showOnScroll in\n                Defaults.set(showOnScroll, forKey: .showOnScroll)\n            }\n            .store(in: &c)\n\n        $itemSpacingOffset\n            .receive(on: DispatchQueue.main)\n            .sink { [weak appState] offset in\n                Defaults.set(offset, forKey: .itemSpacingOffset)\n                appState?.spacingManager.offset = Int(offset)\n            }\n            .store(in: &c)\n\n        $autoRehide\n            .receive(on: DispatchQueue.main)\n            .sink { autoRehide in\n                Defaults.set(autoRehide, forKey: .autoRehide)\n            }\n            .store(in: &c)\n\n        $rehideStrategy\n            .receive(on: DispatchQueue.main)\n            .sink { strategy in\n                Defaults.set(strategy.rawValue, forKey: .rehideStrategy)\n            }\n            .store(in: &c)\n\n        $rehideInterval\n            .receive(on: DispatchQueue.main)\n            .sink { interval in\n                Defaults.set(interval, forKey: .rehideInterval)\n            }\n            .store(in: &c)\n\n        cancellables = c\n    }\n}\n\n// MARK: GeneralSettingsManager: BindingExposable\nextension GeneralSettingsManager: BindingExposable { }\n\n// MARK: - Logger\nprivate extension Logger {\n    static let generalSettingsManager = Logger(category: \"GeneralSettingsManager\")\n}\n"
  },
  {
    "path": "Ice/Settings/SettingsManagers/HotkeySettingsManager.swift",
    "content": "//\n//  HotkeySettingsManager.swift\n//  Ice\n//\n\nimport Combine\nimport Foundation\n\n@MainActor\nfinal class HotkeySettingsManager: ObservableObject {\n    /// All hotkeys.\n    @Published private(set) var hotkeys = HotkeyAction.allCases.map { action in\n        Hotkey(keyCombination: nil, action: action)\n    }\n\n    /// Encoder for hotkeys.\n    private let encoder = JSONEncoder()\n\n    /// Decoder for hotkeys.\n    private let decoder = JSONDecoder()\n\n    /// Storage for internal observers.\n    private var cancellables = Set<AnyCancellable>()\n\n    /// The shared app state.\n    private(set) weak var appState: AppState?\n\n    init(appState: AppState) {\n        self.appState = appState\n    }\n\n    func performSetup() {\n        loadInitialState()\n        configureCancellables()\n    }\n\n    private func loadInitialState() {\n        if let dict = Defaults.dictionary(forKey: .hotkeys) as? [String: Data] {\n            for hotkey in hotkeys {\n                if let data = dict[hotkey.action.rawValue] {\n                    do {\n                        hotkey.keyCombination = try decoder.decode(KeyCombination?.self, from: data)\n                    } catch {\n                        Logger.hotkeySettingsManager.error(\"Error decoding hotkey: \\(error)\")\n                    }\n                }\n            }\n        }\n    }\n\n    private func configureCancellables() {\n        var c = Set<AnyCancellable>()\n\n        $hotkeys.combineLatest(Publishers.MergeMany(hotkeys.map { $0.$keyCombination }))\n            .receive(on: DispatchQueue.main)\n            .sink { [weak self] hotkeys, _ in\n                guard\n                    let self,\n                    let appState\n                else {\n                    return\n                }\n                var dict = [String: Data]()\n                for hotkey in hotkeys {\n                    hotkey.assignAppState(appState)\n                    do {\n                        dict[hotkey.action.rawValue] = try self.encoder.encode(hotkey.keyCombination)\n                    } catch {\n                        Logger.hotkeySettingsManager.error(\"Error encoding hotkey: \\(error)\")\n                    }\n                }\n                Defaults.set(dict, forKey: .hotkeys)\n            }\n            .store(in: &c)\n\n        cancellables = c\n    }\n\n    func hotkey(withAction action: HotkeyAction) -> Hotkey? {\n        hotkeys.first { $0.action == action }\n    }\n}\n\n// MARK: - Logger\nprivate extension Logger {\n    static let hotkeySettingsManager = Logger(category: \"HotkeySettingsManager\")\n}\n"
  },
  {
    "path": "Ice/Settings/SettingsManagers/SettingsManager.swift",
    "content": "//\n//  SettingsManager.swift\n//  Ice\n//\n\nimport Combine\n\n@MainActor\nfinal class SettingsManager: ObservableObject {\n    /// The manager for general settings.\n    let generalSettingsManager: GeneralSettingsManager\n\n    /// The manager for advanced settings.\n    let advancedSettingsManager: AdvancedSettingsManager\n\n    /// The manager for hotkey settings.\n    let hotkeySettingsManager: HotkeySettingsManager\n\n    /// Storage for internal observers.\n    private var cancellables = Set<AnyCancellable>()\n\n    /// The shared app state.\n    private(set) weak var appState: AppState?\n\n    init(appState: AppState) {\n        self.generalSettingsManager = GeneralSettingsManager(appState: appState)\n        self.advancedSettingsManager = AdvancedSettingsManager(appState: appState)\n        self.hotkeySettingsManager = HotkeySettingsManager(appState: appState)\n        self.appState = appState\n    }\n\n    func performSetup() {\n        configureCancellables()\n        generalSettingsManager.performSetup()\n        advancedSettingsManager.performSetup()\n        hotkeySettingsManager.performSetup()\n    }\n\n    private func configureCancellables() {\n        var c = Set<AnyCancellable>()\n\n        generalSettingsManager.objectWillChange\n            .sink { [weak self] in\n                self?.objectWillChange.send()\n            }\n            .store(in: &c)\n        advancedSettingsManager.objectWillChange\n            .sink { [weak self] in\n                self?.objectWillChange.send()\n            }\n            .store(in: &c)\n        hotkeySettingsManager.objectWillChange\n            .sink { [weak self] in\n                self?.objectWillChange.send()\n            }\n            .store(in: &c)\n\n        cancellables = c\n    }\n}\n\n// MARK: SettingsManager: BindingExposable\nextension SettingsManager: BindingExposable { }\n"
  },
  {
    "path": "Ice/Settings/SettingsPanes/AboutSettingsPane.swift",
    "content": "//\n//  AboutSettingsPane.swift\n//  Ice\n//\n\nimport SwiftUI\n\nstruct AboutSettingsPane: View {\n    @EnvironmentObject var appState: AppState\n    @Environment(\\.openURL) private var openURL\n\n    private var updatesManager: UpdatesManager {\n        appState.updatesManager\n    }\n\n    private var acknowledgementsURL: URL {\n        // swiftlint:disable:next force_unwrapping\n        Bundle.main.url(forResource: \"Acknowledgements\", withExtension: \"pdf\")!\n    }\n\n    private var contributeURL: URL {\n        // swiftlint:disable:next force_unwrapping\n        URL(string: \"https://github.com/jordanbaird/Ice\")!\n    }\n\n    private var issuesURL: URL {\n        contributeURL.appendingPathComponent(\"issues\")\n    }\n\n    private var donateURL: URL {\n        // swiftlint:disable:next force_unwrapping\n        URL(string: \"https://icemenubar.app/Donate\")!\n    }\n\n    private var lastUpdateCheckString: String {\n        if let date = updatesManager.lastUpdateCheckDate {\n            date.formatted(date: .abbreviated, time: .standard)\n        } else {\n            \"Never\"\n        }\n    }\n\n    var body: some View {\n        VStack(spacing: 0) {\n            mainForm\n            Spacer(minLength: 20)\n            bottomBar\n        }\n        .padding(30)\n    }\n\n    @ViewBuilder\n    private var mainForm: some View {\n        IceForm(padding: EdgeInsets(top: 5, leading: 30, bottom: 30, trailing: 30), spacing: 0) {\n            appIconAndCopyrightSection\n                .layoutPriority(1)\n\n            Spacer(minLength: 0)\n                .frame(maxHeight: 20)\n\n            updatesSection\n                .layoutPriority(1)\n        }\n        .scrollDisabled(true)\n        .frame(maxHeight: 500)\n        .background(.quinary, in: RoundedRectangle(cornerRadius: 20, style: .circular))\n    }\n\n    @ViewBuilder\n    private var appIconAndCopyrightSection: some View {\n        IceSection(options: .plain) {\n            HStack(spacing: 10) {\n                if let nsImage = NSImage(named: NSImage.applicationIconName) {\n                    Image(nsImage: nsImage)\n                        .resizable()\n                        .aspectRatio(contentMode: .fit)\n                        .frame(width: 225)\n                }\n\n                VStack(alignment: .leading) {\n                    Text(\"Ice\")\n                        .font(.system(size: 72, weight: .medium))\n                        .foregroundStyle(.primary)\n\n                    Text(\"Version \\(Constants.versionString)\")\n                        .font(.system(size: 18))\n                        .foregroundStyle(.secondary)\n\n                    Text(Constants.copyrightString)\n                        .font(.system(size: 14, weight: .medium))\n                        .foregroundStyle(.tertiary)\n                }\n            }\n        }\n    }\n\n    @ViewBuilder\n    private var updatesSection: some View {\n        IceSection(options: .hasDividers) {\n            automaticallyCheckForUpdates\n            automaticallyDownloadUpdates\n            if updatesManager.canCheckForUpdates {\n                checkForUpdates\n            }\n        }\n        .frame(maxWidth: 600)\n    }\n\n    @ViewBuilder\n    private var automaticallyCheckForUpdates: some View {\n        Toggle(\n            \"Automatically check for updates\",\n            isOn: updatesManager.bindings.automaticallyChecksForUpdates\n        )\n    }\n\n    @ViewBuilder\n    private var automaticallyDownloadUpdates: some View {\n        Toggle(\n            \"Automatically download updates\",\n            isOn: updatesManager.bindings.automaticallyDownloadsUpdates\n        )\n    }\n\n    @ViewBuilder\n    private var checkForUpdates: some View {\n        HStack {\n            Button(\"Check for Updates\") {\n                updatesManager.checkForUpdates()\n            }\n            Spacer()\n            Text(\"Last checked: \\(lastUpdateCheckString)\")\n                .font(.caption)\n        }\n    }\n\n    @ViewBuilder\n    private var bottomBar: some View {\n        HStack {\n            Button(\"Quit Ice\") {\n                NSApp.terminate(nil)\n            }\n            Spacer()\n            Button(\"Acknowledgements\") {\n                NSWorkspace.shared.open(acknowledgementsURL)\n            }\n            Button(\"Contribute\") {\n                openURL(contributeURL)\n            }\n            Button(\"Report a Bug\") {\n                openURL(issuesURL)\n            }\n            Button(\"Support Ice\", systemImage: \"heart.circle.fill\") {\n                openURL(donateURL)\n            }\n        }\n        .padding(8)\n        .buttonStyle(BottomBarButtonStyle())\n        .background(.quinary, in: Capsule(style: .circular))\n        .frame(height: 40)\n    }\n}\n\nprivate struct BottomBarButtonStyle: ButtonStyle {\n    @State private var isHovering = false\n\n    private var borderShape: some InsettableShape {\n        Capsule(style: .circular)\n    }\n\n    func makeBody(configuration: Configuration) -> some View {\n        configuration.label\n            .padding(.horizontal, 10)\n            .padding(.vertical, 4)\n            .background {\n                borderShape\n                    .fill(configuration.isPressed ? .tertiary : .quaternary)\n                    .opacity(isHovering ? 1 : 0)\n            }\n            .contentShape([.focusEffect, .interaction], borderShape)\n            .onHover { hovering in\n                isHovering = hovering\n            }\n    }\n}\n"
  },
  {
    "path": "Ice/Settings/SettingsPanes/AdvancedSettingsPane.swift",
    "content": "//\n//  AdvancedSettingsPane.swift\n//  Ice\n//\n\nimport SwiftUI\n\nstruct AdvancedSettingsPane: View {\n    @EnvironmentObject var appState: AppState\n    @State private var maxSliderLabelWidth: CGFloat = 0\n\n    private var menuBarManager: MenuBarManager {\n        appState.menuBarManager\n    }\n\n    private var manager: AdvancedSettingsManager {\n        appState.settingsManager.advancedSettingsManager\n    }\n\n    private func formattedToSeconds(_ interval: TimeInterval) -> LocalizedStringKey {\n        let formatted = interval.formatted()\n        return if interval == 1 {\n            LocalizedStringKey(formatted + \" second\")\n        } else {\n            LocalizedStringKey(formatted + \" seconds\")\n        }\n    }\n\n    var body: some View {\n        IceForm {\n            IceSection {\n                hideApplicationMenus\n                showSectionDividers\n                showAllSectionsOnUserDrag\n                showContextMenuOnRightClick\n            }\n            IceSection {\n                enableAlwaysHiddenSection\n                canToggleAlwaysHiddenSection\n            }\n            IceSection {\n                showOnHoverDelaySlider\n                tempShowIntervalSlider\n            }\n            IceSection(\"Permissions\") {\n                allPermissions\n            }\n        }\n    }\n\n    @ViewBuilder\n    private var hideApplicationMenus: some View {\n        Toggle(\"Hide application menus when showing menu bar items\", isOn: manager.bindings.hideApplicationMenus)\n            .annotation(\"Make more room in the menu bar by hiding the left application menus if needed\")\n    }\n\n    @ViewBuilder\n    private var showSectionDividers: some View {\n        Toggle(\"Show section dividers\", isOn: manager.bindings.showSectionDividers)\n            .annotation {\n                HStack(spacing: 2) {\n                    Text(\"Insert divider items\")\n                    if let nsImage = ControlItemImage.builtin(.chevronLarge).nsImage(for: appState) {\n                        HStack(spacing: 0) {\n                            Text(\"(\")\n                                .font(.body.monospaced().bold())\n                            Image(nsImage: nsImage)\n                                .padding(.horizontal, -2)\n                            Text(\")\")\n                                .font(.body.monospaced().bold())\n                        }\n                    }\n                    Text(\"between sections\")\n                }\n            }\n    }\n\n    @ViewBuilder\n    private var enableAlwaysHiddenSection: some View {\n        Toggle(\"Enable always-hidden section\", isOn: manager.bindings.enableAlwaysHiddenSection)\n    }\n\n    @ViewBuilder\n    private var canToggleAlwaysHiddenSection: some View {\n        if manager.enableAlwaysHiddenSection {\n            Toggle(\"Always-hidden section can be shown\", isOn: manager.bindings.canToggleAlwaysHiddenSection)\n                .annotation {\n                    if appState.settingsManager.generalSettingsManager.showOnClick {\n                        Text(\"Option + click one of Ice's menu bar items, or inside an empty area of the menu bar to show the section\")\n                    } else {\n                        Text(\"Option + click one of Ice's menu bar items to show the section\")\n                    }\n                }\n        }\n    }\n\n    @ViewBuilder\n    private var showOnHoverDelaySlider: some View {\n        IceLabeledContent {\n            IceSlider(\n                formattedToSeconds(manager.showOnHoverDelay),\n                value: manager.bindings.showOnHoverDelay,\n                in: 0...1,\n                step: 0.1\n            )\n        } label: {\n            Text(\"Show on hover delay\")\n                .frame(minHeight: .compactSliderMinHeight)\n                .frame(minWidth: maxSliderLabelWidth, alignment: .leading)\n                .onFrameChange { frame in\n                    maxSliderLabelWidth = max(maxSliderLabelWidth, frame.width)\n                }\n        }\n        .annotation(\"The amount of time to wait before showing on hover\")\n    }\n\n    @ViewBuilder\n    private var tempShowIntervalSlider: some View {\n        IceLabeledContent {\n            IceSlider(\n                formattedToSeconds(manager.tempShowInterval),\n                value: manager.bindings.tempShowInterval,\n                in: 0...30,\n                step: 1\n            )\n        } label: {\n            Text(\"Temporarily shown item delay\")\n                .frame(minHeight: .compactSliderMinHeight)\n                .frame(minWidth: maxSliderLabelWidth, alignment: .leading)\n                .onFrameChange { frame in\n                    maxSliderLabelWidth = max(maxSliderLabelWidth, frame.width)\n                }\n        }\n        .annotation(\"The amount of time to wait before hiding temporarily shown menu bar items\")\n    }\n\n    @ViewBuilder\n    private var showAllSectionsOnUserDrag: some View {\n        Toggle(\"Show all sections when Command + dragging menu bar items\", isOn: manager.bindings.showAllSectionsOnUserDrag)\n    }\n\n    @ViewBuilder\n    private var showContextMenuOnRightClick: some View {\n        Toggle(\"Show context menu on right click\", isOn: manager.bindings.showContextMenuOnRightClick)\n    }\n\n    @ViewBuilder\n    private var allPermissions: some View {\n        ForEach(appState.permissionsManager.allPermissions) { permission in\n            IceLabeledContent {\n                if permission.hasPermission {\n                    Label {\n                        Text(\"Permission Granted\")\n                    } icon: {\n                        Image(systemName: \"checkmark.circle\")\n                            .foregroundStyle(.green)\n                    }\n                } else {\n                    Button(\"Grant Permission\") {\n                        permission.performRequest()\n                    }\n                }\n            } label: {\n                Text(permission.title)\n            }\n            .frame(height: 22)\n        }\n    }\n}\n\n#Preview {\n    AdvancedSettingsPane()\n        .fixedSize()\n        .environmentObject(AppState())\n}\n"
  },
  {
    "path": "Ice/Settings/SettingsPanes/GeneralSettingsPane.swift",
    "content": "//\n//  GeneralSettingsPane.swift\n//  Ice\n//\n\nimport LaunchAtLogin\nimport SwiftUI\n\nstruct GeneralSettingsPane: View {\n    @EnvironmentObject var appState: AppState\n    @State private var isImportingCustomIceIcon = false\n    @State private var isPresentingError = false\n    @State private var presentedError: LocalizedErrorWrapper?\n    @State private var isApplyingOffset = false\n    @State private var tempItemSpacingOffset: CGFloat = 0 // Temporary state for the slider\n\n    private var manager: GeneralSettingsManager {\n        appState.settingsManager.generalSettingsManager\n    }\n\n    private var itemSpacingOffset: LocalizedStringKey {\n        localizedOffsetString(for: manager.itemSpacingOffset)\n    }\n\n    private func localizedOffsetString(for offset: CGFloat) -> LocalizedStringKey {\n        switch offset {\n        case -16:\n            return LocalizedStringKey(\"none\")\n        case 0:\n            return LocalizedStringKey(\"default\")\n        case 16:\n            return LocalizedStringKey(\"max\")\n        default:\n            return LocalizedStringKey(offset.formatted())\n        }\n    }\n\n    private var rehideIntervalKey: LocalizedStringKey {\n        let formatted = manager.rehideInterval.formatted()\n        if manager.rehideInterval == 1 {\n            return LocalizedStringKey(formatted + \" second\")\n        } else {\n            return LocalizedStringKey(formatted + \" seconds\")\n        }\n    }\n\n    private var hasSpacingSliderValueChanged: Bool {\n        tempItemSpacingOffset != manager.itemSpacingOffset\n    }\n\n    private var isActualOffsetDifferentFromDefault: Bool {\n        manager.itemSpacingOffset != 0\n    }\n\n    var body: some View {\n        IceForm {\n            IceSection {\n                launchAtLogin\n            }\n            IceSection {\n                iceIconOptions\n            }\n            IceSection {\n                iceBarOptions\n            }\n            IceSection {\n                showOnClick\n                showOnHover\n                showOnScroll\n            }\n            IceSection {\n                autoRehideOptions\n            }\n            IceSection {\n                spacingOptions\n            }\n        }\n        .alert(isPresented: $isPresentingError, error: presentedError) {\n            Button(\"OK\") {\n                presentedError = nil\n                isPresentingError = false\n            }\n        }\n    }\n\n    @ViewBuilder\n    private var launchAtLogin: some View {\n        LaunchAtLogin.Toggle()\n    }\n\n    @ViewBuilder\n    private func menuItem(for imageSet: ControlItemImageSet) -> some View {\n        Label {\n            Text(imageSet.name.rawValue)\n        } icon: {\n            if let nsImage = imageSet.hidden.nsImage(for: appState) {\n                switch imageSet.name {\n                case .custom:\n                    Image(size: CGSize(width: 18, height: 18)) { context in\n                        context.draw(\n                            Image(nsImage: nsImage),\n                            in: context.clipBoundingRect\n                        )\n                    }\n                default:\n                    Image(nsImage: nsImage)\n                }\n            }\n        }\n    }\n\n    @ViewBuilder\n    private var iceIconOptions: some View {\n        Toggle(\"Show Ice icon\", isOn: manager.bindings.showIceIcon)\n            .annotation {\n                if !manager.showIceIcon {\n                    Text(\"You can still access Ice's settings by right-clicking an empty area in the menu bar\")\n                }\n            }\n        if manager.showIceIcon {\n            IceMenu(\"Ice icon\") {\n                Picker(\"Ice icon\", selection: manager.bindings.iceIcon) {\n                    ForEach(ControlItemImageSet.userSelectableIceIcons) { imageSet in\n                        Button {\n                            manager.iceIcon = imageSet\n                        } label: {\n                            menuItem(for: imageSet)\n                        }\n                        .tag(imageSet)\n                    }\n                    if let lastCustomIceIcon = manager.lastCustomIceIcon {\n                        Button {\n                            manager.iceIcon = lastCustomIceIcon\n                        } label: {\n                            menuItem(for: lastCustomIceIcon)\n                        }\n                        .tag(lastCustomIceIcon)\n                    }\n                }\n                .pickerStyle(.inline)\n                .labelsHidden()\n\n                Divider()\n\n                Button(\"Choose image…\") {\n                    isImportingCustomIceIcon = true\n                }\n            } title: {\n                menuItem(for: manager.iceIcon)\n            }\n            .annotation(\"Choose a custom icon to show in the menu bar\")\n            .fileImporter(\n                isPresented: $isImportingCustomIceIcon,\n                allowedContentTypes: [.image]\n            ) { result in\n                do {\n                    let url = try result.get()\n                    if url.startAccessingSecurityScopedResource() {\n                        defer { url.stopAccessingSecurityScopedResource() }\n                        let data = try Data(contentsOf: url)\n                        manager.iceIcon = ControlItemImageSet(name: .custom, image: .data(data))\n                    }\n                } catch {\n                    presentedError = LocalizedErrorWrapper(error)\n                    isPresentingError = true\n                }\n            }\n\n            if case .custom = manager.iceIcon.name {\n                Toggle(\"Apply system theme to icon\", isOn: manager.bindings.customIceIconIsTemplate)\n                    .annotation(\"Display the icon as a monochrome image matching the system appearance\")\n            }\n        }\n    }\n\n    @ViewBuilder\n    private var iceBarOptions: some View {\n        useIceBar\n        if manager.useIceBar {\n            iceBarLocationPicker\n        }\n    }\n\n    @ViewBuilder\n    private var useIceBar: some View {\n        Toggle(\"Use Ice Bar\", isOn: manager.bindings.useIceBar)\n            .annotation(\"Show hidden menu bar items in a separate bar below the menu bar\")\n    }\n\n    @ViewBuilder\n    private var iceBarLocationPicker: some View {\n        IcePicker(\"Location\", selection: manager.bindings.iceBarLocation) {\n            ForEach(IceBarLocation.allCases) { location in\n                Text(location.localized).tag(location)\n            }\n        }\n        .annotation {\n            switch manager.iceBarLocation {\n            case .dynamic:\n                Text(\"The Ice Bar's location changes based on context\")\n            case .mousePointer:\n                Text(\"The Ice Bar is centered below the mouse pointer\")\n            case .iceIcon:\n                Text(\"The Ice Bar is centered below the Ice icon\")\n            }\n        }\n    }\n\n    @ViewBuilder\n    private var showOnClick: some View {\n        Toggle(\"Show on click\", isOn: manager.bindings.showOnClick)\n            .annotation(\"Click inside an empty area of the menu bar to show hidden menu bar items\")\n    }\n\n    @ViewBuilder\n    private var showOnHover: some View {\n        Toggle(\"Show on hover\", isOn: manager.bindings.showOnHover)\n            .annotation(\"Hover over an empty area of the menu bar to show hidden menu bar items\")\n    }\n\n    @ViewBuilder\n    private var showOnScroll: some View {\n        Toggle(\"Show on scroll\", isOn: manager.bindings.showOnScroll)\n            .annotation(\"Scroll or swipe in the menu bar to toggle hidden menu bar items\")\n    }\n\n    @ViewBuilder\n    private var spacingOptions: some View {\n        IceLabeledContent {\n            IceSlider(\n                localizedOffsetString(for: tempItemSpacingOffset),\n                value: $tempItemSpacingOffset,\n                in: -16...16,\n                step: 2\n            )\n            .disabled(isApplyingOffset)\n        } label: {\n            IceLabeledContent {\n                Button(\"Apply\") {\n                    applyOffset()\n                }\n                .help(\"Apply the current spacing\")\n                .disabled(isApplyingOffset || !hasSpacingSliderValueChanged)\n\n                if isApplyingOffset {\n                    ProgressView()\n                        .progressViewStyle(.circular)\n                        .scaleEffect(0.5)\n                        .frame(width: 15, height: 15)\n                } else {\n                    Button {\n                        resetOffsetToDefault()\n                    } label: {\n                        Image(systemName: \"arrow.counterclockwise.circle.fill\")\n                    }\n                    .buttonStyle(.borderless)\n                    .help(\"Reset to the default spacing\")\n                    .disabled(isApplyingOffset || !isActualOffsetDifferentFromDefault)\n                }\n            } label: {\n                HStack {\n                    Text(\"Menu bar item spacing\")\n                    BetaBadge()\n                }\n            }\n        }\n        .annotation(\n            \"Applying this setting will relaunch all apps with menu bar items. Some apps may need to be manually relaunched.\",\n            spacing: 2\n        )\n        .annotation(spacing: 10, font: .callout.bold()) {\n            IceGroupBox {\n                Label {\n                    Text(\"Note: You may need to log out and back in for this setting to apply properly.\")\n                } icon: {\n                    Image(systemName: \"exclamationmark.circle\")\n                }\n                .frame(maxWidth: .infinity)\n            }\n        }\n        .onAppear {\n            tempItemSpacingOffset = manager.itemSpacingOffset\n        }\n    }\n\n    @ViewBuilder\n    private var rehideStrategyPicker: some View {\n        IcePicker(\"Strategy\", selection: manager.bindings.rehideStrategy) {\n            ForEach(RehideStrategy.allCases) { strategy in\n                Text(strategy.localized).tag(strategy)\n            }\n        }\n        .annotation {\n            switch manager.rehideStrategy {\n            case .smart:\n                Text(\"Menu bar items are rehidden using a smart algorithm\")\n            case .timed:\n                Text(\"Menu bar items are rehidden after a fixed amount of time\")\n            case .focusedApp:\n                Text(\"Menu bar items are rehidden when the focused app changes\")\n            }\n        }\n    }\n\n    @ViewBuilder\n    private var autoRehideOptions: some View {\n        Toggle(\"Automatically rehide\", isOn: manager.bindings.autoRehide)\n        if manager.autoRehide {\n            if case .timed = manager.rehideStrategy {\n                VStack {\n                    rehideStrategyPicker\n                    IceSlider(\n                        rehideIntervalKey,\n                        value: manager.bindings.rehideInterval,\n                        in: 0...30,\n                        step: 1\n                    )\n                }\n            } else {\n                rehideStrategyPicker\n            }\n        }\n    }\n\n    /// Apply menu bar spacing offset.\n    private func applyOffset() {\n        isApplyingOffset = true\n        manager.itemSpacingOffset = tempItemSpacingOffset\n        Task {\n            do {\n                try await appState.spacingManager.applyOffset()\n            } catch {\n                let alert = NSAlert(error: error)\n                alert.runModal()\n            }\n            isApplyingOffset = false\n        }\n    }\n\n    /// Reset menu bar spacing offset to default.\n    private func resetOffsetToDefault() {\n        tempItemSpacingOffset = 0\n        manager.itemSpacingOffset = tempItemSpacingOffset\n        applyOffset()\n    }\n}\n"
  },
  {
    "path": "Ice/Settings/SettingsPanes/HotkeysSettingsPane.swift",
    "content": "//\n//  HotkeysSettingsPane.swift\n//  Ice\n//\n\nimport SwiftUI\n\nstruct HotkeysSettingsPane: View {\n    @EnvironmentObject var appState: AppState\n\n    private var hotkeySettingsManager: HotkeySettingsManager {\n        appState.settingsManager.hotkeySettingsManager\n    }\n\n    var body: some View {\n        IceForm {\n            IceSection(\"Menu Bar Sections\") {\n                hotkeyRecorder(forSection: .hidden)\n                hotkeyRecorder(forSection: .alwaysHidden)\n            }\n            IceSection(\"Menu Bar Items\") {\n                hotkeyRecorder(forAction: .searchMenuBarItems)\n            }\n            IceSection(\"Other\") {\n                hotkeyRecorder(forAction: .enableIceBar)\n                hotkeyRecorder(forAction: .showSectionDividers)\n                hotkeyRecorder(forAction: .toggleApplicationMenus)\n            }\n        }\n    }\n\n    @ViewBuilder\n    private func hotkeyRecorder(forAction action: HotkeyAction) -> some View {\n        if let hotkey = hotkeySettingsManager.hotkey(withAction: action) {\n            HotkeyRecorder(hotkey: hotkey) {\n                switch action {\n                case .toggleHiddenSection:\n                    Text(\"Toggle the hidden section\")\n                case .toggleAlwaysHiddenSection:\n                    Text(\"Toggle the always-hidden section\")\n                case .searchMenuBarItems:\n                    Text(\"Search menu bar items\")\n                case .enableIceBar:\n                    Text(\"Enable the Ice Bar\")\n                case .showSectionDividers:\n                    Text(\"Show section dividers\")\n                case .toggleApplicationMenus:\n                    Text(\"Toggle application menus\")\n                }\n            }\n        }\n    }\n\n    @ViewBuilder\n    private func hotkeyRecorder(forSection name: MenuBarSection.Name) -> some View {\n        if appState.menuBarManager.section(withName: name)?.isEnabled == true {\n            if case .hidden = name {\n                hotkeyRecorder(forAction: .toggleHiddenSection)\n            } else if case .alwaysHidden = name {\n                hotkeyRecorder(forAction: .toggleAlwaysHiddenSection)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Ice/Settings/SettingsPanes/MenuBarAppearanceSettingsPane.swift",
    "content": "//\n//  MenuBarAppearanceSettingsPane.swift\n//  Ice\n//\n\nimport SwiftUI\n\nstruct MenuBarAppearanceSettingsPane: View {\n    @EnvironmentObject var appState: AppState\n\n    var body: some View {\n        MenuBarAppearanceEditor(location: .settings)\n            .environmentObject(appState.appearanceManager)\n    }\n}\n\n#Preview {\n    MenuBarAppearanceSettingsPane()\n        .environmentObject(AppState())\n}\n"
  },
  {
    "path": "Ice/Settings/SettingsPanes/MenuBarLayoutSettingsPane.swift",
    "content": "//\n//  MenuBarLayoutSettingsPane.swift\n//  Ice\n//\n\nimport SwiftUI\n\nstruct MenuBarLayoutSettingsPane: View {\n    @EnvironmentObject var appState: AppState\n\n    var body: some View {\n        if !ScreenCapture.cachedCheckPermissions() {\n            missingScreenRecordingPermission\n        } else if appState.menuBarManager.isMenuBarHiddenBySystemUserDefaults {\n            cannotArrange\n        } else {\n            IceForm(alignment: .leading, spacing: 20) {\n                header\n                layoutBars\n            }\n        }\n    }\n\n    @ViewBuilder\n    private var header: some View {\n        Text(\"Drag to arrange your menu bar items\")\n            .font(.title2)\n\n        IceGroupBox {\n            AnnotationView(\n                alignment: .center,\n                font: .callout.bold()\n            ) {\n                Label {\n                    Text(\"Tip: you can also arrange menu bar items by Command + dragging them in the menu bar\")\n                } icon: {\n                    Image(systemName: \"lightbulb\")\n                }\n            }\n        }\n    }\n\n    @ViewBuilder\n    private var layoutBars: some View {\n        VStack(spacing: 25) {\n            ForEach(MenuBarSection.Name.allCases, id: \\.self) { section in\n                layoutBar(for: section)\n            }\n        }\n    }\n\n    @ViewBuilder\n    private var cannotArrange: some View {\n        Text(\"Ice cannot arrange menu bar items in automatically hidden menu bars\")\n            .font(.title3)\n            .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)\n    }\n\n    @ViewBuilder\n    private var missingScreenRecordingPermission: some View {\n        VStack {\n            Text(\"Menu bar layout requires screen recording permissions\")\n                .font(.title2)\n\n            Button {\n                appState.navigationState.settingsNavigationIdentifier = .advanced\n            } label: {\n                Text(\"Go to Advanced Settings\")\n            }\n            .buttonStyle(.link)\n        }\n    }\n\n    @ViewBuilder\n    private func layoutBar(for section: MenuBarSection.Name) -> some View {\n        if\n            let section = appState.menuBarManager.section(withName: section),\n            section.isEnabled\n        {\n            VStack(alignment: .leading, spacing: 4) {\n                Text(\"\\(section.name.displayString) Section\")\n                    .font(.system(size: 14))\n                    .padding(.leading, 2)\n\n                LayoutBar(section: section)\n                    .environmentObject(appState.imageCache)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Ice/Settings/SettingsView.swift",
    "content": "//\n//  SettingsView.swift\n//  Ice\n//\n\nimport SwiftUI\n\nstruct SettingsView: View {\n    @EnvironmentObject var navigationState: AppNavigationState\n    @Environment(\\.sidebarRowSize) var sidebarRowSize\n\n    private var sidebarWidth: CGFloat {\n        switch sidebarRowSize {\n        case .small: 190\n        case .medium: 210\n        case .large: 230\n        @unknown default: 210\n        }\n    }\n\n    private var sidebarItemHeight: CGFloat {\n        switch sidebarRowSize {\n        case .small: 26\n        case .medium: 32\n        case .large: 34\n        @unknown default: 32\n        }\n    }\n\n    private var sidebarItemFontSize: CGFloat {\n        switch sidebarRowSize {\n        case .small: 13\n        case .medium: 15\n        case .large: 16\n        @unknown default: 15\n        }\n    }\n\n    var body: some View {\n        NavigationSplitView {\n            sidebar\n        } detail: {\n            detailView\n        }\n        .navigationTitle(navigationState.settingsNavigationIdentifier.localized)\n    }\n\n    @ViewBuilder\n    private var sidebar: some View {\n        List(selection: $navigationState.settingsNavigationIdentifier) {\n            Section {\n                ForEach(SettingsNavigationIdentifier.allCases, id: \\.self) { identifier in\n                    sidebarItem(for: identifier)\n                }\n            } header: {\n                Text(\"Ice\")\n                    .font(.system(size: 36, weight: .medium))\n                    .foregroundStyle(.primary)\n                    .padding(.vertical, 5)\n            }\n            .collapsible(false)\n        }\n        .scrollDisabled(true)\n        .removeSidebarToggle()\n        .navigationSplitViewColumnWidth(sidebarWidth)\n    }\n\n    @ViewBuilder\n    private var detailView: some View {\n        switch navigationState.settingsNavigationIdentifier {\n        case .general:\n            GeneralSettingsPane()\n        case .menuBarLayout:\n            MenuBarLayoutSettingsPane()\n        case .menuBarAppearance:\n            MenuBarAppearanceSettingsPane()\n        case .hotkeys:\n            HotkeysSettingsPane()\n        case .advanced:\n            AdvancedSettingsPane()\n        case .about:\n            AboutSettingsPane()\n        }\n    }\n\n    @ViewBuilder\n    private func sidebarItem(for identifier: SettingsNavigationIdentifier) -> some View {\n        Label {\n            Text(identifier.localized)\n                .font(.system(size: sidebarItemFontSize))\n                .padding(.leading, 2)\n        } icon: {\n            icon(for: identifier).view\n        }\n        .frame(height: sidebarItemHeight)\n    }\n\n    private func icon(for identifier: SettingsNavigationIdentifier) -> IconResource {\n        switch identifier {\n        case .general: .systemSymbol(\"gearshape\")\n        case .menuBarLayout: .systemSymbol(\"rectangle.topthird.inset.filled\")\n        case .menuBarAppearance: .systemSymbol(\"swatchpalette\")\n        case .hotkeys: .systemSymbol(\"keyboard\")\n        case .advanced: .systemSymbol(\"gearshape.2\")\n        case .about: .assetCatalog(.iceCubeStroke)\n        }\n    }\n}\n"
  },
  {
    "path": "Ice/Settings/SettingsWindow.swift",
    "content": "//\n//  SettingsWindow.swift\n//  Ice\n//\n\nimport SwiftUI\n\nstruct SettingsWindow: Scene {\n    @ObservedObject var appState: AppState\n\n    var body: some Scene {\n        Window(Constants.settingsWindowTitle, id: Constants.settingsWindowID) {\n            SettingsView()\n                .readWindow { window in\n                    guard let window else {\n                        return\n                    }\n                    appState.assignSettingsWindow(window)\n                }\n                .frame(minWidth: 825, minHeight: 500)\n        }\n        .commandsRemoved()\n        .windowResizability(.contentSize)\n        .defaultSize(width: 900, height: 625)\n        .environmentObject(appState)\n        .environmentObject(appState.navigationState)\n    }\n}\n"
  },
  {
    "path": "Ice/Swizzling/NSSplitViewItem+swizzledCanCollapse.swift",
    "content": "//\n//  NSSplitViewItem+swizzledCanCollapse.swift\n//  Ice\n//\n\nimport Cocoa\n\nextension NSSplitViewItem {\n    @nonobjc private static let swizzler: () = {\n        let originalCanCollapseSel = #selector(getter: canCollapse)\n        let swizzledCanCollapseSel = #selector(getter: swizzledCanCollapse)\n\n        guard\n            let originalCanCollapseMethod = class_getInstanceMethod(NSSplitViewItem.self, originalCanCollapseSel),\n            let swizzledCanCollapseMethod = class_getInstanceMethod(NSSplitViewItem.self, swizzledCanCollapseSel)\n        else {\n            return\n        }\n\n        method_exchangeImplementations(originalCanCollapseMethod, swizzledCanCollapseMethod)\n    }()\n\n    @objc private var swizzledCanCollapse: Bool {\n        if\n            let window = viewController.view.window,\n            window.identifier?.rawValue == Constants.settingsWindowID\n        {\n            return false\n        }\n        return self.swizzledCanCollapse\n    }\n\n    static func swizzle() {\n        _ = swizzler\n    }\n}\n"
  },
  {
    "path": "Ice/UI/HotkeyRecorder/HotkeyRecorder.swift",
    "content": "//\n//  HotkeyRecorder.swift\n//  Ice\n//\n\nimport SwiftUI\n\nstruct HotkeyRecorder<Label: View>: View {\n    @StateObject private var model: HotkeyRecorderModel\n\n    private let label: Label\n\n    init(hotkey: Hotkey, @ViewBuilder label: () -> Label) {\n        self._model = StateObject(wrappedValue: HotkeyRecorderModel(hotkey: hotkey))\n        self.label = label()\n    }\n\n    var body: some View {\n        IceLabeledContent {\n            HStack(spacing: 1) {\n                leadingSegment\n                trailingSegment\n            }\n            .frame(width: 132, height: 24)\n            .alignmentGuide(.firstTextBaseline) { dimension in\n                dimension[VerticalAlignment.center]\n            }\n        } label: {\n            label\n                .alignmentGuide(.firstTextBaseline) { dimension in\n                    dimension[VerticalAlignment.center]\n                }\n        }\n        .alert(\n            \"Hotkey is reserved by macOS\",\n            isPresented: $model.isPresentingReservedByMacOSError\n        ) {\n            Button(\"OK\") {\n                model.isPresentingReservedByMacOSError = false\n            }\n        }\n    }\n\n    @ViewBuilder\n    private var leadingSegment: some View {\n        Button {\n            model.startRecording()\n        } label: {\n            leadingSegmentLabel\n        }\n        .buttonStyle(\n            HotkeyRecorderSegmentButtonStyle(\n                segment: .leading,\n                isHighlighted: model.isRecording\n            )\n        )\n    }\n\n    @ViewBuilder\n    private var trailingSegment: some View {\n        Button {\n            if model.isRecording {\n                model.stopRecording()\n            } else if model.hotkey.isEnabled {\n                model.hotkey.keyCombination = nil\n            } else {\n                model.startRecording()\n            }\n        } label: {\n            trailingSegmentLabel\n        }\n        .buttonStyle(\n            HotkeyRecorderSegmentButtonStyle(\n                segment: .trailing,\n                isHighlighted: false\n            )\n        )\n        .aspectRatio(1, contentMode: .fit)\n    }\n\n    @ViewBuilder\n    private var leadingSegmentLabel: some View {\n        if model.isRecording {\n            Text(\"Type Hotkey\")\n        } else if model.hotkey.isEnabled {\n            if let keyCombination = model.hotkey.keyCombination {\n                HStack(spacing: 0) {\n                    Text(keyCombination.modifiers.symbolicValue)\n                    Text(keyCombination.key.stringValue.capitalized)\n                }\n            } else {\n                Text(\"ERROR\")\n            }\n        } else {\n            Text(\"Record Hotkey\")\n        }\n    }\n\n    @ViewBuilder\n    private var trailingSegmentLabel: some View {\n        let symbolString = if model.isRecording {\n            \"escape\"\n        } else if model.hotkey.isEnabled {\n            \"xmark.circle.fill\"\n        } else {\n            \"record.circle\"\n        }\n        Image(systemName: symbolString)\n            .resizable()\n            .aspectRatio(contentMode: .fill)\n            .padding(2)\n    }\n}\n\nprivate struct HotkeyRecorderSegmentButtonStyle: PrimitiveButtonStyle {\n    enum Segment {\n        case leading\n        case trailing\n    }\n\n    @State private var frame = CGRect.zero\n    @State private var isPressed = false\n\n    var segment: Segment\n    var isHighlighted: Bool\n\n    private var radii: RectangleCornerRadii {\n        switch segment {\n        case .leading:\n            RectangleCornerRadii(topLeading: 5, bottomLeading: 5)\n        case .trailing:\n            RectangleCornerRadii(bottomTrailing: 5, topTrailing: 5)\n        }\n    }\n\n    func makeBody(configuration: Configuration) -> some View {\n        UnevenRoundedRectangle(cornerRadii: radii, style: .circular)\n            .fill(isHighlighted || isPressed ? .tertiary : .quaternary)\n            .overlay {\n                configuration.label\n                    .lineLimit(1)\n                    .foregroundStyle(.primary)\n                    .padding(EdgeInsets(top: 3, leading: 8, bottom: 3, trailing: 8))\n            }\n            .simultaneousGesture(\n                DragGesture(minimumDistance: 0)\n                    .onChanged { value in\n                        isPressed = frame.contains(value.location)\n                    }\n                    .onEnded { value in\n                        isPressed = false\n                        if frame.contains(value.location) {\n                            configuration.trigger()\n                        }\n                    }\n            )\n            .onFrameChange(update: $frame)\n    }\n}\n"
  },
  {
    "path": "Ice/UI/HotkeyRecorder/HotkeyRecorderModel.swift",
    "content": "//\n//  HotkeyRecorderModel.swift\n//  Ice\n//\n\nimport Combine\nimport SwiftUI\n\n@MainActor\nfinal class HotkeyRecorderModel: ObservableObject {\n    @EnvironmentObject private var appState: AppState\n\n    @Published private(set) var isRecording = false\n\n    @Published var isPresentingReservedByMacOSError = false\n\n    let hotkey: Hotkey\n\n    private lazy var monitor = LocalEventMonitor(mask: .keyDown) { [weak self] event in\n        guard let self else {\n            return event\n        }\n        handleKeyDown(event: event)\n        return nil\n    }\n\n    private var cancellables = Set<AnyCancellable>()\n\n    init(hotkey: Hotkey) {\n        self.hotkey = hotkey\n        configureCancellables()\n    }\n\n    private func configureCancellables() {\n        var c = Set<AnyCancellable>()\n\n        hotkey.objectWillChange\n            .sink { [weak self] in\n                self?.objectWillChange.send()\n            }\n            .store(in: &c)\n\n        cancellables = c\n    }\n\n    func startRecording() {\n        guard !isRecording else {\n            return\n        }\n        hotkey.disable()\n        monitor.start()\n        isRecording = true\n    }\n\n    func stopRecording() {\n        guard isRecording else {\n            return\n        }\n        monitor.stop()\n        hotkey.enable()\n        isRecording = false\n    }\n\n    private func handleKeyDown(event: NSEvent) {\n        let keyCombination = KeyCombination(event: event)\n        guard !keyCombination.modifiers.isEmpty else {\n            if keyCombination.key == .escape {\n                stopRecording()\n            } else {\n                NSSound.beep()\n            }\n            return\n        }\n        guard keyCombination.modifiers != .shift else {\n            NSSound.beep()\n            return\n        }\n        guard !keyCombination.isReservedBySystem else {\n            isPresentingReservedByMacOSError = true\n            return\n        }\n        hotkey.keyCombination = keyCombination\n        stopRecording()\n    }\n}\n"
  },
  {
    "path": "Ice/UI/IceBar/IceBar.swift",
    "content": "//\n//  IceBar.swift\n//  Ice\n//\n\nimport Combine\nimport SwiftUI\n\n// MARK: - IceBarPanel\n\nfinal class IceBarPanel: NSPanel {\n    private weak var appState: AppState?\n\n    private(set) var currentSection: MenuBarSection.Name?\n\n    private lazy var colorManager = IceBarColorManager(iceBarPanel: self)\n\n    private var cancellables = Set<AnyCancellable>()\n\n    init(appState: AppState) {\n        super.init(\n            contentRect: .zero,\n            styleMask: [.nonactivatingPanel, .fullSizeContentView, .borderless],\n            backing: .buffered,\n            defer: false\n        )\n        self.appState = appState\n        self.title = \"Ice Bar\"\n        self.titlebarAppearsTransparent = true\n        self.isMovableByWindowBackground = true\n        self.allowsToolTipsWhenApplicationIsInactive = true\n        self.isFloatingPanel = true\n        self.animationBehavior = .none\n        self.backgroundColor = .clear\n        self.hasShadow = false\n        self.level = .mainMenu + 1\n        self.collectionBehavior = [.fullScreenAuxiliary, .ignoresCycle, .moveToActiveSpace]\n    }\n\n    func performSetup() {\n        configureCancellables()\n    }\n\n    private func configureCancellables() {\n        var c = Set<AnyCancellable>()\n\n        // Close the panel when the active space changes, or when the screen parameters change.\n        Publishers.Merge(\n            NSWorkspace.shared.notificationCenter.publisher(for: NSWorkspace.activeSpaceDidChangeNotification),\n            NotificationCenter.default.publisher(for: NSApplication.didChangeScreenParametersNotification)\n        )\n        .sink { [weak self] _ in\n            self?.close()\n        }\n        .store(in: &c)\n\n        if\n            let section = appState?.menuBarManager.section(withName: .hidden),\n            let window = section.controlItem.window\n        {\n            window.publisher(for: \\.frame)\n                .debounce(for: 0.1, scheduler: DispatchQueue.main)\n                .sink { [weak self, weak window] _ in\n                    guard\n                        let self,\n                        let appState,\n                        // Only continue if the menu bar is automatically hidden, as Ice\n                        // can't currently display its menu bar items.\n                        appState.menuBarManager.isMenuBarHiddenBySystemUserDefaults,\n                        let info = window.flatMap({ WindowInfo(windowID: CGWindowID($0.windowNumber)) }),\n                        // Window being offscreen means the menu bar is currently hidden.\n                        // Close the bar, as things will start to look weird if we don't.\n                        !info.isOnScreen\n                    else {\n                        return\n                    }\n                    close()\n                }\n                .store(in: &c)\n        }\n\n        // Update the panel's origin whenever its size changes.\n        publisher(for: \\.frame)\n            .map(\\.size)\n            .removeDuplicates()\n            .sink { [weak self] _ in\n                guard\n                    let self,\n                    let screen\n                else {\n                    return\n                }\n                updateOrigin(for: screen)\n            }\n            .store(in: &c)\n\n        cancellables = c\n    }\n\n    private func updateOrigin(for screen: NSScreen) {\n        guard let appState else {\n            return\n        }\n\n        func getOrigin(for iceBarLocation: IceBarLocation) -> CGPoint {\n            let menuBarHeight = screen.getMenuBarHeight() ?? 0\n            let originY = ((screen.frame.maxY - 1) - menuBarHeight) - frame.height\n\n            var originForRightOfScreen: CGPoint {\n                CGPoint(x: screen.frame.maxX - frame.width, y: originY)\n            }\n\n            switch iceBarLocation {\n            case .dynamic:\n                if appState.eventManager.isMouseInsideEmptyMenuBarSpace {\n                    return getOrigin(for: .mousePointer)\n                }\n                return getOrigin(for: .iceIcon)\n            case .mousePointer:\n                guard let location = MouseCursor.locationAppKit else {\n                    return getOrigin(for: .iceIcon)\n                }\n\n                let lowerBound = screen.frame.minX\n                let upperBound = screen.frame.maxX - frame.width\n\n                guard lowerBound <= upperBound else {\n                    return originForRightOfScreen\n                }\n\n                return CGPoint(x: (location.x - frame.width / 2).clamped(to: lowerBound...upperBound), y: originY)\n            case .iceIcon:\n                let lowerBound = screen.frame.minX\n                let upperBound = screen.frame.maxX - frame.width\n\n                guard\n                    lowerBound <= upperBound,\n                    let section = appState.menuBarManager.section(withName: .visible),\n                    let windowID = section.controlItem.windowID,\n                    // Bridging.getWindowFrame is more reliable than ControlItem.windowFrame,\n                    // i.e. if the control item is offscreen.\n                    let itemFrame = Bridging.getWindowFrame(for: windowID)\n                else {\n                    return originForRightOfScreen\n                }\n\n                return CGPoint(x: (itemFrame.midX - frame.width / 2).clamped(to: lowerBound...upperBound), y: originY)\n            }\n        }\n\n        setFrameOrigin(getOrigin(for: appState.settingsManager.generalSettingsManager.iceBarLocation))\n    }\n\n    func show(section: MenuBarSection.Name, on screen: NSScreen) async {\n        guard let appState else {\n            return\n        }\n\n        // Important that we set the navigation state and current section before updating the cache.\n        appState.navigationState.isIceBarPresented = true\n        currentSection = section\n\n        await appState.itemManager.cacheItemsIfNeeded()\n\n        if ScreenCapture.cachedCheckPermissions() {\n            await appState.imageCache.updateCache()\n        }\n\n        contentView = IceBarHostingView(appState: appState, colorManager: colorManager, screen: screen, section: section) { [weak self] in\n            self?.close()\n        }\n\n        updateOrigin(for: screen)\n\n        // Color manager must be updated after updating the panel's origin, but before it is shown.\n        //\n        // Color manager handles frame changes automatically, but does so on the main queue, so we\n        // need to update manually once before showing the panel to prevent the color from flashing.\n        colorManager.updateAllProperties(with: frame, screen: screen)\n\n        orderFrontRegardless()\n    }\n\n    override func close() {\n        super.close()\n        contentView = nil\n        currentSection = nil\n        appState?.navigationState.isIceBarPresented = false\n    }\n}\n\n// MARK: - IceBarHostingView\n\nprivate final class IceBarHostingView: NSHostingView<AnyView> {\n    override var safeAreaInsets: NSEdgeInsets {\n        NSEdgeInsets()\n    }\n\n    init(\n        appState: AppState,\n        colorManager: IceBarColorManager,\n        screen: NSScreen,\n        section: MenuBarSection.Name,\n        closePanel: @escaping () -> Void\n    ) {\n        super.init(\n            rootView: IceBarContentView(screen: screen, section: section, closePanel: closePanel)\n                .environmentObject(appState)\n                .environmentObject(appState.imageCache)\n                .environmentObject(appState.itemManager)\n                .environmentObject(appState.menuBarManager)\n                .environmentObject(colorManager)\n                .erasedToAnyView()\n        )\n    }\n\n    @available(*, unavailable)\n    required init?(coder: NSCoder) {\n        fatalError(\"init(coder:) has not been implemented\")\n    }\n\n    @available(*, unavailable)\n    required init(rootView: AnyView) {\n        fatalError(\"init(rootView:) has not been implemented\")\n    }\n\n    override func acceptsFirstMouse(for event: NSEvent?) -> Bool {\n        return true\n    }\n}\n\n// MARK: - IceBarContentView\n\nprivate struct IceBarContentView: View {\n    @EnvironmentObject var appState: AppState\n    @EnvironmentObject var colorManager: IceBarColorManager\n    @EnvironmentObject var itemManager: MenuBarItemManager\n    @EnvironmentObject var imageCache: MenuBarItemImageCache\n    @EnvironmentObject var menuBarManager: MenuBarManager\n    @State private var frame = CGRect.zero\n    @State private var scrollIndicatorsFlashTrigger = 0\n\n    let screen: NSScreen\n    let section: MenuBarSection.Name\n    let closePanel: () -> Void\n\n    private var items: [MenuBarItem] {\n        itemManager.itemCache.managedItems(for: section)\n    }\n\n    private var configuration: MenuBarAppearanceConfigurationV2 {\n        appState.appearanceManager.configuration\n    }\n\n    private var horizontalPadding: CGFloat {\n        configuration.hasRoundedShape ? 7 : 5\n    }\n\n    private var verticalPadding: CGFloat {\n        screen.hasNotch ? 0 : 2\n    }\n\n    private var contentHeight: CGFloat? {\n        guard let menuBarHeight = imageCache.menuBarHeight ?? screen.getMenuBarHeight() else {\n            return nil\n        }\n        if configuration.shapeKind != .none && configuration.isInset && screen.hasNotch {\n            return menuBarHeight - appState.appearanceManager.menuBarInsetAmount * 2\n        }\n        return menuBarHeight\n    }\n\n    private var clipShape: AnyInsettableShape {\n        if configuration.hasRoundedShape {\n            AnyInsettableShape(Capsule())\n        } else {\n            AnyInsettableShape(RoundedRectangle(cornerRadius: frame.height / 5, style: .continuous))\n        }\n    }\n\n    private var shadowOpacity: CGFloat {\n        configuration.current.hasShadow ? 0.5 : 0.33\n    }\n\n    var body: some View {\n        ZStack {\n            content\n                .frame(height: contentHeight)\n                .padding(.horizontal, horizontalPadding)\n                .padding(.vertical, verticalPadding)\n                .layoutBarStyle(appState: appState, averageColorInfo: colorManager.colorInfo)\n                .foregroundStyle(colorManager.colorInfo?.color.brightness ?? 0 > 0.67 ? .black : .white)\n                .clipShape(clipShape)\n                .shadow(color: .black.opacity(shadowOpacity), radius: 2.5)\n\n            if configuration.current.hasBorder {\n                clipShape\n                    .inset(by: configuration.current.borderWidth / 2)\n                    .stroke(lineWidth: configuration.current.borderWidth)\n                    .foregroundStyle(Color(cgColor: configuration.current.borderColor))\n            }\n        }\n        .padding(5)\n        .frame(maxWidth: imageCache.screen?.frame.width)\n        .fixedSize()\n        .onFrameChange(update: $frame)\n    }\n\n    @ViewBuilder\n    private var content: some View {\n        if !ScreenCapture.cachedCheckPermissions() {\n            HStack {\n                Text(\"The Ice Bar requires screen recording permissions.\")\n\n                Button {\n                    closePanel()\n                    appState.navigationState.settingsNavigationIdentifier = .advanced\n                    appState.appDelegate?.openSettingsWindow()\n                } label: {\n                    Text(\"Open Ice Settings\")\n                }\n                .buttonStyle(.plain)\n                .foregroundStyle(.link)\n            }\n            .padding(.horizontal, 10)\n        } else if menuBarManager.isMenuBarHiddenBySystemUserDefaults {\n            Text(\"Ice cannot display menu bar items for automatically hidden menu bars\")\n                .padding(.horizontal, 10)\n        } else if imageCache.cacheFailed(for: section) {\n            Text(\"Unable to display menu bar items\")\n                .padding(.horizontal, 10)\n        } else {\n            ScrollView(.horizontal) {\n                HStack(spacing: 0) {\n                    ForEach(items, id: \\.windowID) { item in\n                        IceBarItemView(item: item, closePanel: closePanel)\n                    }\n                }\n            }\n            .environment(\\.isScrollEnabled, frame.width == imageCache.screen?.frame.width)\n            .defaultScrollAnchor(.trailing)\n            .scrollIndicatorsFlash(trigger: scrollIndicatorsFlashTrigger)\n            .task {\n                scrollIndicatorsFlashTrigger += 1\n            }\n        }\n    }\n}\n\n// MARK: - IceBarItemView\n\nprivate struct IceBarItemView: View {\n    @EnvironmentObject var imageCache: MenuBarItemImageCache\n    @EnvironmentObject var itemManager: MenuBarItemManager\n\n    let item: MenuBarItem\n    let closePanel: () -> Void\n\n    private var leftClickAction: () -> Void {\n        return { [weak itemManager] in\n            guard let itemManager else {\n                return\n            }\n            closePanel()\n            Task {\n                try await Task.sleep(for: .milliseconds(25))\n                itemManager.tempShowItem(item, clickWhenFinished: true, mouseButton: .left)\n            }\n        }\n    }\n\n    private var rightClickAction: () -> Void {\n        return { [weak itemManager] in\n            guard let itemManager else {\n                return\n            }\n            closePanel()\n            Task {\n                try await Task.sleep(for: .milliseconds(25))\n                itemManager.tempShowItem(item, clickWhenFinished: true, mouseButton: .right)\n            }\n        }\n    }\n\n    private var image: NSImage? {\n        guard\n            let image = imageCache.images[item.info],\n            let screen = imageCache.screen\n        else {\n            return nil\n        }\n        let size = CGSize(\n            width: CGFloat(image.width) / screen.backingScaleFactor,\n            height: CGFloat(image.height) / screen.backingScaleFactor\n        )\n        return NSImage(cgImage: image, size: size)\n    }\n\n    var body: some View {\n        if let image {\n            Image(nsImage: image)\n                .contentShape(Rectangle())\n                .overlay {\n                    IceBarItemClickView(item: item, leftClickAction: leftClickAction, rightClickAction: rightClickAction)\n                }\n                .accessibilityLabel(item.displayName)\n                .accessibilityAction(named: \"left click\", leftClickAction)\n                .accessibilityAction(named: \"right click\", rightClickAction)\n        }\n    }\n}\n\n// MARK: - IceBarItemClickView\n\nprivate struct IceBarItemClickView: NSViewRepresentable {\n    private final class Represented: NSView {\n        let item: MenuBarItem\n\n        let leftClickAction: () -> Void\n        let rightClickAction: () -> Void\n\n        private var lastLeftMouseDownDate = Date.now\n        private var lastRightMouseDownDate = Date.now\n\n        private var lastLeftMouseDownLocation = CGPoint.zero\n        private var lastRightMouseDownLocation = CGPoint.zero\n\n        init(item: MenuBarItem, leftClickAction: @escaping () -> Void, rightClickAction: @escaping () -> Void) {\n            self.item = item\n            self.leftClickAction = leftClickAction\n            self.rightClickAction = rightClickAction\n            super.init(frame: .zero)\n            self.toolTip = item.displayName\n        }\n\n        @available(*, unavailable)\n        required init?(coder: NSCoder) {\n            fatalError(\"init(coder:) has not been implemented\")\n        }\n\n        private func absoluteDistance(_ p1: CGPoint, _ p2: CGPoint) -> CGFloat {\n            hypot(p1.x - p2.x, p1.y - p2.y).magnitude\n        }\n\n        override func mouseDown(with event: NSEvent) {\n            super.mouseDown(with: event)\n            lastLeftMouseDownDate = .now\n            lastLeftMouseDownLocation = NSEvent.mouseLocation\n        }\n\n        override func rightMouseDown(with event: NSEvent) {\n            super.rightMouseDown(with: event)\n            lastRightMouseDownDate = .now\n            lastRightMouseDownLocation = NSEvent.mouseLocation\n        }\n\n        override func mouseUp(with event: NSEvent) {\n            super.mouseUp(with: event)\n            guard\n                Date.now.timeIntervalSince(lastLeftMouseDownDate) < 0.5,\n                absoluteDistance(lastLeftMouseDownLocation, NSEvent.mouseLocation) < 5\n            else {\n                return\n            }\n            leftClickAction()\n        }\n\n        override func rightMouseUp(with event: NSEvent) {\n            super.rightMouseUp(with: event)\n            guard\n                Date.now.timeIntervalSince(lastRightMouseDownDate) < 0.5,\n                absoluteDistance(lastRightMouseDownLocation, NSEvent.mouseLocation) < 5\n            else {\n                return\n            }\n            rightClickAction()\n        }\n    }\n\n    let item: MenuBarItem\n\n    let leftClickAction: () -> Void\n    let rightClickAction: () -> Void\n\n    func makeNSView(context: Context) -> NSView {\n        Represented(item: item, leftClickAction: leftClickAction, rightClickAction: rightClickAction)\n    }\n\n    func updateNSView(_ nsView: NSView, context: Context) { }\n}\n"
  },
  {
    "path": "Ice/UI/IceBar/IceBarColorManager.swift",
    "content": "//\n//  IceBarColorManager.swift\n//  Ice\n//\n\nimport Cocoa\nimport Combine\n\nfinal class IceBarColorManager: ObservableObject {\n    @Published private(set) var colorInfo: MenuBarAverageColorInfo?\n\n    private weak var iceBarPanel: IceBarPanel?\n\n    private var windowImage: CGImage?\n\n    private var cancellables = Set<AnyCancellable>()\n\n    init(iceBarPanel: IceBarPanel) {\n        self.iceBarPanel = iceBarPanel\n        configureCancellables()\n    }\n\n    private func configureCancellables() {\n        var c = Set<AnyCancellable>()\n\n        if let iceBarPanel {\n            iceBarPanel.publisher(for: \\.screen)\n                .receive(on: DispatchQueue.main)\n                .sink { [weak self] screen in\n                    guard\n                        let self,\n                        let screen,\n                        screen == .main\n                    else {\n                        return\n                    }\n                    updateWindowImage(for: screen)\n                }\n                .store(in: &c)\n\n            Publishers.CombineLatest(\n                iceBarPanel.publisher(for: \\.frame),\n                iceBarPanel.publisher(for: \\.isVisible)\n            )\n            .receive(on: DispatchQueue.main)\n            .sink { [weak self] frame, isVisible in\n                guard\n                    let self,\n                    let screen = iceBarPanel.screen,\n                    isVisible,\n                    screen == .main\n                else {\n                    return\n                }\n                updateColorInfo(with: frame, screen: screen)\n            }\n            .store(in: &c)\n\n            Publishers.Merge4(\n                NSWorkspace.shared.notificationCenter\n                    .publisher(for: NSWorkspace.activeSpaceDidChangeNotification)\n                    .mapToVoid(),\n                NotificationCenter.default\n                    .publisher(for: NSApplication.didChangeScreenParametersNotification)\n                    .mapToVoid(),\n                DistributedNotificationCenter.default()\n                    .publisher(for: DistributedNotificationCenter.interfaceThemeChangedNotification)\n                    .mapToVoid(),\n                Timer.publish(every: 5, on: .main, in: .default)\n                    .autoconnect()\n                    .mapToVoid()\n            )\n            .receive(on: DispatchQueue.main)\n            .sink { [weak self, weak iceBarPanel] in\n                guard\n                    let self,\n                    let iceBarPanel,\n                    let screen = iceBarPanel.screen,\n                    screen == .main\n                else {\n                    return\n                }\n                updateWindowImage(for: screen)\n                if iceBarPanel.isVisible {\n                    updateColorInfo(with: iceBarPanel.frame, screen: screen)\n                }\n            }\n            .store(in: &c)\n        }\n\n        cancellables = c\n    }\n\n    private func updateWindowImage(for screen: NSScreen) {\n        let displayID = screen.displayID\n        if\n            let window = WindowInfo.getMenuBarWindow(for: displayID),\n            let image = ScreenCapture.captureWindow(window.windowID, option: .nominalResolution)\n        {\n            windowImage = image\n        } else {\n            windowImage = nil\n        }\n    }\n\n    private func updateColorInfo(with frame: CGRect, screen: NSScreen) {\n        guard let windowImage else {\n            colorInfo = nil\n            return\n        }\n\n        let imageBounds = CGRect(x: 0, y: 0, width: windowImage.width, height: windowImage.height)\n        let insetScreenFrame = screen.frame.insetBy(dx: frame.width / 2, dy: 0)\n        let percentage = ((frame.midX - insetScreenFrame.minX) / insetScreenFrame.width).clamped(to: 0...1)\n        let cropRect = CGRect(x: imageBounds.width * percentage, y: 0, width: 0, height: 1)\n            .insetBy(dx: -50, dy: 0)\n            .intersection(imageBounds)\n\n        guard\n            let croppedImage = windowImage.cropping(to: cropRect),\n            let averageColor = croppedImage.averageColor()\n        else {\n            colorInfo = nil\n            return\n        }\n\n        colorInfo = MenuBarAverageColorInfo(color: averageColor, source: .menuBarWindow)\n    }\n\n    func updateAllProperties(with frame: CGRect, screen: NSScreen) {\n        updateWindowImage(for: screen)\n        updateColorInfo(with: frame, screen: screen)\n    }\n}\n"
  },
  {
    "path": "Ice/UI/IceBar/IceBarLocation.swift",
    "content": "//\n//  IceBarLocation.swift\n//  Ice\n//\n\nimport SwiftUI\n\n/// Locations where the Ice Bar can appear.\nenum IceBarLocation: Int, CaseIterable, Identifiable {\n    /// The Ice Bar will appear in different locations based on context.\n    case dynamic = 0\n\n    /// The Ice Bar will appear centered below the mouse pointer.\n    case mousePointer = 1\n\n    /// The Ice Bar will appear centered below the Ice icon.\n    case iceIcon = 2\n\n    var id: Int { rawValue }\n\n    /// Localized string key representation.\n    var localized: LocalizedStringKey {\n        switch self {\n        case .dynamic: \"Dynamic\"\n        case .mousePointer: \"Mouse pointer\"\n        case .iceIcon: \"Ice icon\"\n        }\n    }\n}\n"
  },
  {
    "path": "Ice/UI/IceUI/IceForm.swift",
    "content": "//\n//  IceForm.swift\n//  Ice\n//\n\nimport SwiftUI\n\nstruct IceForm<Content: View>: View {\n    @Environment(\\.isScrollEnabled) private var isScrollEnabled\n    @State private var contentFrame = CGRect.zero\n\n    private let alignment: HorizontalAlignment\n    private let padding: EdgeInsets\n    private let spacing: CGFloat\n    private let content: Content\n\n    init(\n        alignment: HorizontalAlignment = .center,\n        padding: EdgeInsets,\n        spacing: CGFloat = 10,\n        @ViewBuilder content: () -> Content\n    ) {\n        self.alignment = alignment\n        self.padding = padding\n        self.spacing = spacing\n        self.content = content()\n    }\n\n    init(\n        alignment: HorizontalAlignment = .center,\n        padding: CGFloat = 20,\n        spacing: CGFloat = 10,\n        @ViewBuilder content: () -> Content\n    ) {\n        self.init(\n            alignment: alignment,\n            padding: EdgeInsets(top: padding, leading: padding, bottom: padding, trailing: padding),\n            spacing: spacing\n        ) {\n            content()\n        }\n    }\n\n    var body: some View {\n        if isScrollEnabled {\n            GeometryReader { geometry in\n                if contentFrame.height > geometry.size.height {\n                    ScrollView {\n                        contentStack\n                    }\n                    .scrollContentBackground(.hidden)\n                } else {\n                    contentStack\n                }\n            }\n        } else {\n            contentStack\n        }\n    }\n\n    @ViewBuilder\n    private var contentStack: some View {\n        VStack(alignment: alignment, spacing: spacing) {\n            content\n                .toggleStyle(IceFormToggleStyle())\n        }\n        .padding(padding)\n        .onFrameChange(update: $contentFrame)\n    }\n}\n\nprivate struct IceFormToggleStyle: ToggleStyle {\n    func makeBody(configuration: Configuration) -> some View {\n        IceLabeledContent {\n            Toggle(isOn: configuration.$isOn) {\n                configuration.label\n            }\n            .labelsHidden()\n            .toggleStyle(.switch)\n            .controlSize(.mini)\n        } label: {\n            configuration.label\n        }\n    }\n}\n"
  },
  {
    "path": "Ice/UI/IceUI/IceGroupBox.swift",
    "content": "//\n//  IceGroupBox.swift\n//  Ice\n//\n\nimport SwiftUI\n\nstruct IceGroupBox<Header: View, Content: View, Footer: View>: View {\n    private let header: Header\n    private let content: Content\n    private let footer: Footer\n    private let padding: CGFloat\n\n    private var backgroundShape: some InsettableShape {\n        RoundedRectangle(cornerRadius: 6, style: .circular)\n    }\n\n    init(\n        padding: CGFloat = 10,\n        @ViewBuilder header: () -> Header,\n        @ViewBuilder content: () -> Content,\n        @ViewBuilder footer: () -> Footer\n    ) {\n        self.padding = padding\n        self.header = header()\n        self.content = content()\n        self.footer = footer()\n    }\n\n    init(\n        padding: CGFloat = 10,\n        @ViewBuilder content: () -> Content,\n        @ViewBuilder footer: () -> Footer\n    ) where Header == EmptyView {\n        self.init(padding: padding) {\n            EmptyView()\n        } content: {\n            content()\n        } footer: {\n            footer()\n        }\n    }\n\n    init(\n        padding: CGFloat = 10,\n        @ViewBuilder header: () -> Header,\n        @ViewBuilder content: () -> Content\n    ) where Footer == EmptyView {\n        self.init(padding: padding) {\n            header()\n        } content: {\n            content()\n        } footer: {\n            EmptyView()\n        }\n    }\n\n    init(\n        padding: CGFloat = 10,\n        @ViewBuilder content: () -> Content\n    ) where Header == EmptyView, Footer == EmptyView {\n        self.init(padding: padding) {\n            EmptyView()\n        } content: {\n            content()\n        } footer: {\n            EmptyView()\n        }\n    }\n\n    init(\n        _ title: LocalizedStringKey,\n        padding: CGFloat = 10,\n        @ViewBuilder content: () -> Content\n    ) where Header == Text, Footer == EmptyView {\n        self.init(padding: padding) {\n            Text(title)\n                .font(.headline)\n        } content: {\n            content()\n        }\n    }\n\n    var body: some View {\n        VStack(alignment: .leading) {\n            header\n            VStack {\n                content\n            }\n            .padding(padding)\n            .background {\n                backgroundShape\n                    .fill(.quinary)\n                    .overlay {\n                        backgroundShape\n                            .strokeBorder(.quaternary)\n                    }\n            }\n            footer\n        }\n    }\n}\n"
  },
  {
    "path": "Ice/UI/IceUI/IceLabeledContent.swift",
    "content": "//\n//  IceLabeledContent.swift\n//  Ice\n//\n\nimport SwiftUI\n\nstruct IceLabeledContent<Label: View, Content: View>: View {\n    private let label: Label\n    private let content: Content\n\n    init(\n        @ViewBuilder content: () -> Content,\n        @ViewBuilder label: () -> Label\n    ) {\n        self.label = label()\n        self.content = content()\n    }\n\n    init(\n        _ titleKey: LocalizedStringKey,\n        @ViewBuilder content: () -> Content\n    ) where Label == Text {\n        self.init {\n            content()\n        } label: {\n            Text(titleKey)\n        }\n    }\n\n    var body: some View {\n        LabeledContent {\n            content\n                .layoutPriority(1)\n        } label: {\n            label\n                .frame(maxWidth: .infinity, alignment: .leading)\n                .layoutPriority(0)\n        }\n    }\n}\n"
  },
  {
    "path": "Ice/UI/IceUI/IceMenu.swift",
    "content": "//\n//  IceMenu.swift\n//  Ice\n//\n\nimport SwiftUI\n\nstruct IceMenu<Title: View, Label: View, Content: View>: View {\n    private let title: Title\n    private let label: Label\n    private let content: Content\n\n    /// Creates a menu with the given content, title, and label.\n    ///\n    /// - Parameters:\n    ///   - content: A group of menu items.\n    ///   - title: A view to display inside the menu.\n    ///   - label: A view to display as an external label for the menu.\n    init(\n        @ViewBuilder content: () -> Content,\n        @ViewBuilder title: () -> Title,\n        @ViewBuilder label: () -> Label\n    ) {\n        self.title = title()\n        self.label = label()\n        self.content = content()\n    }\n\n    /// Creates a menu with the given content, title, and label key.\n    ///\n    /// - Parameters:\n    ///   - labelKey: A string key for the menu's external label.\n    ///   - content: A group of menu items.\n    ///   - title: A view to display inside the menu.\n    init(\n        _ labelKey: LocalizedStringKey,\n        @ViewBuilder content: () -> Content,\n        @ViewBuilder title: () -> Title\n    ) where Label == Text {\n        self.init {\n            content()\n        } title: {\n            title()\n        } label: {\n            Text(labelKey)\n        }\n    }\n\n    var body: some View {\n        IceLabeledContent {\n            Menu {\n                content\n                    .labelStyle(.titleAndIcon)\n                    .toggleStyle(.automatic)\n            } label: {\n                title\n            }\n            .menuStyle(.button)\n            .buttonStyle(.bordered)\n            .labelsHidden()\n            .fixedSize()\n        } label: {\n            label\n        }\n    }\n}\n"
  },
  {
    "path": "Ice/UI/IceUI/IcePicker.swift",
    "content": "//\n//  IcePicker.swift\n//  Ice\n//\n\nimport SwiftUI\n\nstruct IcePicker<Label: View, SelectionValue: Hashable, Content: View>: View {\n    @Binding var selection: SelectionValue\n\n    let label: Label\n    let content: Content\n\n    init(\n        selection: Binding<SelectionValue>,\n        @ViewBuilder content: () -> Content,\n        @ViewBuilder label: () -> Label\n    ) {\n        self._selection = selection\n        self.label = label()\n        self.content = content()\n    }\n\n    init(\n        _ titleKey: LocalizedStringKey,\n        selection: Binding<SelectionValue>,\n        @ViewBuilder content: () -> Content\n    ) where Label == Text {\n        self.init(selection: selection) {\n            content()\n        } label: {\n            Text(titleKey)\n        }\n    }\n\n    var body: some View {\n        IceLabeledContent {\n            Picker(selection: $selection) {\n                content\n                    .labelStyle(.titleAndIcon)\n                    .toggleStyle(.automatic)\n            } label: {\n                label\n            }\n            .pickerStyle(.menu)\n            .buttonStyle(.bordered)\n            .labelsHidden()\n            .fixedSize()\n        } label: {\n            label\n        }\n    }\n}\n"
  },
  {
    "path": "Ice/UI/IceUI/IceSection.swift",
    "content": "//\n//  IceSection.swift\n//  Ice\n//\n\nimport SwiftUI\n\nstruct IceSectionOptions: OptionSet {\n    let rawValue: Int\n\n    static let isBordered = IceSectionOptions(rawValue: 1 << 0)\n    static let hasDividers = IceSectionOptions(rawValue: 1 << 1)\n\n    static let plain: IceSectionOptions = []\n    static let `default`: IceSectionOptions = [.isBordered, .hasDividers]\n}\n\nstruct IceSection<Header: View, Content: View, Footer: View>: View {\n    private let header: Header\n    private let content: Content\n    private let footer: Footer\n    private let spacing: CGFloat\n    private let options: IceSectionOptions\n\n    private var isBordered: Bool { options.contains(.isBordered) }\n    private var hasDividers: Bool { options.contains(.hasDividers) }\n\n    init(\n        spacing: CGFloat = 10,\n        options: IceSectionOptions = .default,\n        @ViewBuilder header: () -> Header,\n        @ViewBuilder content: () -> Content,\n        @ViewBuilder footer: () -> Footer\n    ) {\n        self.spacing = spacing\n        self.options = options\n        self.header = header()\n        self.content = content()\n        self.footer = footer()\n    }\n\n    init(\n        spacing: CGFloat = 10,\n        options: IceSectionOptions = .default,\n        @ViewBuilder content: () -> Content,\n        @ViewBuilder footer: () -> Footer\n    ) where Header == EmptyView {\n        self.init(spacing: spacing, options: options) {\n            EmptyView()\n        } content: {\n            content()\n        } footer: {\n            footer()\n        }\n    }\n\n    init(\n        spacing: CGFloat = 10,\n        options: IceSectionOptions = .default,\n        @ViewBuilder header: () -> Header,\n        @ViewBuilder content: () -> Content\n    ) where Footer == EmptyView {\n        self.init(spacing: spacing, options: options) {\n            header()\n        } content: {\n            content()\n        } footer: {\n            EmptyView()\n        }\n    }\n\n    init(\n        spacing: CGFloat = 10,\n        options: IceSectionOptions = .default,\n        @ViewBuilder content: () -> Content\n    ) where Header == EmptyView, Footer == EmptyView {\n        self.init(spacing: spacing, options: options) {\n            EmptyView()\n        } content: {\n            content()\n        } footer: {\n            EmptyView()\n        }\n    }\n\n    init(\n        _ title: LocalizedStringKey,\n        spacing: CGFloat = 10,\n        options: IceSectionOptions = .default,\n        @ViewBuilder content: () -> Content\n    ) where Header == Text, Footer == EmptyView {\n        self.init(spacing: spacing, options: options) {\n            Text(title)\n                .font(.headline)\n        } content: {\n            content()\n        }\n    }\n\n    var body: some View {\n        if isBordered {\n            IceGroupBox(padding: spacing) {\n                header\n            } content: {\n                dividedContent\n            } footer: {\n                footer\n            }\n        } else {\n            VStack(alignment: .leading) {\n                header\n                dividedContent\n                footer\n            }\n        }\n    }\n\n    @ViewBuilder\n    private var dividedContent: some View {\n        if hasDividers {\n            _VariadicView.Tree(IceSectionLayout(spacing: spacing)) {\n                content\n                    .frame(maxWidth: .infinity)\n            }\n        } else {\n            content\n                .frame(maxWidth: .infinity)\n        }\n    }\n}\n\nprivate struct IceSectionLayout: _VariadicView_UnaryViewRoot {\n    let spacing: CGFloat\n\n    @ViewBuilder\n    func body(children: _VariadicView.Children) -> some View {\n        let last = children.last?.id\n        VStack(alignment: .leading, spacing: spacing) {\n            ForEach(children) { child in\n                child\n                if child.id != last {\n                    Divider()\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Ice/UI/IceUI/IceSlider.swift",
    "content": "//\n//  IceSlider.swift\n//  Ice\n//\n\nimport CompactSlider\nimport SwiftUI\n\nstruct IceSlider<Value: BinaryFloatingPoint, ValueLabel: View, ValueLabelSelectability: TextSelectability>: View {\n    private let value: Binding<Value>\n    private let bounds: ClosedRange<Value>\n    private let step: Value\n    private let valueLabel: ValueLabel\n    private let valueLabelSelectability: ValueLabelSelectability\n\n    init(\n        value: Binding<Value>,\n        in bounds: ClosedRange<Value> = 0...1,\n        step: Value = 0,\n        valueLabelSelectability: ValueLabelSelectability = .disabled,\n        @ViewBuilder valueLabel: () -> ValueLabel\n    ) {\n        self.value = value\n        self.bounds = bounds\n        self.step = step\n        self.valueLabel = valueLabel()\n        self.valueLabelSelectability = valueLabelSelectability\n    }\n\n    init(\n        _ valueLabelKey: LocalizedStringKey,\n        valueLabelSelectability: ValueLabelSelectability = .disabled,\n        value: Binding<Value>,\n        in bounds: ClosedRange<Value> = 0...1,\n        step: Value = 0\n    ) where ValueLabel == Text {\n        self.init(\n            value: value,\n            in: bounds,\n            step: step,\n            valueLabelSelectability: valueLabelSelectability\n        ) {\n            Text(valueLabelKey)\n        }\n    }\n\n    var body: some View {\n        CompactSlider(\n            value: value,\n            in: bounds,\n            step: step,\n            handleVisibility: .hovering(width: 1)\n        ) {\n            valueLabel\n                .textSelection(valueLabelSelectability)\n        }\n        .compactSliderDisabledHapticFeedback(true)\n    }\n}\n"
  },
  {
    "path": "Ice/UI/LayoutBar/LayoutBar.swift",
    "content": "//\n//  LayoutBar.swift\n//  Ice\n//\n\nimport SwiftUI\n\nstruct LayoutBar: View {\n    private struct Representable: NSViewRepresentable {\n        let appState: AppState\n        let section: MenuBarSection\n        let spacing: CGFloat\n\n        func makeNSView(context: Context) -> LayoutBarScrollView {\n            LayoutBarScrollView(appState: appState, section: section, spacing: spacing)\n        }\n\n        func updateNSView(_ nsView: LayoutBarScrollView, context: Context) {\n            nsView.spacing = spacing\n        }\n    }\n\n    @EnvironmentObject var appState: AppState\n    @EnvironmentObject var imageCache: MenuBarItemImageCache\n\n    let section: MenuBarSection\n    let spacing: CGFloat\n\n    private var menuBarManager: MenuBarManager {\n        appState.menuBarManager\n    }\n\n    private var backgroundShape: some InsettableShape {\n        RoundedRectangle(cornerRadius: 9, style: .circular)\n    }\n\n    init(section: MenuBarSection, spacing: CGFloat = 0) {\n        self.section = section\n        self.spacing = spacing\n    }\n\n    var body: some View {\n        conditionalBody\n            .frame(height: 50)\n            .frame(maxWidth: .infinity)\n            .layoutBarStyle(appState: appState, averageColorInfo: menuBarManager.averageColorInfo)\n            .clipShape(backgroundShape)\n            .overlay {\n                backgroundShape\n                    .stroke(.quaternary)\n            }\n    }\n\n    @ViewBuilder\n    private var conditionalBody: some View {\n        if imageCache.cacheFailed(for: section.name) {\n            Text(\"Unable to display menu bar items\")\n                .foregroundStyle(menuBarManager.averageColorInfo?.color.brightness ?? 0 > 0.67 ? .black : .white)\n        } else {\n            Representable(appState: appState, section: section, spacing: spacing)\n        }\n    }\n}\n"
  },
  {
    "path": "Ice/UI/LayoutBar/LayoutBarContainer.swift",
    "content": "//\n//  LayoutBarContainer.swift\n//  Ice\n//\n\nimport Cocoa\nimport Combine\n\n/// A container for the items in the menu bar layout interface.\nfinal class LayoutBarContainer: NSView {\n    /// Phases for a dragging session.\n    enum DraggingPhase {\n        case entered, exited, updated, ended\n    }\n\n    /// Cached width constraint for the container view.\n    private lazy var widthConstraint: NSLayoutConstraint = {\n        let constraint = widthAnchor.constraint(equalToConstant: 0)\n        constraint.isActive = true\n        return constraint\n    }()\n\n    /// Cached height constraint for the container view.\n    private lazy var heightConstraint: NSLayoutConstraint = {\n        let constraint = heightAnchor.constraint(equalToConstant: 0)\n        constraint.isActive = true\n        return constraint\n    }()\n\n    /// The shared app state instance.\n    private(set) weak var appState: AppState?\n\n    /// The section whose items are represented.\n    let section: MenuBarSection\n\n    /// A Boolean value that indicates whether the container should\n    /// animate its next layout pass.\n    ///\n    /// After each layout pass, this value is reset to `true`.\n    var shouldAnimateNextLayoutPass = true\n\n    /// A Boolean value that indicates whether the container can\n    /// set its arranged views.\n    var canSetArrangedViews = true\n\n    /// The amount of space between each arranged view.\n    var spacing: CGFloat {\n        didSet {\n            layoutArrangedViews()\n        }\n    }\n\n    /// The contaner's arranged views.\n    ///\n    /// The views are laid out from left to right in the order that they\n    /// appear in the array. The ``spacing`` property determines the amount\n    /// of space between each view.\n    var arrangedViews = [LayoutBarItemView]() {\n        didSet {\n            layoutArrangedViews(oldViews: oldValue)\n        }\n    }\n\n    private var cancellables = Set<AnyCancellable>()\n\n    /// Creates a container view with the given app state, section, and spacing.\n    ///\n    /// - Parameters:\n    ///   - appState: The shared app state instance.\n    ///   - section: The section whose items are represented.\n    ///   - spacing: The amount of space between each arranged view.\n    init(appState: AppState, section: MenuBarSection, spacing: CGFloat) {\n        self.appState = appState\n        self.section = section\n        self.spacing = spacing\n        super.init(frame: .zero)\n        self.translatesAutoresizingMaskIntoConstraints = false\n        unregisterDraggedTypes()\n        configureCancellables()\n    }\n\n    @available(*, unavailable)\n    required init?(coder: NSCoder) {\n        fatalError(\"init(coder:) has not been implemented\")\n    }\n\n    private func configureCancellables() {\n        var c = Set<AnyCancellable>()\n\n        if let appState {\n            appState.itemManager.$itemCache\n                .removeDuplicates()\n                .sink { [weak self] cache in\n                    guard let self else {\n                        return\n                    }\n                    setArrangedViews(items: cache.managedItems(for: section.name))\n                }\n                .store(in: &c)\n\n            appState.imageCache.$images\n                .removeDuplicates()\n                .sink { [weak self] _ in\n                    guard let self else {\n                        return\n                    }\n                    layoutArrangedViews()\n                }\n                .store(in: &c)\n        }\n\n        cancellables = c\n    }\n\n    /// Performs layout of the container's arranged views.\n    ///\n    /// The container removes from its subviews the views that are included\n    /// in the `oldViews` array but not in the the current ``arrangedViews``\n    /// array. Views that are found in both arrays, but at different indices\n    /// are animated from their old index to their new index.\n    ///\n    /// - Parameter oldViews: The old value of the container's arranged views.\n    ///   Pass `nil` to use the current ``arrangedViews`` array.\n    private func layoutArrangedViews(oldViews: [LayoutBarItemView]? = nil) {\n        defer {\n            shouldAnimateNextLayoutPass = true\n        }\n\n        let oldViews = oldViews ?? arrangedViews\n\n        // remove views that are no longer part of the arranged views\n        for view in oldViews where !arrangedViews.contains(view) {\n            view.removeFromSuperview()\n            view.hasContainer = false\n        }\n\n        // retain the previous view on each iteration; use its frame\n        // to calculate the x coordinate of the next view's origin\n        var previous: NSView?\n\n        // get the max height of all arranged views to calculate the\n        // y coordinate of each view's origin\n        let maxHeight = arrangedViews.lazy\n            .map { $0.bounds.height }\n            .max() ?? 0\n\n        for var view in arrangedViews {\n            if subviews.contains(view) {\n                // view already exists inside the layout view, but may\n                // have moved from its previous location;\n                if shouldAnimateNextLayoutPass {\n                    // replace the view with its animator proxy\n                    view = view.animator()\n                }\n            } else {\n                // view does not already exist inside the layout view;\n                // add it as a subview\n                addSubview(view)\n                view.hasContainer = true\n            }\n\n            // set the view's origin; if the view is an animator proxy,\n            // it will animate to the new position; otherwise, it must\n            // be a newly added view\n            view.setFrameOrigin(\n                CGPoint(\n                    x: previous.map { $0.frame.maxX + spacing } ?? 0,\n                    y: (maxHeight / 2) - view.bounds.midY\n                )\n            )\n\n            previous = view // retain the view\n        }\n\n        // update the width and height constraints using the information\n        // collected while iterating\n        widthConstraint.constant = previous?.frame.maxX ?? 0\n        heightConstraint.constant = maxHeight\n    }\n\n    /// Sets the container's arranged views with the given items.\n    ///\n    /// - Note: If the value of the container's ``canSetArrangedViews``\n    ///   property is `false`, this function returns early.\n    func setArrangedViews(items: [MenuBarItem]?) {\n        guard\n            let appState,\n            canSetArrangedViews\n        else {\n            return\n        }\n        guard let items else {\n            arrangedViews.removeAll()\n            return\n        }\n        var newViews = [LayoutBarItemView]()\n        for item in items {\n            if let existingView = arrangedViews.first(where: { $0.item == item }) {\n                newViews.append(existingView)\n            } else {\n                let view = LayoutBarItemView(appState: appState, item: item)\n                newViews.append(view)\n            }\n        }\n        arrangedViews = newViews\n    }\n\n    /// Updates the positions of the container's arranged views using the\n    /// specified dragging information and phase.\n    ///\n    /// - Parameters:\n    ///   - draggingInfo: The dragging information to use to update the\n    ///     container's arranged views.\n    ///   - phase: The current dragging phase of the container.\n    /// - Returns: A dragging operation.\n    @discardableResult\n    func updateArrangedViewsForDrag(with draggingInfo: NSDraggingInfo, phase: DraggingPhase) -> NSDragOperation {\n        guard let sourceView = draggingInfo.draggingSource as? LayoutBarItemView else {\n            return []\n        }\n        switch phase {\n        case .entered:\n            if !arrangedViews.contains(sourceView) {\n                shouldAnimateNextLayoutPass = false\n            }\n            return updateArrangedViewsForDrag(with: draggingInfo, phase: .updated)\n        case .exited:\n            if let sourceIndex = arrangedViews.firstIndex(of: sourceView) {\n                shouldAnimateNextLayoutPass = false\n                arrangedViews.remove(at: sourceIndex)\n            }\n            return .move\n        case .updated:\n            if\n                sourceView.oldContainerInfo == nil,\n                let sourceIndex = arrangedViews.firstIndex(of: sourceView)\n            {\n                sourceView.oldContainerInfo = (self, sourceIndex)\n            }\n            // updating normally relies on the presence of other arranged views,\n            // but if the container is empty, it needs to be handled separately\n            guard !arrangedViews.filter({ $0.isEnabled }).isEmpty else {\n                arrangedViews.insert(sourceView, at: 0)\n                return .move\n            }\n            // convert dragging location from window coordinates\n            let draggingLocation = convert(draggingInfo.draggingLocation, from: nil)\n            guard\n                let destinationView = arrangedView(nearestTo: draggingLocation.x),\n                destinationView !== sourceView,\n                // don't rearrange if destination is disabled\n                destinationView.isEnabled,\n                // don't rearrange if in the middle of an animation\n                destinationView.layer?.animationKeys() == nil,\n                let destinationIndex = arrangedViews.firstIndex(of: destinationView)\n            else {\n                return .move\n            }\n            // drag must be near the horizontal center of the destination\n            // view to trigger a swap\n            let midX = destinationView.frame.midX\n            let offset = destinationView.frame.width / 2\n            if !((midX - offset)...(midX + offset)).contains(draggingLocation.x) {\n                if sourceView.oldContainerInfo?.container === self {\n                    return .move\n                }\n            }\n            if let sourceIndex = arrangedViews.firstIndex(of: sourceView) {\n                // source view is already inside this container, so move\n                // it from its old index to the new one\n                var targetIndex = destinationIndex\n                if destinationIndex > sourceIndex {\n                    targetIndex += 1\n                }\n                arrangedViews.move(fromOffsets: [sourceIndex], toOffset: targetIndex)\n            } else {\n                // source view is being dragged from another container,\n                // so just insert it\n                arrangedViews.insert(sourceView, at: destinationIndex)\n            }\n            return .move\n        case .ended:\n            return .move\n        }\n    }\n\n    /// Returns the nearest arranged view to the given X position within\n    /// the coordinate system of the container view.\n    ///\n    /// The nearest arranged view is defined as the arranged view whose\n    /// horizontal center is closest to `xPosition`.\n    ///\n    /// - Parameter xPosition: A floating point value representing an X\n    ///   position within the coordinate system of the container view.\n    func arrangedView(nearestTo xPosition: CGFloat) -> LayoutBarItemView? {\n        arrangedViews.min { view1, view2 in\n            let distance1 = abs(view1.frame.midX - xPosition)\n            let distance2 = abs(view2.frame.midX - xPosition)\n            return distance1 < distance2\n        }\n    }\n}\n"
  },
  {
    "path": "Ice/UI/LayoutBar/LayoutBarItemView.swift",
    "content": "//\n//  LayoutBarItemView.swift\n//  Ice\n//\n\nimport Cocoa\nimport Combine\n\n// MARK: - LayoutBarItemView\n\n/// A view that displays an image in a menu bar layout view.\nfinal class LayoutBarItemView: NSView {\n    private weak var appState: AppState?\n\n    private var cancellables = Set<AnyCancellable>()\n\n    /// The item that the view represents.\n    let item: MenuBarItem\n\n    /// Temporary information that the item view retains when it is moved outside\n    /// of a layout view.\n    ///\n    /// When the item view is dragged outside of a layout view, this property is set\n    /// to hold the layout view's container view, as well as the index of the item\n    /// view in relation to the container's other items. Upon being inserted into a\n    /// new layout view, these values are removed. If the item is dropped outside of\n    /// a layout view, these values are used to reinsert the item view in its original\n    /// layout view.\n    var oldContainerInfo: (container: LayoutBarContainer, index: Int)?\n\n    /// A Boolean value that indicates whether the item view is currently inside a container.\n    var hasContainer = false\n\n    /// The image displayed inside the view.\n    private var image: NSImage? {\n        didSet {\n            if\n                let image,\n                let screen = appState?.imageCache.screen\n            {\n                let size = CGSize(\n                    width: image.size.width / screen.backingScaleFactor,\n                    height: image.size.height / screen.backingScaleFactor\n                )\n                setFrameSize(size)\n            } else {\n                setFrameSize(.zero)\n            }\n            needsDisplay = true\n        }\n    }\n\n    /// A Boolean value that indicates whether the item view is a dragging placeholder.\n    ///\n    /// If this value is `true`, the item view does not draw its image.\n    var isDraggingPlaceholder = false {\n        didSet {\n            needsDisplay = true\n        }\n    }\n\n    /// A Boolean value that indicates whether the view is enabled.\n    var isEnabled = true {\n        didSet {\n            needsDisplay = true\n        }\n    }\n\n    /// Creates a view that displays the given menu bar item.\n    init(appState: AppState, item: MenuBarItem) {\n        self.item = item\n        self.appState = appState\n\n        // set the frame to the full item frame size; the image will be centered when displayed\n        super.init(frame: CGRect(origin: .zero, size: item.frame.size))\n        unregisterDraggedTypes()\n\n        self.toolTip = item.displayName\n        self.isEnabled = item.isMovable\n\n        configureCancellables()\n    }\n\n    @available(*, unavailable)\n    required init?(coder: NSCoder) {\n        fatalError(\"init(coder:) has not been implemented\")\n    }\n\n    private func configureCancellables() {\n        var c = Set<AnyCancellable>()\n\n        if let appState {\n            appState.imageCache.$images\n                .sink { [weak self] images in\n                    guard\n                        let self,\n                        let cgImage = images[item.info]\n                    else {\n                        return\n                    }\n                    image = NSImage(cgImage: cgImage, size: CGSize(width: cgImage.width, height: cgImage.height))\n                }\n                .store(in: &c)\n        }\n\n        cancellables = c\n    }\n\n    /// Provides an alert to display when the item view is disabled.\n    func provideAlertForDisabledItem() -> NSAlert {\n        let alert = NSAlert()\n        alert.messageText = \"Menu bar item is not movable.\"\n        alert.informativeText = \"macOS prohibits \\\"\\(item.displayName)\\\" from being moved.\"\n        return alert\n    }\n\n    /// Provides an alert to display when a menu bar item is unresponsive.\n    func provideAlertForUnresponsiveItem() -> NSAlert {\n        let alert = provideAlertForDisabledItem()\n        alert.informativeText = \"\\(item.displayName) is unresponsive. Until it is restarted, it cannot be moved. Movement of other menu bar items may also be affected until this is resolved.\"\n        return alert\n    }\n\n    override func draw(_ dirtyRect: NSRect) {\n        if !isDraggingPlaceholder {\n            image?.draw(\n                in: bounds,\n                from: .zero,\n                operation: .sourceOver,\n                fraction: isEnabled ? 1.0 : 0.67\n            )\n            if Bridging.responsivity(for: item.ownerPID) == .unresponsive {\n                let warningImage = NSImage.warning\n                let width: CGFloat = 15\n                let scale = width / warningImage.size.width\n                let size = CGSize(\n                    width: width,\n                    height: warningImage.size.height * scale\n                )\n                warningImage.draw(\n                    in: CGRect(\n                        x: bounds.maxX - size.width,\n                        y: bounds.minY,\n                        width: size.width,\n                        height: size.height\n                    )\n                )\n            }\n        }\n    }\n\n    override func mouseDragged(with event: NSEvent) {\n        super.mouseDragged(with: event)\n\n        guard isEnabled else {\n            let alert = provideAlertForDisabledItem()\n            alert.runModal()\n            return\n        }\n\n        guard Bridging.responsivity(for: item.ownerPID) != .unresponsive else {\n            let alert = provideAlertForUnresponsiveItem()\n            alert.runModal()\n            return\n        }\n\n        let pasteboardItem = NSPasteboardItem()\n        // contents of the pasteboard item don't matter here, as all needed information\n        // is available directly from the dragging session; what matters is that the type\n        // is set to `layoutBarItem`, as that is what the layout bar registers for\n        pasteboardItem.setData(Data(), forType: .layoutBarItem)\n\n        let draggingItem = NSDraggingItem(pasteboardWriter: pasteboardItem)\n        draggingItem.setDraggingFrame(bounds, contents: image)\n\n        beginDraggingSession(with: [draggingItem], event: event, source: self)\n    }\n}\n\n// MARK: LayoutBarItemView: NSDraggingSource\nextension LayoutBarItemView: NSDraggingSource {\n    func draggingSession(_ session: NSDraggingSession, sourceOperationMaskFor context: NSDraggingContext) -> NSDragOperation {\n        return .move\n    }\n\n    func draggingSession(_ session: NSDraggingSession, willBeginAt screenPoint: NSPoint) {\n        // make sure the container doesn't update its arranged views and that items\n        // aren't arranged during a dragging session\n        if let container = superview as? LayoutBarContainer {\n            container.canSetArrangedViews = false\n        }\n\n        // prevent the dragging image from animating back to its original location\n        session.animatesToStartingPositionsOnCancelOrFail = false\n\n        // async to prevent the view from disappearing before the dragging image appears\n        DispatchQueue.main.async {\n            self.isDraggingPlaceholder = true\n        }\n    }\n\n    func draggingSession(_ session: NSDraggingSession, endedAt screenPoint: NSPoint, operation: NSDragOperation) {\n        defer {\n            // always remove container info at the end of a session\n            oldContainerInfo = nil\n        }\n\n        // since the session's `animatesToStartingPositionsOnCancelOrFail` property was\n        // set to false when the session began (above), there is no delay between the user\n        // releasing the dragging item and this method being called; thus, `isDraggingPlaceholder`\n        // only needs to be updated here; if we ever decide we want animation, it may also\n        // need to be updated inside `performDragOperation(_:)` on `LayoutBarPaddingView`\n        isDraggingPlaceholder = false\n\n        // if the drop occurs outside of a container, reinsert the view into its original\n        // container at its original index\n        if !hasContainer {\n            guard let (container, index) = oldContainerInfo else {\n                return\n            }\n            container.shouldAnimateNextLayoutPass = false\n            container.arrangedViews.insert(self, at: index)\n        }\n    }\n}\n\nextension LayoutBarItemView: NSAccessibilityLayoutItem { }\n\n// MARK: Layout Bar Item Pasteboard Type\nextension NSPasteboard.PasteboardType {\n    static let layoutBarItem = Self(\"\\(Constants.bundleIdentifier).layout-bar-item\")\n}\n"
  },
  {
    "path": "Ice/UI/LayoutBar/LayoutBarPaddingView.swift",
    "content": "//\n//  LayoutBarPaddingView.swift\n//  Ice\n//\n\nimport Cocoa\nimport Combine\n\n/// A Cocoa view that manages the menu bar layout interface.\nfinal class LayoutBarPaddingView: NSView {\n    private let container: LayoutBarContainer\n\n    /// The section whose items are represented.\n    var section: MenuBarSection {\n        container.section\n    }\n\n    /// The amount of space between each arranged view.\n    var spacing: CGFloat {\n        get { container.spacing }\n        set { container.spacing = newValue }\n    }\n\n    /// The layout view's arranged views.\n    ///\n    /// The views are laid out from left to right in the order that they\n    /// appear in the array. The ``spacing`` property determines the amount\n    /// of space between each view.\n    var arrangedViews: [LayoutBarItemView] {\n        get { container.arrangedViews }\n        set { container.arrangedViews = newValue }\n    }\n\n    /// Creates a layout bar view with the given app state, section, and spacing.\n    ///\n    /// - Parameters:\n    ///   - appState: The shared app state instance.\n    ///   - section: The section whose items are represented.\n    ///   - spacing: The amount of space between each arranged view.\n    init(appState: AppState, section: MenuBarSection, spacing: CGFloat) {\n        self.container = LayoutBarContainer(appState: appState, section: section, spacing: spacing)\n\n        super.init(frame: .zero)\n        addSubview(self.container)\n\n        self.translatesAutoresizingMaskIntoConstraints = false\n        NSLayoutConstraint.activate([\n            // center the container along the y axis\n            container.centerYAnchor.constraint(equalTo: centerYAnchor),\n\n            // give the container a few points of trailing space\n            trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: 7.5),\n\n            // allow variable spacing between leading anchors to let the view stretch\n            // to fit whatever size is required; container should remain aligned toward\n            // the trailing edge; this view is itself nested in a scroll view, so if it\n            // has to expand to a larger size, it can be clipped\n            leadingAnchor.constraint(lessThanOrEqualTo: container.leadingAnchor, constant: -7.5),\n        ])\n\n        registerForDraggedTypes([.layoutBarItem])\n    }\n\n    @available(*, unavailable)\n    required init?(coder: NSCoder) {\n        fatalError(\"init(coder:) has not been implemented\")\n    }\n\n    override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {\n        container.updateArrangedViewsForDrag(with: sender, phase: .entered)\n    }\n\n    override func draggingExited(_ sender: NSDraggingInfo?) {\n        if let sender {\n            container.updateArrangedViewsForDrag(with: sender, phase: .exited)\n        }\n    }\n\n    override func draggingUpdated(_ sender: NSDraggingInfo) -> NSDragOperation {\n        container.updateArrangedViewsForDrag(with: sender, phase: .updated)\n    }\n\n    override func draggingEnded(_ sender: NSDraggingInfo) {\n        container.updateArrangedViewsForDrag(with: sender, phase: .ended)\n    }\n\n    override func performDragOperation(_ sender: NSDraggingInfo) -> Bool {\n        defer {\n            DispatchQueue.main.async {\n                self.container.canSetArrangedViews = true\n            }\n        }\n\n        guard let draggingSource = sender.draggingSource as? LayoutBarItemView else {\n            return false\n        }\n\n        if let index = arrangedViews.firstIndex(of: draggingSource) {\n            if arrangedViews.count == 1 {\n                // dragging source is the only view in the layout bar, so we\n                // need to find a target item\n                let items = MenuBarItem.getMenuBarItems(onScreenOnly: false, activeSpaceOnly: true)\n                let targetItem: MenuBarItem? = switch section.name {\n                case .visible: nil // visible section always has more than 1 item\n                case .hidden: items.first { $0.info == .hiddenControlItem }\n                case .alwaysHidden: items.first { $0.info == .alwaysHiddenControlItem }\n                }\n                if let targetItem {\n                    move(item: draggingSource.item, to: .leftOfItem(targetItem))\n                } else {\n                    Logger.layoutBar.error(\"No target item for layout bar drag\")\n                }\n            } else if arrangedViews.indices.contains(index + 1) {\n                // we have a view to the right of the dragging source\n                let targetItem = arrangedViews[index + 1].item\n                move(item: draggingSource.item, to: .leftOfItem(targetItem))\n            } else if arrangedViews.indices.contains(index - 1) {\n                // we have a view to the left of the dragging source\n                let targetItem = arrangedViews[index - 1].item\n                move(item: draggingSource.item, to: .rightOfItem(targetItem))\n            }\n        }\n\n        return true\n    }\n\n    private func move(item: MenuBarItem, to destination: MenuBarItemManager.MoveDestination) {\n        guard let appState = container.appState else {\n            return\n        }\n        Task {\n            try await Task.sleep(for: .milliseconds(25))\n            do {\n                try await appState.itemManager.slowMove(item: item, to: destination)\n                appState.itemManager.removeTempShownItemFromCache(with: item.info)\n            } catch {\n                Logger.layoutBar.error(\"Error moving menu bar item: \\(error)\")\n                let alert = NSAlert(error: error)\n                alert.runModal()\n            }\n        }\n    }\n}\n\n// MARK: - Logger\nprivate extension Logger {\n    static let layoutBar = Logger(category: \"LayoutBar\")\n}\n"
  },
  {
    "path": "Ice/UI/LayoutBar/LayoutBarScrollView.swift",
    "content": "//\n//  LayoutBarScrollView.swift\n//  Ice\n//\n\nimport Cocoa\n\nfinal class LayoutBarScrollView: NSScrollView {\n    private let paddingView: LayoutBarPaddingView\n\n    /// The amount of space between each arranged view.\n    var spacing: CGFloat {\n        get { paddingView.spacing }\n        set { paddingView.spacing = newValue }\n    }\n\n    /// The layout view's arranged views.\n    ///\n    /// The views are laid out from left to right in the order that they appear in\n    /// the array. The ``spacing`` property determines the amount of space between\n    /// each view.\n    var arrangedViews: [LayoutBarItemView] {\n        get { paddingView.arrangedViews }\n        set { paddingView.arrangedViews = newValue }\n    }\n\n    /// Creates a layout bar scroll view with the given app state, section, and spacing.\n    ///\n    /// - Parameters:\n    ///   - appState: The shared app state instance.\n    ///   - section: The section whose items are represented.\n    ///   - spacing: The amount of space between each arranged view.\n    init(appState: AppState, section: MenuBarSection, spacing: CGFloat) {\n        self.paddingView = LayoutBarPaddingView(appState: appState, section: section, spacing: spacing)\n\n        super.init(frame: .zero)\n\n        self.hasHorizontalScroller = true\n        self.horizontalScroller = HorizontalScroller()\n\n        self.autohidesScrollers = true\n\n        self.verticalScrollElasticity = .none\n        self.horizontalScrollElasticity = .none\n\n        self.drawsBackground = false\n\n        self.documentView = self.paddingView\n\n        self.translatesAutoresizingMaskIntoConstraints = false\n        NSLayoutConstraint.activate([\n            // constrain the padding view's height to the content view's height\n            paddingView.heightAnchor.constraint(equalTo: contentView.heightAnchor),\n\n            // constrain the padding view's width to greater than or equal to the content\n            // view's width\n            paddingView.widthAnchor.constraint(greaterThanOrEqualTo: contentView.widthAnchor),\n\n            // constrain the padding view's trailing anchor to the content view's trailing\n            // anchor; this, in combination with the above width constraint, aligns the\n            // items in the layout bar to the trailing edge\n            paddingView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),\n        ])\n    }\n\n    @available(*, unavailable)\n    required init?(coder: NSCoder) {\n        fatalError(\"init(coder:) has not been implemented\")\n    }\n}\n\nextension LayoutBarScrollView {\n    override func accessibilityChildren() -> [Any]? {\n        return arrangedViews\n    }\n}\n\nextension LayoutBarScrollView {\n    /// A custom scroller that overrides its knob slot to be transparent.\n    final class HorizontalScroller: NSScroller {\n        override static var isCompatibleWithOverlayScrollers: Bool { true }\n\n        override init(frame frameRect: NSRect) {\n            super.init(frame: frameRect)\n            self.controlSize = .mini\n        }\n\n        @available(*, unavailable)\n        required init?(coder: NSCoder) {\n            fatalError(\"init(coder:) has not been implemented\")\n        }\n    }\n}\n"
  },
  {
    "path": "Ice/UI/Pickers/CustomColorPicker/CustomColorPicker.swift",
    "content": "//\n//  CustomColorPicker.swift\n//  Ice\n//\n\nimport Combine\nimport SwiftUI\n\nstruct CustomColorPicker: NSViewRepresentable {\n    final class Coordinator {\n        @Binding var selection: CGColor\n\n        let supportsOpacity: Bool\n        let mode: NSColorPanel.Mode\n\n        private var cancellables = Set<AnyCancellable>()\n\n        init(\n            selection: Binding<CGColor>,\n            supportsOpacity: Bool,\n            mode: NSColorPanel.Mode\n        ) {\n            self._selection = selection\n            self.supportsOpacity = supportsOpacity\n            self.mode = mode\n        }\n\n        func configure(with nsView: NSColorWell) {\n            var c = Set<AnyCancellable>()\n\n            nsView\n                .publisher(for: \\.color)\n                .removeDuplicates()\n                .sink { [weak self] color in\n                    DispatchQueue.main.async {\n                        if self?.selection != color.cgColor {\n                            self?.selection = color.cgColor\n                        }\n                    }\n                }\n                .store(in: &c)\n\n            NSColorPanel.shared\n                .publisher(for: \\.isVisible)\n                .sink { [weak self, weak nsView] isVisible in\n                    guard\n                        let self,\n                        let nsView,\n                        isVisible,\n                        nsView.isActive\n                    else {\n                        return\n                    }\n                    NSColorPanel.shared.showsAlpha = supportsOpacity\n                    NSColorPanel.shared.mode = mode\n                    if let window = nsView.window {\n                        NSColorPanel.shared.level = window.level + 1\n                    }\n                    if NSColorPanel.shared.frame.origin == .zero {\n                        NSColorPanel.shared.center()\n                    }\n                }\n                .store(in: &c)\n\n            NSColorPanel.shared\n                .publisher(for: \\.level)\n                .sink { [weak nsView] level in\n                    guard\n                        let nsView,\n                        nsView.isActive,\n                        let window = nsView.window,\n                        level != window.level + 1\n                    else {\n                        return\n                    }\n                    NSColorPanel.shared.level = window.level + 1\n                }\n                .store(in: &c)\n\n            cancellables = c\n        }\n    }\n\n    @Binding var selection: CGColor\n\n    let supportsOpacity: Bool\n    let mode: NSColorPanel.Mode\n\n    func makeNSView(context: Context) -> NSColorWell {\n        let nsView = NSColorWell()\n        context.coordinator.configure(with: nsView)\n        return nsView\n    }\n\n    func updateNSView(_ nsView: NSColorWell, context: Context) {\n        if let color = NSColor(cgColor: selection) {\n            nsView.color = color\n        }\n        nsView.supportsAlpha = supportsOpacity\n    }\n\n    func makeCoordinator() -> Coordinator {\n        Coordinator(\n            selection: $selection,\n            supportsOpacity: supportsOpacity,\n            mode: mode\n        )\n    }\n\n    func sizeThatFits(\n        _ proposal: ProposedViewSize,\n        nsView: NSColorWell,\n        context: Context\n    ) -> CGSize? {\n        switch nsView.controlSize {\n        case .large:\n            CGSize(width: 55, height: 30)\n        case .regular:\n            CGSize(width: 44, height: 24)\n        case .small:\n            CGSize(width: 33, height: 18)\n        case .mini:\n            CGSize(width: 29, height: 16)\n        @unknown default:\n            nsView.intrinsicContentSize\n        }\n    }\n}\n"
  },
  {
    "path": "Ice/UI/Pickers/CustomGradientPicker/ColorStop.swift",
    "content": "//\n//  ColorStop.swift\n//  Ice\n//\n\nimport CoreGraphics\n\n/// A color stop in a gradient.\nstruct ColorStop: Hashable {\n    /// The color of the stop.\n    var color: CGColor\n    /// The location of the stop relative to its gradient.\n    var location: CGFloat\n\n    /// Returns a copy of the color stop with the given alpha value.\n    func withAlphaComponent(_ alpha: CGFloat) -> ColorStop? {\n        guard let newColor = color.copy(alpha: alpha) else {\n            return nil\n        }\n        return ColorStop(color: newColor, location: location)\n    }\n}\n\nextension ColorStop: Codable {\n    private enum CodingKeys: CodingKey {\n        case color\n        case location\n    }\n\n    init(from decoder: Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        self.color = try container.decode(CodableColor.self, forKey: .color).cgColor\n        self.location = try container.decode(CGFloat.self, forKey: .location)\n    }\n\n    func encode(to encoder: Encoder) throws {\n        var container = encoder.container(keyedBy: CodingKeys.self)\n        try container.encode(CodableColor(cgColor: color), forKey: .color)\n        try container.encode(location, forKey: .location)\n    }\n}\n"
  },
  {
    "path": "Ice/UI/Pickers/CustomGradientPicker/CustomGradient.swift",
    "content": "//\n//  CustomGradient.swift\n//  Ice\n//\n\nimport SwiftUI\n\n/// A custom gradient for use with a ``GradientPicker``.\nstruct CustomGradient: View {\n    /// The color stops in the gradient.\n    var stops: [ColorStop]\n\n    /// The color stops in the gradient, sorted by location.\n    var sortedStops: [ColorStop] {\n        stops.sorted { lhs, rhs in\n            lhs.location < rhs.location\n        }\n    }\n\n    /// A Cocoa representation of this gradient.\n    var nsGradient: NSGradient? {\n        let sortedStops = sortedStops\n        let colors = sortedStops.compactMap { stop in\n            NSColor(cgColor: stop.color)\n        }\n        var locations = sortedStops.map { stop in\n            stop.location\n        }\n        guard colors.count == locations.count else {\n            return nil\n        }\n        return NSGradient(\n            colors: colors,\n            atLocations: &locations,\n            colorSpace: .sRGB\n        )\n    }\n\n    var body: some View {\n        GeometryReader { geometry in\n            if stops.isEmpty {\n                Color.clear\n            } else {\n                Image(\n                    nsImage: NSImage(\n                        size: geometry.size,\n                        flipped: false\n                    ) { bounds in\n                        guard let nsGradient else {\n                            return false\n                        }\n                        nsGradient.draw(in: bounds, angle: 0)\n                        return true\n                    }\n                )\n            }\n        }\n    }\n\n    /// Creates a gradient with the given unsorted stops.\n    ///\n    /// - Parameter stops: An array of color stops to sort and\n    ///   assign as the gradient's color stops.\n    init(unsortedStops stops: [ColorStop]) {\n        self.stops = stops.sorted { $0.location < $1.location }\n    }\n\n    init() {\n        self.init(unsortedStops: [])\n    }\n\n    /// Returns the color at the given location in the gradient.\n    ///\n    /// - Parameter location: A value between 0 and 1 representing\n    ///   the location of the color that should be returned.\n    func color(at location: CGFloat) -> CGColor? {\n        guard\n            let nsColor = nsGradient?.interpolatedColor(atLocation: location),\n            let colorSpace = CGColorSpace(name: CGColorSpace.sRGB)\n        else {\n            return nil\n        }\n        return nsColor.cgColor.converted(\n            to: colorSpace,\n            intent: .defaultIntent,\n            options: nil\n        )\n    }\n\n    /// Returns a copy of the gradient with the given alpha value.\n    func withAlphaComponent(_ alpha: CGFloat) -> CustomGradient {\n        var copy = self\n        copy.stops = copy.stops.map { stop in\n            stop.withAlphaComponent(alpha) ?? stop\n        }\n        return copy\n    }\n}\n\nextension CustomGradient {\n    /// The default menu bar tint gradient.\n    static let defaultMenuBarTint = CustomGradient(\n        unsortedStops: [\n            ColorStop(\n                color: CGColor(srgbRed: 1, green: 1, blue: 1, alpha: 1),\n                location: 0\n            ),\n            ColorStop(\n                color: CGColor(srgbRed: 0, green: 0, blue: 0, alpha: 1),\n                location: 1\n            ),\n        ]\n    )\n}\n\nextension CustomGradient: Codable { }\n\nextension CustomGradient: Hashable { }\n"
  },
  {
    "path": "Ice/UI/Pickers/CustomGradientPicker/CustomGradientPicker.swift",
    "content": "//\n//  CustomGradientPicker.swift\n//  Ice\n//\n\nimport Combine\nimport SwiftUI\n\nstruct CustomGradientPicker: View {\n    @Binding var gradient: CustomGradient\n    @State private var selectedStop: ColorStop?\n    @State private var zOrderedStops: [ColorStop]\n    @State private var window: NSWindow?\n    @State private var cancellables = Set<AnyCancellable>()\n\n    let supportsOpacity: Bool\n    let allowsEmptySelections: Bool\n    let mode: NSColorPanel.Mode\n\n    /// Creates a new gradient picker.\n    ///\n    /// - Parameters:\n    ///   - gradient: A binding to a gradient.\n    ///   - supportsOpacity: A Boolean value indicating whether the\n    ///     picker should support opacity.\n    ///   - allowsEmptySelections: A Boolean value indicating whether\n    ///     the picker should allow empty gradient selections.\n    ///   - mode: The mode that the color panel should take on when\n    ///     picking a color for the gradient.\n    init(\n        gradient: Binding<CustomGradient>,\n        supportsOpacity: Bool,\n        allowsEmptySelections: Bool,\n        mode: NSColorPanel.Mode\n    ) {\n        self._gradient = gradient\n        self.zOrderedStops = gradient.wrappedValue.stops\n        self.supportsOpacity = supportsOpacity\n        self.allowsEmptySelections = allowsEmptySelections\n        self.mode = mode\n    }\n\n    var body: some View {\n        gradientView\n            .clipShape(borderShape)\n            .overlay {\n                borderView\n            }\n            .shadow(radius: 1)\n            .frame(width: 200, height: 18)\n            .overlay {\n                GeometryReader { geometry in\n                    selectionReader(geometry: geometry)\n                    insertionReader(geometry: geometry)\n                    handles(geometry: geometry)\n                }\n            }\n            .foregroundStyle(Color(white: 0.9))\n            .frame(height: 24)\n            .onChange(of: gradient) { _, newValue in\n                gradientChanged(to: newValue)\n            }\n            .readWindow(window: $window)\n    }\n\n    @ViewBuilder\n    private var borderShape: some Shape {\n        RoundedRectangle(cornerRadius: 4, style: .circular)\n    }\n\n    @ViewBuilder\n    private var gradientView: some View {\n        if gradient.stops.isEmpty {\n            Rectangle()\n                .fill(.white.gradient.opacity(0.1))\n                .blendMode(.softLight)\n        } else {\n            gradient\n        }\n    }\n\n    @ViewBuilder\n    private var borderView: some View {\n        borderShape\n            .stroke()\n            .overlay {\n                centerTickMark\n            }\n            .foregroundStyle(.secondary.opacity(0.75))\n            .blendMode(.softLight)\n    }\n\n    @ViewBuilder\n    private var centerTickMark: some View {\n        Rectangle()\n            .frame(width: 1, height: 6)\n    }\n\n    @ViewBuilder\n    private func selectionReader(geometry: GeometryProxy) -> some View {\n        Color.clear\n            .localEventMonitor(mask: .leftMouseDown) { event in\n                guard\n                    let window = event.window,\n                    self.window === window\n                else {\n                    return event\n                }\n                let locationInWindow = event.locationInWindow\n                guard window.contentLayoutRect.contains(locationInWindow) else {\n                    return event\n                }\n                let globalFrame = geometry.frame(in: .global)\n                let flippedLocation = CGPoint(x: locationInWindow.x, y: window.frame.height - locationInWindow.y)\n                if !globalFrame.contains(flippedLocation) {\n                    selectedStop = nil\n                }\n                return event\n            }\n    }\n\n    @ViewBuilder\n    private func insertionReader(geometry: GeometryProxy) -> some View {\n        Color.clear\n            .contentShape(borderShape)\n            .gesture(\n                DragGesture(minimumDistance: 0, coordinateSpace: .local)\n                    .onEnded { value in\n                        guard abs(value.translation.width) <= 2 else {\n                            return\n                        }\n                        let frame = geometry.frame(in: .local)\n                        guard frame.contains(value.location) else {\n                            return\n                        }\n                        let x = value.location.x\n                        let width = frame.width - 10\n                        let location = (x / width) - (6 / width)\n                        insertStop(at: location, select: true)\n                    }\n            )\n    }\n\n    @ViewBuilder\n    private func handles(geometry: GeometryProxy) -> some View {\n        ForEach(gradient.stops.indices, id: \\.self) { index in\n            CustomGradientPickerHandle(\n                gradient: $gradient,\n                selectedStop: $selectedStop,\n                zOrderedStops: $zOrderedStops,\n                cancellables: $cancellables,\n                index: index,\n                supportsOpacity: supportsOpacity,\n                mode: mode,\n                geometry: geometry\n            )\n        }\n    }\n\n    /// Inserts a new stop with the appropriate color at the given location\n    /// in the gradient.\n    private func insertStop(at location: CGFloat, select: Bool) {\n        var location = location.clamped(to: 0...1)\n        if (0.48...0.52).contains(location) {\n            location = 0.5\n        }\n        let newStop: ColorStop = if\n            !gradient.stops.isEmpty,\n            let color = gradient.color(at: location)\n        {\n            ColorStop(color: color, location: location)\n        } else {\n            ColorStop(color: .black, location: location)\n        }\n        gradient.stops.append(newStop)\n        if select {\n            DispatchQueue.main.async {\n                self.selectedStop = newStop\n            }\n        }\n    }\n\n    private func gradientChanged(to gradient: CustomGradient) {\n        if allowsEmptySelections {\n            return\n        }\n        if gradient.stops.isEmpty {\n            self.gradient = .defaultMenuBarTint\n        } else if gradient.stops.count == 1 {\n            var gradient = gradient\n            if gradient.stops[0].location >= 0.5 {\n                gradient.stops[0].location = 1\n                let stop = ColorStop(color: .white, location: 0)\n                gradient.stops.append(stop)\n            } else {\n                gradient.stops[0].location = 0\n                let stop = ColorStop(color: .black, location: 1)\n                gradient.stops.append(stop)\n            }\n            self.gradient = gradient\n        }\n    }\n}\n\nprivate struct CustomGradientPickerHandle: View {\n    @Binding var gradient: CustomGradient\n    @Binding var selectedStop: ColorStop?\n    @Binding var zOrderedStops: [ColorStop]\n    @Binding var cancellables: Set<AnyCancellable>\n    @State private var canActivate = true\n\n    let index: Int\n    let supportsOpacity: Bool\n    let mode: NSColorPanel.Mode\n    let geometry: GeometryProxy\n    let width: CGFloat = 8\n    let height: CGFloat = 22\n\n    private var stop: ColorStop? {\n        get {\n            guard gradient.stops.indices.contains(index) else {\n                return nil\n            }\n            return gradient.stops[index]\n        }\n        nonmutating set {\n            guard gradient.stops.indices.contains(index) else {\n                return\n            }\n            if let newValue {\n                gradient.stops[index] = newValue\n            } else {\n                gradient.stops.remove(at: index)\n            }\n        }\n    }\n\n    var body: some View {\n        if let stop {\n            handleView(cgColor: stop.color)\n                .overlay {\n                    borderView\n                }\n                .frame(width: width, height: height)\n                .overlay {\n                    selectionIndicator(isSelected: selectedStop == stop)\n                }\n                .offset(\n                    x: (geometry.size.width - width) * stop.location,\n                    y: (geometry.size.height - height) / 2\n                )\n                .shadow(radius: 1)\n                .gesture(\n                    DragGesture(minimumDistance: 5)\n                        .onChanged { value in\n                            update(\n                                with: value.location.x,\n                                shouldSnap: abs(value.velocity.width) <= 75\n                            )\n                        }\n                        .onEnded { value in\n                            update(\n                                with: value.location.x,\n                                shouldSnap: true\n                            )\n                        }\n                )\n                .onTapGesture(count: 2) {\n                    if gradient.stops.count == 1 {\n                        gradient.stops[0].location = 0.5\n                    } else {\n                        let last = CGFloat(gradient.stops.count - 1)\n                        gradient.stops = gradient.sortedStops\n                            .enumerated()\n                            .map { n, stop in\n                                var stop = stop\n                                stop.location = CGFloat(n) / last\n                                return stop\n                            }\n                    }\n                }\n                .onTapGesture {\n                    selectedStop = stop\n                }\n                .zIndex(Double(zOrderedStops.firstIndex(of: stop) ?? 0))\n                .onChange(of: selectedStop == stop) {\n                    deactivate()\n                    DispatchQueue.main.async {\n                        if self.selectedStop == stop {\n                            activate()\n                        }\n                    }\n                }\n                .onKeyDown(key: .escape) {\n                    selectedStop = nil\n                }\n                .onKeyDown(key: .delete) {\n                    deleteSelectedStop()\n                }\n        }\n    }\n\n    @ViewBuilder\n    private func handleView(cgColor: CGColor) -> some View {\n        Capsule()\n            .inset(by: -1)\n            .fill(Color(cgColor: cgColor))\n    }\n\n    @ViewBuilder\n    private var borderView: some View {\n        Capsule()\n            .inset(by: -1)\n            .stroke()\n            .foregroundStyle(.secondary.opacity(0.75))\n            .blendMode(.softLight)\n    }\n\n    @ViewBuilder\n    private func selectionIndicator(isSelected: Bool) -> some View {\n        if isSelected {\n            Capsule()\n                .inset(by: -1.5)\n                .stroke(.primary, lineWidth: 1.5)\n        }\n    }\n\n    private func update(with location: CGFloat, shouldSnap: Bool) {\n        guard var stop else {\n            return\n        }\n        let newLocation = (location - (width / 2)) / (geometry.size.width - width)\n        if let index = zOrderedStops.firstIndex(of: stop) {\n            zOrderedStops.remove(at: index)\n        }\n        let isSelected = selectedStop == stop\n        if\n            shouldSnap,\n            (0.48...0.52).contains(newLocation)\n        {\n            stop.location = 0.5\n        } else {\n            stop.location = min(1, max(0, newLocation))\n        }\n        self.stop = stop\n        if isSelected {\n            selectedStop = stop\n        }\n        zOrderedStops.append(stop)\n    }\n\n    private func activate() {\n        guard canActivate else {\n            return\n        }\n\n        deactivate()\n\n        NSColorPanel.shared.showsAlpha = supportsOpacity\n        NSColorPanel.shared.mode = mode\n        if let color = stop.flatMap({ NSColor(cgColor: $0.color) }) {\n            NSColorPanel.shared.color = color\n        }\n        NSColorPanel.shared.orderFrontRegardless()\n\n        if let index = stop.flatMap(zOrderedStops.firstIndex) {\n            zOrderedStops.append(zOrderedStops.remove(at: index))\n        }\n\n        var c = Set<AnyCancellable>()\n\n        NSColorPanel.shared.publisher(for: \\.color)\n            .receive(on: DispatchQueue.main)\n            .dropFirst()\n            .sink { color in\n                canActivate = false\n                defer {\n                    canActivate = true\n                }\n                if stop?.color != color.cgColor {\n                    stop?.color = color.cgColor\n                    selectedStop = stop\n                }\n            }\n            .store(in: &c)\n\n        NSColorPanel.shared.publisher(for: \\.isVisible)\n            .sink { isVisible in\n                if isVisible {\n                    if NSColorPanel.shared.frame.origin == .zero {\n                        NSColorPanel.shared.center()\n                    }\n                } else {\n                    selectedStop = nil\n                }\n            }\n            .store(in: &c)\n\n        cancellables = c\n    }\n\n    private func deactivate() {\n        for cancellable in cancellables {\n            cancellable.cancel()\n        }\n        cancellables.removeAll()\n        NSColorPanel.shared.close()\n    }\n\n    private func deleteSelectedStop() {\n        deactivate()\n        guard\n            let selectedStop,\n            let index = gradient.stops.firstIndex(of: selectedStop)\n        else {\n            return\n        }\n        gradient.stops.remove(at: index)\n        self.selectedStop = nil\n    }\n}\n\n#if DEBUG\nprivate struct CustomGradientPickerPreview: View {\n    @State private var gradient = CustomGradient(unsortedStops: [\n        ColorStop(color: NSColor.systemRed.cgColor, location: 0),\n        ColorStop(color: NSColor.systemBlue.cgColor, location: 1 / 3),\n        ColorStop(color: NSColor.systemIndigo.cgColor, location: 2 / 3),\n        ColorStop(color: NSColor.systemPurple.cgColor, location: 1),\n    ])\n\n    var body: some View {\n        CustomGradientPicker(\n            gradient: $gradient,\n            supportsOpacity: false,\n            allowsEmptySelections: false,\n            mode: .crayon\n        )\n    }\n}\n\n#Preview {\n    CustomGradientPickerPreview()\n        .padding()\n}\n#endif\n"
  },
  {
    "path": "Ice/UI/Shapes/AnyInsettableShape.swift",
    "content": "//\n//  AnyInsettableShape.swift\n//  Ice\n//\n\nimport SwiftUI\n\n/// A type-erased insettable shape.\nstruct AnyInsettableShape: InsettableShape {\n    typealias InsetShape = AnyInsettableShape\n\n    private let base: any InsettableShape\n\n    /// Creates a type-erased insettable shape.\n    init(_ shape: any InsettableShape) {\n        self.base = shape\n    }\n\n    func path(in rect: CGRect) -> Path {\n        base.path(in: rect)\n    }\n\n    func inset(by amount: CGFloat) -> AnyInsettableShape {\n        AnyInsettableShape(base.inset(by: amount))\n    }\n}\n"
  },
  {
    "path": "Ice/UI/ViewModifiers/BottomBar.swift",
    "content": "//\n//  BottomBar.swift\n//  Ice\n//\n\nimport SwiftUI\n\nextension View {\n    /// Adds the given view as a bottom bar to the current view.\n    ///\n    /// - Parameter content: A view to be added as a bottom bar to the current view.\n    func bottomBar<Content: View>(@ViewBuilder content: () -> Content) -> some View {\n        safeAreaInset(edge: .bottom) {\n            content()\n                .background {\n                    Rectangle()\n                        .fill(.quinary.shadow(.inner(radius: 2)))\n                        .shadow(radius: 2)\n                }\n        }\n    }\n}\n"
  },
  {
    "path": "Ice/UI/ViewModifiers/ErasedToAnyView.swift",
    "content": "//\n//  ErasedToAnyView.swift\n//  Ice\n//\n\nimport SwiftUI\n\nextension View {\n    /// Returns a view that has been erased to the `AnyView` type.\n    func erasedToAnyView() -> AnyView {\n        AnyView(erasing: self)\n    }\n}\n"
  },
  {
    "path": "Ice/UI/ViewModifiers/LayoutBarStyle.swift",
    "content": "//\n//  LayoutBarStyle.swift\n//  Ice\n//\n\nimport SwiftUI\n\nextension View {\n    /// Returns a view that is drawn in the style of a layout bar.\n    ///\n    /// - Note: The view this modifier is applied to must be transparent, or the style\n    ///   will be drawn incorrectly.\n    @ViewBuilder\n    func layoutBarStyle(appState: AppState, averageColorInfo: MenuBarAverageColorInfo?) -> some View {\n        background {\n            if appState.isActiveSpaceFullscreen {\n                Color.black\n            } else if let averageColorInfo {\n                switch averageColorInfo.source {\n                case .menuBarWindow:\n                    Color(cgColor: averageColorInfo.color)\n                        .overlay(\n                            Material.bar\n                                .opacity(0.2)\n                                .blendMode(.softLight)\n                        )\n                case .desktopWallpaper:\n                    Color(cgColor: averageColorInfo.color)\n                        .overlay(\n                            Material.bar\n                                .opacity(0.5)\n                                .blendMode(.softLight)\n                        )\n                }\n            } else {\n                Color.defaultLayoutBar\n            }\n        }\n        .overlay {\n            if !appState.isActiveSpaceFullscreen {\n                switch appState.appearanceManager.configuration.current.tintKind {\n                case .none:\n                    EmptyView()\n                case .solid:\n                    Color(cgColor: appState.appearanceManager.configuration.current.tintColor)\n                        .opacity(0.2)\n                        .allowsHitTesting(false)\n                case .gradient:\n                    appState.appearanceManager.configuration.current.tintGradient\n                        .opacity(0.2)\n                        .allowsHitTesting(false)\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Ice/UI/ViewModifiers/LocalEventMonitorModifier.swift",
    "content": "//\n//  LocalEventMonitorModifier.swift\n//  Ice\n//\n\nimport SwiftUI\n\nprivate final class LocalEventMonitorModifierState: ObservableObject {\n    let monitor: LocalEventMonitor\n\n    init(mask: NSEvent.EventTypeMask, action: @escaping (NSEvent) -> NSEvent?) {\n        self.monitor = LocalEventMonitor(mask: mask, handler: action)\n        self.monitor.start()\n    }\n\n    deinit {\n        monitor.stop()\n    }\n}\n\nprivate struct LocalEventMonitorModifier: ViewModifier {\n    @StateObject private var state: LocalEventMonitorModifierState\n\n    init(mask: NSEvent.EventTypeMask, action: @escaping (NSEvent) -> NSEvent?) {\n        let state = LocalEventMonitorModifierState(mask: mask, action: action)\n        self._state = StateObject(wrappedValue: state)\n    }\n\n    func body(content: Content) -> some View {\n        content\n    }\n}\n\nextension View {\n    /// Returns a view that performs the given action when events\n    /// specified by the given mask are received.\n    ///\n    /// - Parameters:\n    ///   - mask: An event type mask specifying which events to monitor.\n    ///   - action: An action to perform when the event monitor receives\n    ///     an event corresponding to the event types in `mask`.\n    func localEventMonitor(\n        mask: NSEvent.EventTypeMask,\n        action: @escaping (NSEvent) -> NSEvent?\n    ) -> some View {\n        modifier(LocalEventMonitorModifier(mask: mask, action: action))\n    }\n}\n"
  },
  {
    "path": "Ice/UI/ViewModifiers/OnFrameChange.swift",
    "content": "//\n//  OnFrameChange.swift\n//  Ice\n//\n\nimport SwiftUI\n\nprivate struct FramePreferenceKey: PreferenceKey {\n    static let defaultValue = CGRect.zero\n\n    static func reduce(value: inout CGRect, nextValue: () -> CGRect) {\n        value = nextValue()\n    }\n}\n\nextension View {\n    /// Adds an action to perform when the view's frame changes.\n    ///\n    /// - Parameters:\n    ///   - coordinateSpace: The coordinate space to use as a reference\n    ///     when accessing the view's frame.\n    ///   - action: The action to perform when the view's frame changes.\n    ///     The `action` closure passes the new frame as its parameter.\n    ///\n    /// - Returns: A view that triggers `action` when its frame changes.\n    func onFrameChange(\n        in coordinateSpace: CoordinateSpace = .local,\n        perform action: @escaping (CGRect) -> Void\n    ) -> some View {\n        background {\n            GeometryReader { proxy in\n                Color.clear\n                    .preference(\n                        key: FramePreferenceKey.self,\n                        value: proxy.frame(in: coordinateSpace)\n                    )\n                    .onPreferenceChange(FramePreferenceKey.self, perform: action)\n            }\n        }\n    }\n\n    /// Returns a version of this view that updates the given binding\n    /// when its frame changes.\n    ///\n    /// - Parameters:\n    ///   - coordinateSpace: The coordinate space to use as a reference\n    ///     when accessing the view's frame.\n    ///   - binding: A binding to update when the view's frame changes.\n    ///\n    /// - Returns: A view that updates `binding` when its frame changes.\n    func onFrameChange(\n        in coordinateSpace: CoordinateSpace = .local,\n        update binding: Binding<CGRect>\n    ) -> some View {\n        onFrameChange(in: coordinateSpace) { frame in\n            binding.wrappedValue = frame\n        }\n    }\n}\n"
  },
  {
    "path": "Ice/UI/ViewModifiers/OnKeyDown.swift",
    "content": "//\n//  OnKeyDown.swift\n//  Ice\n//\n\nimport SwiftUI\n\nextension View {\n    /// Returns a view that performs the given action when\n    /// the specified key is pressed.\n    func onKeyDown(key: KeyCode, action: @escaping () -> Void) -> some View {\n        localEventMonitor(mask: .keyDown) { event in\n            if event.keyCode == key.rawValue {\n                action()\n                return nil\n            }\n            return event\n        }\n    }\n}\n"
  },
  {
    "path": "Ice/UI/ViewModifiers/Once.swift",
    "content": "//\n//  Once.swift\n//  Ice\n//\n\nimport SwiftUI\n\nprivate struct OnceModifier: ViewModifier {\n    @State private var hasAppeared = false\n\n    let onAppear: () -> Void\n\n    func body(content: Content) -> some View {\n        content.onAppear {\n            if !hasAppeared {\n                onAppear()\n                hasAppeared = true\n            }\n        }\n    }\n}\n\nextension View {\n    /// Adds an action to perform exactly once, before the first\n    /// time the view appears.\n    ///\n    /// - Parameter action: The action to perform.\n    func once(perform action: @escaping () -> Void) -> some View {\n        modifier(OnceModifier(onAppear: action))\n    }\n}\n"
  },
  {
    "path": "Ice/UI/ViewModifiers/ReadWindow.swift",
    "content": "//\n//  ReadWindow.swift\n//  Ice\n//\n\nimport Combine\nimport SwiftUI\n\nprivate struct WindowReader: NSViewRepresentable {\n    final class Coordinator: ObservableObject {\n        private var cancellable: AnyCancellable?\n\n        func configure(for view: NSView, onWindowChange: @MainActor @escaping (NSWindow?) -> Void) {\n            cancellable = view.publisher(for: \\.window).sink { window in\n                Task { @MainActor in\n                    onWindowChange(window)\n                }\n            }\n        }\n    }\n\n    let onWindowChange: @MainActor (NSWindow?) -> Void\n\n    func makeNSView(context: Context) -> NSView {\n        let view = NSView()\n        context.coordinator.configure(for: view) { window in\n            onWindowChange(window)\n        }\n        return view\n    }\n\n    func makeCoordinator() -> Coordinator {\n        return Coordinator()\n    }\n\n    func updateNSView(_: NSView, context: Context) { }\n}\n\nextension View {\n    /// Reads the window of this view, performing the given closure when\n    /// the window changes.\n    ///\n    /// - Parameter onChange: A closure to perform when the window changes.\n    func readWindow(onChange: @MainActor @escaping (_ window: NSWindow?) -> Void) -> some View {\n        background {\n            WindowReader(onWindowChange: onChange)\n        }\n    }\n\n    /// Reads the window of this view, assigning it to the given binding.\n    ///\n    /// - Parameter window: A binding to use to store the view's window.\n    func readWindow(window: Binding<NSWindow?>) -> some View {\n        readWindow { window.wrappedValue = $0 }\n    }\n}\n"
  },
  {
    "path": "Ice/UI/ViewModifiers/RemoveSidebarToggle.swift",
    "content": "//\n//  RemoveSidebarToggle.swift\n//  Ice\n//\n\nimport SwiftUI\n\nextension View {\n    /// Removes the sidebar toggle button from the toolbar.\n    func removeSidebarToggle() -> some View {\n        toolbar(removing: .sidebarToggle)\n            .toolbar {\n                Color.clear\n            }\n    }\n}\n"
  },
  {
    "path": "Ice/UI/Views/AnnotationView.swift",
    "content": "//\n//  AnnotationView.swift\n//  Ice\n//\n\nimport SwiftUI\n\n/// A view that displays content as an annotation below a parent view.\nstruct AnnotationView<Parent: View, Content: View, ForegroundStyle: ShapeStyle>: View {\n    private let alignment: HorizontalAlignment\n    private let spacing: CGFloat\n    private let font: Font?\n    private let foregroundStyle: ForegroundStyle\n    private let parent: Parent\n    private let content: Content\n\n    /// Creates an annotation view with a parent and content view.\n    ///\n    /// - Parameters:\n    ///   - alignment: The alignment of the content view in relation to the parent view.\n    ///   - spacing: The spacing between the parent and content view.\n    ///   - font: The font to apply to the content view's environment.\n    ///   - foregroundStyle: The foreground style to apply to the content view's environment.\n    ///   - parent: The parent view of the annotation.\n    ///   - content: The content view of the annotation.\n    init(\n        alignment: HorizontalAlignment = .leading,\n        spacing: CGFloat = 0,\n        font: Font? = .subheadline,\n        foregroundStyle: ForegroundStyle = .secondary,\n        @ViewBuilder parent: () -> Parent,\n        @ViewBuilder content: () -> Content\n    ) {\n        self.alignment = alignment\n        self.spacing = spacing\n        self.font = font\n        self.foregroundStyle = foregroundStyle\n        self.parent = parent()\n        self.content = content()\n    }\n\n    /// Creates an annotation view with a string key and parent view.\n    ///\n    /// - Parameters:\n    ///   - titleKey: The string key to display as text below the parent view.\n    ///   - alignment: The alignment of the content view in relation to the parent view.\n    ///   - spacing: The spacing between the parent and content view.\n    ///   - font: The font to apply to the content view's environment.\n    ///   - foregroundStyle: The foreground style to apply to the content view's environment.\n    ///   - parent: The parent view of the annotation.\n    init(\n        _ titleKey: LocalizedStringKey,\n        alignment: HorizontalAlignment = .leading,\n        spacing: CGFloat = 0,\n        font: Font? = .subheadline,\n        foregroundStyle: ForegroundStyle = .secondary,\n        @ViewBuilder parent: () -> Parent\n    ) where Content == Text {\n        self.init(\n            alignment: alignment,\n            spacing: spacing,\n            font: font,\n            foregroundStyle: foregroundStyle\n        ) {\n            parent()\n        } content: {\n            Text(titleKey)\n        }\n    }\n\n    /// Creates an annotation view with a content view.\n    ///\n    /// - Parameters:\n    ///   - alignment: The alignment of the content view in relation to the parent view.\n    ///   - spacing: The spacing between the parent and content view.\n    ///   - font: The font to apply to the content view's environment.\n    ///   - foregroundStyle: The foreground style to apply to the content view's environment.\n    ///   - content: The content view of the annotation.\n    init(\n        alignment: HorizontalAlignment = .leading,\n        spacing: CGFloat = 0,\n        font: Font? = .subheadline,\n        foregroundStyle: ForegroundStyle = .secondary,\n        @ViewBuilder content: () -> Content\n    ) where Parent == EmptyView {\n        self.init(\n            alignment: alignment,\n            spacing: spacing,\n            font: font,\n            foregroundStyle: foregroundStyle\n        ) {\n            EmptyView()\n        } content: {\n            content()\n        }\n    }\n\n    /// Creates an annotation view with a string key.\n    ///\n    /// - Parameters:\n    ///   - titleKey: The string key to display as text.\n    ///   - alignment: The alignment of the content view in relation to the parent view.\n    ///   - spacing: The spacing between the parent and content view.\n    ///   - font: The font to apply to the content view's environment.\n    ///   - foregroundStyle: The foreground style to apply to the content view's environment.\n    init(\n        _ titleKey: LocalizedStringKey,\n        alignment: HorizontalAlignment = .leading,\n        spacing: CGFloat = 0,\n        font: Font? = .subheadline,\n        foregroundStyle: ForegroundStyle = .secondary\n    ) where Parent == EmptyView, Content == Text {\n        self.init(\n            titleKey,\n            alignment: alignment,\n            spacing: spacing,\n            font: font,\n            foregroundStyle: foregroundStyle\n        ) {\n            EmptyView()\n        }\n    }\n\n    var body: some View {\n        VStack(alignment: alignment, spacing: spacing) {\n            parent\n            content\n                .font(font)\n                .foregroundStyle(foregroundStyle)\n        }\n        .frame(maxWidth: .infinity, alignment: Alignment(horizontal: alignment, vertical: .center))\n    }\n}\n\nextension View {\n    /// Adds the given view as an annotation below this view.\n    ///\n    /// - Parameters:\n    ///   - alignment: The guide for aligning the annotation content horizontally with this view.\n    ///   - spacing: The vertical spacing between this view and the annotation content.\n    ///   - font: The font to apply to the annotation content's environment.\n    ///   - foregroundStyle: The foreground style to apply to the annotation content's environment.\n    ///   - content: A view builder that creates the annotation content.\n    func annotation<Content: View, ForegroundStyle: ShapeStyle>(\n        alignment: HorizontalAlignment = .leading,\n        spacing: CGFloat = 0,\n        font: Font? = .subheadline,\n        foregroundStyle: ForegroundStyle = .secondary,\n        @ViewBuilder content: () -> Content\n    ) -> some View {\n        AnnotationView(\n            alignment: alignment,\n            spacing: spacing,\n            font: font,\n            foregroundStyle: foregroundStyle\n        ) {\n            self\n        } content: {\n            content()\n        }\n    }\n\n    /// Adds the given text as an annotation below this view.\n    ///\n    /// - Parameters:\n    ///   - titleKey: The string key to display as text below this view.\n    ///   - alignment: The guide for aligning the annotation content horizontally with this view.\n    ///   - spacing: The vertical spacing between this view and the annotation content.\n    ///   - font: The font to apply to the annotation content's environment.\n    ///   - foregroundStyle: The foreground style to apply to the annotation content's environment.\n    func annotation<ForegroundStyle: ShapeStyle>(\n        _ titleKey: LocalizedStringKey,\n        alignment: HorizontalAlignment = .leading,\n        spacing: CGFloat = 0,\n        font: Font? = .subheadline,\n        foregroundStyle: ForegroundStyle = .secondary\n    ) -> some View {\n        AnnotationView(\n            titleKey,\n            alignment: alignment,\n            spacing: spacing,\n            font: font,\n            foregroundStyle: foregroundStyle\n        ) {\n            self\n        }\n    }\n}\n"
  },
  {
    "path": "Ice/UI/Views/BetaBadge.swift",
    "content": "//\n//  BetaBadge.swift\n//  Ice\n//\n\nimport SwiftUI\n\n/// A view that displays a badge indicating a beta feature.\nstruct BetaBadge: View {\n    var body: some View {\n        Text(\"BETA\")\n            .font(.caption.bold())\n            .padding(.horizontal, 6)\n            .background {\n                Capsule(style: .circular)\n                    .stroke()\n            }\n            .foregroundStyle(.green)\n    }\n}\n"
  },
  {
    "path": "Ice/UI/Views/SectionedList.swift",
    "content": "//\n//  SectionedList.swift\n//  Ice\n//\n\nimport SwiftUI\n\n// MARK: - SectionedList\n\n/// A scrollable list of items broken up by section.\nstruct SectionedList<ItemID: Hashable>: View {\n    private enum ScrollDirection {\n        case up, down\n    }\n\n    @Binding var selection: ItemID?\n\n    @Binding var items: [SectionedListItem<ItemID>]\n\n    @State private var itemFrames = [ItemID: CGRect]()\n\n    @State private var scrollIndicatorsFlashTrigger = 0\n\n    let spacing: CGFloat\n\n    private(set) var contentPadding = EdgeInsets()\n\n    private var nextSelectableItem: SectionedListItem<ItemID>? {\n        guard\n            let index = items.firstIndex(where: { $0.id == selection }),\n            items.indices.contains(index + 1)\n        else {\n            return nil\n        }\n        return items[(index + 1)...].first { $0.isSelectable }\n    }\n\n    private var previousSelectableItem: SectionedListItem<ItemID>? {\n        guard\n            let index = items.firstIndex(where: { $0.id == selection }),\n            items.indices.contains(index - 1)\n        else {\n            return nil\n        }\n        return items[...(index - 1)].last { $0.isSelectable }\n    }\n\n    /// Creates a sectioned list with the given selection, spacing, and items.\n    init(selection: Binding<ItemID?>, items: Binding<[SectionedListItem<ItemID>]>, spacing: CGFloat = 0) {\n        self._selection = selection\n        self._items = items\n        self.spacing = spacing\n    }\n\n    var body: some View {\n        if #available(macOS 15.0, *) {\n            scrollView\n                .contentMargins(.all, contentPadding, for: .scrollContent)\n                .contentMargins(.all, -0.5, for: .scrollIndicators)\n        } else {\n            scrollView\n                .contentMargins(.all, contentPadding, for: .scrollContent)\n                .contentMargins(.all, -contentPadding, for: .scrollIndicators)\n        }\n    }\n\n    @ViewBuilder\n    private var scrollView: some View {\n        ScrollViewReader { scrollView in\n            GeometryReader { geometry in\n                ScrollView {\n                    scrollContent(scrollView: scrollView, geometry: geometry)\n                }\n            }\n        }\n        .scrollIndicatorsFlash(trigger: scrollIndicatorsFlashTrigger)\n        .onKeyDown(key: .downArrow) {\n            DispatchQueue.main.async {\n                if let nextSelectableItem {\n                    selection = nextSelectableItem.id\n                }\n            }\n        }\n        .onKeyDown(key: .upArrow) {\n            DispatchQueue.main.async {\n                if let previousSelectableItem {\n                    selection = previousSelectableItem.id\n                }\n            }\n        }\n        .onKeyDown(key: .return) {\n            DispatchQueue.main.async {\n                items.first { $0.id == selection }?.action?()\n            }\n        }\n        .task {\n            scrollIndicatorsFlashTrigger += 1\n        }\n    }\n\n    @ViewBuilder\n    private func scrollContent(scrollView: ScrollViewProxy, geometry: GeometryProxy) -> some View {\n        VStack(spacing: spacing) {\n            ForEach(items, id: \\.id) { item in\n                SectionedListItemView(\n                    selection: $selection,\n                    itemFrames: $itemFrames,\n                    item: item\n                )\n                .id(item.id)\n            }\n        }\n        .onChange(of: selection) {\n            guard\n                let selection,\n                let direction = scrollDirection(for: selection, geometry: geometry)\n            else {\n                return\n            }\n            let anchor: UnitPoint = switch direction {\n            case .up: .top\n            case .down: .bottom\n            }\n            scrollView.scrollTo(selection, anchor: anchor)\n        }\n    }\n\n    private func scrollDirection(for selection: ItemID, geometry: GeometryProxy) -> ScrollDirection? {\n        guard let selectionFrame = itemFrames[selection] else {\n            return nil\n        }\n        let geometryFrame = geometry.frame(in: .global)\n        if selectionFrame.minY <= geometryFrame.minY + contentPadding.top {\n            return .up\n        }\n        if selectionFrame.maxY >= geometryFrame.maxY - contentPadding.bottom {\n            return .down\n        }\n        return nil\n    }\n}\n\n// MARK: SectionedList Content Padding\nextension SectionedList {\n    /// Sets the padding of the sectioned list's content.\n    func contentPadding(_ insets: EdgeInsets) -> SectionedList {\n        with(self) { copy in\n            copy.contentPadding = insets\n        }\n    }\n\n    /// Sets the padding of the sectioned list's content.\n    func contentPadding(_ length: CGFloat) -> SectionedList {\n        contentPadding(EdgeInsets(top: length, leading: length, bottom: length, trailing: length))\n    }\n}\n\n// MARK: - SectionedListItem\n\n/// An item in a sectioned list.\nstruct SectionedListItem<ID: Hashable> {\n    let content: AnyView\n    let id: ID\n    let isSelectable: Bool\n    let action: (() -> Void)?\n\n    /// Returns a selectable item for a sectioned list.\n    static func item(id: ID, isSelectable: Bool = true, action: (() -> Void)? = nil, @ViewBuilder content: () -> some View) -> SectionedListItem {\n        SectionedListItem(content: AnyView(content()), id: id, isSelectable: isSelectable, action: action)\n    }\n\n    /// Returns a section header item for a sectioned list.\n    static func header(id: ID, @ViewBuilder content: () -> some View) -> SectionedListItem {\n        item(id: id, isSelectable: false, action: nil) {\n            content()\n        }\n    }\n}\n\n// MARK: - SectionedListItemView\n\nprivate struct SectionedListItemView<ItemID: Hashable>: View {\n    @Binding var selection: ItemID?\n    @Binding var itemFrames: [ItemID: CGRect]\n    @State private var isHovering = false\n\n    let item: SectionedListItem<ItemID>\n\n    var body: some View {\n        ZStack {\n            if item.isSelectable {\n                if selection == item.id {\n                    itemBackground.opacity(0.5)\n                } else if isHovering {\n                    itemBackground.opacity(0.25)\n                }\n            }\n            item.content\n        }\n        .frame(minWidth: 22, minHeight: 22)\n        .contentShape(Rectangle())\n        .onHover { hovering in\n            isHovering = hovering\n        }\n        .onTapGesture {\n            selection = item.id\n        }\n        .simultaneousGesture(\n            TapGesture(count: 2).onEnded {\n                item.action?()\n            }\n        )\n        .onFrameChange(in: .global) { frame in\n            itemFrames[item.id] = frame\n        }\n    }\n\n    @ViewBuilder\n    private var itemBackground: some View {\n        VisualEffectView(material: .selection, blendingMode: .withinWindow)\n            .clipShape(RoundedRectangle(cornerRadius: 5, style: .circular))\n    }\n}\n"
  },
  {
    "path": "Ice/UI/Views/VisualEffectView.swift",
    "content": "//\n//  VisualEffectView.swift\n//  Ice\n//\n\nimport SwiftUI\n\n/// A SwiftUI view that wraps an `NSVisualEffectView`.\nstruct VisualEffectView: NSViewRepresentable {\n    let material: NSVisualEffectView.Material\n    let blendingMode: NSVisualEffectView.BlendingMode\n\n    func makeNSView(context: Context) -> NSVisualEffectView {\n        let visualEffectView = NSVisualEffectView()\n        visualEffectView.material = material\n        visualEffectView.blendingMode = blendingMode\n        visualEffectView.state = .active\n        visualEffectView.isEmphasized = true\n        return visualEffectView\n    }\n\n    func updateNSView(_ visualEffectView: NSVisualEffectView, context: Context) {\n        visualEffectView.material = material\n        visualEffectView.blendingMode = blendingMode\n    }\n}\n"
  },
  {
    "path": "Ice/Updates/UpdatesManager.swift",
    "content": "//\n//  UpdatesManager.swift\n//  Ice\n//\n\nimport Sparkle\nimport SwiftUI\n\n/// Manager for app updates.\n@MainActor\nfinal class UpdatesManager: NSObject, ObservableObject {\n    /// A Boolean value that indicates whether the user can check for updates.\n    @Published var canCheckForUpdates = false\n\n    /// The date of the last update check.\n    @Published var lastUpdateCheckDate: Date?\n\n    /// The shared app state.\n    private(set) weak var appState: AppState?\n\n    /// The underlying updater controller.\n    private(set) lazy var updaterController = SPUStandardUpdaterController(\n        startingUpdater: true,\n        updaterDelegate: self,\n        userDriverDelegate: self\n    )\n\n    /// The underlying updater.\n    var updater: SPUUpdater {\n        updaterController.updater\n    }\n\n    /// A Boolean value that indicates whether to automatically check for updates.\n    var automaticallyChecksForUpdates: Bool {\n        get {\n            updater.automaticallyChecksForUpdates\n        }\n        set {\n            objectWillChange.send()\n            updater.automaticallyChecksForUpdates = newValue\n        }\n    }\n\n    /// A Boolean value that indicates whether to automatically download updates.\n    var automaticallyDownloadsUpdates: Bool {\n        get {\n            updater.automaticallyDownloadsUpdates\n        }\n        set {\n            objectWillChange.send()\n            updater.automaticallyDownloadsUpdates = newValue\n        }\n    }\n\n    /// Creates an updates manager with the given app state.\n    init(appState: AppState) {\n        self.appState = appState\n        super.init()\n    }\n\n    /// Sets up the manager.\n    func performSetup() {\n        _ = updaterController\n        configureCancellables()\n    }\n\n    /// Configures the internal observers for the manager.\n    private func configureCancellables() {\n        updater.publisher(for: \\.canCheckForUpdates)\n            .assign(to: &$canCheckForUpdates)\n        updater.publisher(for: \\.lastUpdateCheckDate)\n            .assign(to: &$lastUpdateCheckDate)\n    }\n\n    /// Checks for app updates.\n    @objc func checkForUpdates() {\n        #if DEBUG\n        // Checking for updates hangs in debug mode.\n        let alert = NSAlert()\n        alert.messageText = \"Checking for updates is not supported in debug mode.\"\n        alert.runModal()\n        #else\n        guard let appState else {\n            return\n        }\n        // Activate the app in case an alert needs to be displayed.\n        appState.activate(withPolicy: .regular)\n        appState.openSettingsWindow()\n        updater.checkForUpdates()\n        #endif\n    }\n}\n\n// MARK: UpdatesManager: SPUUpdaterDelegate\nextension UpdatesManager: @preconcurrency SPUUpdaterDelegate {\n    func updater(_ updater: SPUUpdater, willScheduleUpdateCheckAfterDelay delay: TimeInterval) {\n        guard let appState else {\n            return\n        }\n        appState.userNotificationManager.requestAuthorization()\n    }\n}\n\n// MARK: UpdatesManager: SPUStandardUserDriverDelegate\nextension UpdatesManager: @preconcurrency SPUStandardUserDriverDelegate {\n    var supportsGentleScheduledUpdateReminders: Bool { true }\n\n    func standardUserDriverShouldHandleShowingScheduledUpdate(\n        _ update: SUAppcastItem,\n        andInImmediateFocus immediateFocus: Bool\n    ) -> Bool {\n        if NSApp.isActive {\n            return immediateFocus\n        } else {\n            return false\n        }\n    }\n\n    func standardUserDriverWillHandleShowingUpdate(\n        _ handleShowingUpdate: Bool,\n        forUpdate update: SUAppcastItem,\n        state: SPUUserUpdateState\n    ) {\n        guard let appState else {\n            return\n        }\n        if !state.userInitiated {\n            appState.userNotificationManager.addRequest(\n                with: .updateCheck,\n                title: \"A new update is available\",\n                body: \"Version \\(update.displayVersionString) is now available\"\n            )\n        }\n    }\n\n    func standardUserDriverDidReceiveUserAttention(forUpdate update: SUAppcastItem) {\n        guard let appState else {\n            return\n        }\n        appState.userNotificationManager.removeDeliveredNotifications(with: [.updateCheck])\n    }\n}\n\n// MARK: UpdatesManager: BindingExposable\nextension UpdatesManager: BindingExposable { }\n"
  },
  {
    "path": "Ice/UserNotifications/UserNotificationIdentifier.swift",
    "content": "//\n//  UserNotificationIdentifier.swift\n//  Ice\n//\n\n/// An identifier for a user notification.\nenum UserNotificationIdentifier: String {\n    case updateCheck = \"UpdateCheck\"\n}\n"
  },
  {
    "path": "Ice/UserNotifications/UserNotificationManager.swift",
    "content": "//\n//  UserNotificationManager.swift\n//  Ice\n//\n\nimport UserNotifications\n\n/// Manager for user notifications.\n@MainActor\nfinal class UserNotificationManager: NSObject {\n    /// The shared app state.\n    private(set) weak var appState: AppState?\n\n    /// The current notification center.\n    var notificationCenter: UNUserNotificationCenter { .current() }\n\n    /// Creates a user notification manager with the given app state.\n    init(appState: AppState) {\n        self.appState = appState\n        super.init()\n    }\n\n    /// Sets up the manager.\n    func performSetup() {\n        notificationCenter.delegate = self\n    }\n\n    /// Requests authorization to allow user notifications for the app.\n    func requestAuthorization() {\n        Task {\n            do {\n                try await notificationCenter.requestAuthorization(options: [.badge, .alert, .sound])\n            } catch {\n                Logger.userNotifications.error(\"Failed to request authorization for notifications: \\(error)\")\n            }\n        }\n    }\n\n    /// Schedules the delivery of a local notification.\n    func addRequest(with identifier: UserNotificationIdentifier, title: String, body: String) {\n        let content = UNMutableNotificationContent()\n        content.title = title\n        content.body = body\n\n        let request = UNNotificationRequest(\n            identifier: identifier.rawValue,\n            content: content,\n            trigger: nil\n        )\n\n        notificationCenter.add(request)\n    }\n\n    /// Removes the notifications from Notification Center that match the given identifiers.\n    func removeDeliveredNotifications(with identifiers: [UserNotificationIdentifier]) {\n        notificationCenter.removeDeliveredNotifications(withIdentifiers: identifiers.map { $0.rawValue })\n    }\n}\n\n// MARK: UserNotificationManager: UNUserNotificationCenterDelegate\nextension UserNotificationManager: @preconcurrency UNUserNotificationCenterDelegate {\n    func userNotificationCenter(\n        _ center: UNUserNotificationCenter,\n        didReceive response: UNNotificationResponse,\n        withCompletionHandler completionHandler: @escaping () -> Void\n    ) {\n        defer {\n            completionHandler()\n        }\n\n        guard let appState else {\n            return\n        }\n\n        switch UserNotificationIdentifier(rawValue: response.notification.request.identifier) {\n        case .updateCheck:\n            guard response.actionIdentifier == UNNotificationDefaultActionIdentifier else {\n                break\n            }\n            appState.updatesManager.checkForUpdates()\n        case nil:\n            break\n        }\n    }\n}\n\n// MARK: - Logger\nprivate extension Logger {\n    static let userNotifications = Logger(category: \"UserNotifications\")\n}\n"
  },
  {
    "path": "Ice/Utilities/BindingExposable.swift",
    "content": "//\n//  BindingExposable.swift\n//  Ice\n//\n\nimport SwiftUI\n\n/// A type that exposes its writable properties as bindings.\nprotocol BindingExposable {\n    /// A lens that exposes bindings to the writable properties of this type.\n    typealias Bindings = ExposedBindings<Self>\n\n    /// A lens that exposes bindings to the writable properties of this instance.\n    var bindings: Bindings { get }\n}\n\nextension BindingExposable {\n    var bindings: Bindings {\n        Bindings(base: self)\n    }\n}\n\n/// A lens that exposes bindings to the writable properties of a base object.\n@dynamicMemberLookup\nstruct ExposedBindings<Base: BindingExposable> {\n    /// The object whose bindings are exposed.\n    private let base: Base\n\n    /// Creates a lens that exposes the bindings of the given object.\n    init(base: Base) {\n        self.base = base\n    }\n\n    /// Returns a binding to the property at the given key path.\n    subscript<Value>(dynamicMember keyPath: ReferenceWritableKeyPath<Base, Value>) -> Binding<Value> {\n        Binding(get: { base[keyPath: keyPath] }, set: { base[keyPath: keyPath] = $0 })\n    }\n\n    /// Returns a lens that exposes the bindings of the object at the given key path.\n    subscript<T: BindingExposable>(dynamicMember keyPath: KeyPath<Base, T>) -> ExposedBindings<T> {\n        ExposedBindings<T>(base: base[keyPath: keyPath])\n    }\n}\n"
  },
  {
    "path": "Ice/Utilities/CodableColor.swift",
    "content": "//\n//  CodableColor.swift\n//  Ice\n//\n\nimport CoreGraphics\nimport Foundation\n\n/// A Codable wrapper around a CGColor.\nstruct CodableColor {\n    /// The CGColor contained within the wrapper.\n    var cgColor: CGColor\n}\n\n// MARK: CodableColor: Codable\nextension CodableColor: Codable {\n    private enum CodingKeys: CodingKey {\n        case components\n        case colorSpace\n    }\n\n    init(from decoder: Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        var components = try container.decode([CGFloat].self, forKey: .components)\n        let iccData = try container.decode(Data.self, forKey: .colorSpace) as CFData\n        guard let colorSpace = CGColorSpace(iccData: iccData) else {\n            throw DecodingError.dataCorruptedError(\n                forKey: .colorSpace,\n                in: container,\n                debugDescription: \"Invalid ICC profile data\"\n            )\n        }\n        guard let cgColor = CGColor(colorSpace: colorSpace, components: &components) else {\n            throw DecodingError.dataCorrupted(\n                DecodingError.Context(\n                    codingPath: decoder.codingPath,\n                    debugDescription: \"Invalid color space or components\"\n                )\n            )\n        }\n        self.cgColor = cgColor\n    }\n\n    func encode(to encoder: Encoder) throws {\n        guard let components = cgColor.components else {\n            throw EncodingError.invalidValue(\n                cgColor,\n                EncodingError.Context(\n                    codingPath: encoder.codingPath,\n                    debugDescription: \"Missing color components\"\n                )\n            )\n        }\n        guard let colorSpace = cgColor.colorSpace else {\n            throw EncodingError.invalidValue(\n                cgColor,\n                EncodingError.Context(\n                    codingPath: encoder.codingPath,\n                    debugDescription: \"Missing color space\"\n                )\n            )\n        }\n        guard let iccData = colorSpace.copyICCData() else {\n            throw EncodingError.invalidValue(\n                colorSpace,\n                EncodingError.Context(\n                    codingPath: encoder.codingPath,\n                    debugDescription: \"Missing ICC profile data\"\n                )\n            )\n        }\n        var container = encoder.container(keyedBy: CodingKeys.self)\n        try container.encode(components, forKey: .components)\n        try container.encode(iccData as Data, forKey: .colorSpace)\n    }\n}\n"
  },
  {
    "path": "Ice/Utilities/Constants.swift",
    "content": "//\n//  Constants.swift\n//  Ice\n//\n\nimport Foundation\n\nenum Constants {\n    // swiftlint:disable force_unwrapping\n    /// The version string in the app's bundle.\n    static let versionString = Bundle.main.versionString!\n\n    /// The build string in the app's bundle.\n    static let buildString = Bundle.main.buildString!\n\n    /// The user-readable copyright string in the app's bundle.\n    static let copyrightString = Bundle.main.copyrightString!\n\n    /// The bundle identifier of the app.\n    static let bundleIdentifier = Bundle.main.bundleIdentifier!\n    // swiftlint:enable force_unwrapping\n\n    /// The identifier for the settings window.\n    static let settingsWindowID = \"SettingsWindow\"\n\n    /// The identifier for the permissions window.\n    static let permissionsWindowID = \"PermissionsWindow\"\n\n    /// The title for the settings window.\n    static let settingsWindowTitle = \"Ice\"\n\n    /// The title for the permissions window.\n    static let permissionsWindowTitle = \"Permissions\"\n}\n"
  },
  {
    "path": "Ice/Utilities/Defaults.swift",
    "content": "//\n//  Defaults.swift\n//  Ice\n//\n\nimport Foundation\n\nenum Defaults {\n    /// Returns a dictionary containing the keys and values for\n    /// the defaults meant to be seen by all applications.\n    static var globalDomain: [String: Any] {\n        UserDefaults.standard.persistentDomain(forName: UserDefaults.globalDomain) ?? [:]\n    }\n\n    /// Returns the object for the specified key.\n    ///\n    /// - Parameter key: The key in the UserDefaults database\n    ///   to retrieve the value for.\n    static func object(forKey key: Key) -> Any? {\n        UserDefaults.standard.object(forKey: key.rawValue)\n    }\n\n    /// Returns the string for the specified key.\n    ///\n    /// - Parameter key: The key in the UserDefaults database\n    ///   to retrieve the value for.\n    static func string(forKey key: Key) -> String? {\n        UserDefaults.standard.string(forKey: key.rawValue)\n    }\n\n    /// Returns the array for the specified key.\n    ///\n    /// - Parameter key: The key in the UserDefaults database\n    ///   to retrieve the value for.\n    static func array(forKey key: Key) -> [Any]? {\n        UserDefaults.standard.array(forKey: key.rawValue)\n    }\n\n    /// Returns the dictionary for the specified key.\n    ///\n    /// - Parameter key: The key in the UserDefaults database\n    ///   to retrieve the value for.\n    static func dictionary(forKey key: Key) -> [String: Any]? {\n        UserDefaults.standard.dictionary(forKey: key.rawValue)\n    }\n\n    /// Returns the data for the specified key.\n    ///\n    /// - Parameter key: The key in the UserDefaults database\n    ///   to retrieve the value for.\n    static func data(forKey key: Key) -> Data? {\n        UserDefaults.standard.data(forKey: key.rawValue)\n    }\n\n    /// Returns the string array for the specified key.\n    ///\n    /// - Parameter key: The key in the UserDefaults database\n    ///   to retrieve the value for.\n    static func stringArray(forKey key: Key) -> [String]? {\n        UserDefaults.standard.stringArray(forKey: key.rawValue)\n    }\n\n    /// Returns the integer value for the specified key.\n    ///\n    /// - Parameter key: The key in the UserDefaults database\n    ///   to retrieve the value for.\n    static func integer(forKey key: Key) -> Int {\n        UserDefaults.standard.integer(forKey: key.rawValue)\n    }\n\n    /// Returns the single precision floating point value for\n    /// the specified key.\n    ///\n    /// - Parameter key: The key in the UserDefaults database\n    ///   to retrieve the value for.\n    static func float(forKey key: Key) -> Float {\n        UserDefaults.standard.float(forKey: key.rawValue)\n    }\n\n    /// Returns the double precision floating point value for\n    /// the specified key.\n    ///\n    /// - Parameter key: The key in the UserDefaults database\n    ///   to retrieve the value for.\n    static func double(forKey key: Key) -> Double {\n        UserDefaults.standard.double(forKey: key.rawValue)\n    }\n\n    /// Returns the Boolean value for the specified key.\n    ///\n    /// - Parameter key: The key in the UserDefaults database\n    ///   to retrieve the value for.\n    static func bool(forKey key: Key) -> Bool {\n        UserDefaults.standard.bool(forKey: key.rawValue)\n    }\n\n    /// Returns the url for the specified key.\n    ///\n    /// - Parameter key: The key in the UserDefaults database\n    ///   to retrieve the value for.\n    static func url(forKey key: Key) -> URL? {\n        UserDefaults.standard.url(forKey: key.rawValue)\n    }\n\n    /// Sets the value for the specified key.\n    ///\n    /// - Parameter key: The key in the UserDefaults database\n    ///   to set the value for.\n    static func set(_ value: Any?, forKey key: Key) {\n        UserDefaults.standard.set(value, forKey: key.rawValue)\n    }\n\n    /// Removes the value of the specified key.\n    ///\n    /// - Parameter key: The key in the UserDefaults database\n    ///   to remove the value for.\n    static func removeObject(forKey key: Key) {\n        UserDefaults.standard.removeObject(forKey: key.rawValue)\n    }\n\n    /// Retrieves the value for the given key, and, if it is\n    /// present, assigns it to the given `inout` parameter.\n    static func ifPresent<Value>(key: Key, assign value: inout Value) {\n        if let found = object(forKey: key) as? Value {\n            value = found\n        }\n    }\n\n    /// Retrieves the value for the given key, and, if it is\n    /// present, performs the given closure.\n    static func ifPresent<Value>(key: Key, body: (Value) throws -> Void) rethrows {\n        if let found = object(forKey: key) as? Value {\n            try body(found)\n        }\n    }\n}\n\nextension Defaults {\n    enum Key: String {\n\n        // MARK: General Settings\n\n        case showIceIcon = \"ShowIceIcon\"\n        case iceIcon = \"IceIcon\"\n        case customIceIconIsTemplate = \"CustomIceIconIsTemplate\"\n        case useIceBar = \"UseIceBar\"\n        case showOnClick = \"ShowOnClick\"\n        case showOnHover = \"ShowOnHover\"\n        case showOnScroll = \"ShowOnScroll\"\n        case itemSpacingOffset = \"ItemSpacingOffset\"\n        case autoRehide = \"AutoRehide\"\n        case rehideStrategy = \"RehideStrategy\"\n        case rehideInterval = \"RehideInterval\"\n\n        // MARK: Hotkey Settings\n\n        case hotkeys = \"Hotkeys\"\n\n        // MARK: Advanced Settings\n\n        case hideApplicationMenus = \"HideApplicationMenus\"\n        case showSectionDividers = \"ShowSectionDividers\"\n        case enableAlwaysHiddenSection = \"EnableAlwaysHiddenSection\"\n        case canToggleAlwaysHiddenSection = \"CanToggleAlwaysHiddenSection\"\n        case showOnHoverDelay = \"ShowOnHoverDelay\"\n        case tempShowInterval = \"TempShowInterval\"\n        case showAllSectionsOnUserDrag = \"ShowAllSectionsOnUserDrag\"\n        case showContextMenuOnRightClick = \"ShowContextMenuOnRightClick\"\n\n        // MARK: Menu Bar Appearance Settings\n\n        case menuBarAppearanceConfigurationV2 = \"MenuBarAppearanceConfigurationV2\"\n\n        // MARK: Ice Bar Settings\n\n        case iceBarLocation = \"IceBarLocation\"\n        case iceBarPinnedLocation = \"IceBarPinnedLocation\"\n\n        // MARK: Migration\n\n        case hasMigrated0_8_0 = \"hasMigrated0_8_0\"\n        case hasMigrated0_10_0 = \"hasMigrated0_10_0\"\n        case hasMigrated0_10_1 = \"hasMigrated0_10_1\"\n        case hasMigrated0_11_10 = \"hasMigrated0_11_10\"\n\n        // MARK: Deprecated\n\n        case sections = \"Sections\"\n        case menuBarHasBorder = \"MenuBarHasBorder\"\n        case menuBarBorderColor = \"MenuBarBorderColor\"\n        case menuBarBorderWidth = \"MenuBarBorderWidth\"\n        case menuBarHasShadow = \"MenuBarHasShadow\"\n        case menuBarTintKind = \"MenuBarTintKind\"\n        case menuBarTintColor = \"MenuBarTintColor\"\n        case menuBarTintGradient = \"MenuBarTintGradient\"\n        case menuBarShapeKind = \"MenuBarShapeKind\"\n        case menuBarFullShapeInfo = \"MenuBarFullShapeInfo\"\n        case menuBarSplitShapeInfo = \"MenuBarSplitShapeInfo\"\n        case menuBarAppearanceConfiguration = \"MenuBarAppearanceConfiguration\"\n    }\n}\n"
  },
  {
    "path": "Ice/Utilities/Extensions.swift",
    "content": "//\n//  Extensions.swift\n//  Ice\n//\n\nimport Combine\nimport SwiftUI\n\n// MARK: - Bundle\n\nextension Bundle {\n    /// The bundle's copyright string.\n    ///\n    /// This accessor looks for an associated value for the \"NSHumanReadableCopyright\"\n    /// key in the bundle's Info.plist. If a string value cannot be found for this key,\n    /// this accessor returns `nil`.\n    var copyrightString: String? {\n        object(forInfoDictionaryKey: \"NSHumanReadableCopyright\") as? String\n    }\n\n    /// The bundle's version string.\n    ///\n    /// This accessor looks for an associated value for the \"CFBundleShortVersionString\"\n    /// key in the bundle's Info.plist. If a string value cannot be found for this key,\n    /// this accessor returns `nil`.\n    var versionString: String? {\n        object(forInfoDictionaryKey: \"CFBundleShortVersionString\") as? String\n    }\n\n    /// The bundle's build string.\n    ///\n    /// This accessor looks for an associated value for the \"CFBundleVersion\" key in\n    /// the bundle's Info.plist. If a string value cannot be found for this key, this\n    /// accessor returns `nil`.\n    var buildString: String? {\n        object(forInfoDictionaryKey: \"CFBundleVersion\") as? String\n    }\n}\n\n// MARK: - CGColor\n\nextension CGColor {\n    /// The brightness of the color.\n    var brightness: CGFloat? {\n        guard\n            let rgb = converted(to: CGColorSpaceCreateDeviceRGB(), intent: .defaultIntent, options: nil),\n            let components = rgb.components\n        else {\n            return nil\n        }\n        // Algorithm from http://www.w3.org/WAI/ER/WD-AERT/#color-contrast\n        return ((components[0] * 299) + (components[1] * 587) + (components[2] * 114)) / 1000\n    }\n}\n\n// MARK: - CGError\n\nextension CGError {\n    /// A string to use for logging purposes.\n    var logString: String {\n        switch self {\n        case .success: \"\\(rawValue): success\"\n        case .failure: \"\\(rawValue): failure\"\n        case .illegalArgument: \"\\(rawValue): illegalArgument\"\n        case .invalidConnection: \"\\(rawValue): invalidConnection\"\n        case .invalidContext: \"\\(rawValue): invalidContext\"\n        case .cannotComplete: \"\\(rawValue): cannotComplete\"\n        case .notImplemented: \"\\(rawValue): notImplemented\"\n        case .rangeCheck: \"\\(rawValue): rangeCheck\"\n        case .typeCheck: \"\\(rawValue): typeCheck\"\n        case .invalidOperation: \"\\(rawValue): invalidOperation\"\n        case .noneAvailable: \"\\(rawValue): noneAvailable\"\n        @unknown default: \"\\(rawValue): unknown\"\n        }\n    }\n}\n\n// MARK: - CGImage\n\nextension CGImage {\n\n    // MARK: Average Color\n\n    /// Computes and returns the average color of the image.\n    ///\n    /// - Parameters:\n    ///   - alphaThreshold: An alpha value below which pixels should be ignored. Pixels with\n    ///     an alpha component greater than or equal to this value contribute to the average.\n    ///   - makeOpaque: A Boolean value that indicates whether the resulting color should be\n    ///     made opaque, regardless of the alpha content of the image.\n    func averageColor(alphaThreshold: CGFloat = 0.5, makeOpaque: Bool = false) -> CGColor? {\n        func createPixelData(width: Int, height: Int) -> [UInt32]? {\n            var data = [UInt32](repeating: 0, count: width * height)\n            guard let context = CGContext(\n                data: &data,\n                width: width,\n                height: height,\n                bitsPerComponent: 8,\n                bytesPerRow: width * 4,\n                space: CGColorSpaceCreateDeviceRGB(),\n                bitmapInfo: CGImageByteOrderInfo.order32Little.rawValue | CGImageAlphaInfo.premultipliedFirst.rawValue\n            ) else {\n                return nil\n            }\n            context.draw(self, in: CGRect(x: 0, y: 0, width: width, height: height))\n            return data\n        }\n\n        func computeComponent(shift: UInt32, pixel: UInt32) -> Int {\n            return Int((pixel >> shift) & 255)\n        }\n\n        // Resize the image for better performance.\n        let width = min(width, 10)\n        let height = min(height, 10)\n\n        guard let pixelData = createPixelData(width: width, height: height) else {\n            return nil\n        }\n\n        // Convert the alpha threshold to a valid component for comparison.\n        let alphaThreshold = Int((alphaThreshold.clamped(to: 0...1) * 255).rounded(.toNearestOrAwayFromZero))\n\n        var includedPixelCount = width * height\n        var totals = (red: 0, green: 0, blue: 0, alpha: 0)\n\n        for column in 0..<width {\n            for row in 0..<height {\n                let pixel = pixelData[(row * width) + column]\n\n                // Check alpha before computing other components.\n                let alphaComponent = computeComponent(shift: 24, pixel: pixel)\n\n                guard alphaComponent >= alphaThreshold else {\n                    includedPixelCount -= 1 // Don't include this pixel.\n                    continue\n                }\n\n                // Add the components to the totals.\n                totals.red += computeComponent(shift: 16, pixel: pixel)\n                totals.green += computeComponent(shift: 8, pixel: pixel)\n                totals.blue += computeComponent(shift: 0, pixel: pixel)\n                totals.alpha += alphaComponent\n            }\n        }\n\n        // Multiply the included pixel count by 255 to convert the components\n        // to their corresponding floating point values.\n        let adjustedPixelCount = CGFloat(includedPixelCount * 255)\n\n        return CGColor(\n            red: CGFloat(totals.red) / adjustedPixelCount,\n            green: CGFloat(totals.green) / adjustedPixelCount,\n            blue: CGFloat(totals.blue) / adjustedPixelCount,\n            alpha: makeOpaque ? 1 : CGFloat(totals.alpha) / adjustedPixelCount\n        )\n    }\n\n    // MARK: Trim Transparent Pixels\n\n    /// A context for handling transparency data in an image.\n    private struct TransparencyContext: ~Copyable {\n        private let image: CGImage\n        private let maxAlpha: UInt8\n        private let cgContext: CGContext\n        private let zeroByteBlock: UnsafeMutableRawPointer\n        private let rowRange: LazySequence<Range<Int>>\n        private let columnRange: LazySequence<Range<Int>>\n\n        /// Creates a context with the given image and alpha threshold.\n        ///\n        /// - Parameters:\n        ///   - image: The image to form a context around.\n        ///   - maxAlpha: The maximum alpha value to consider transparent.\n        init?(image: CGImage, maxAlpha: UInt8) {\n            guard\n                let cgContext = CGContext(\n                    data: nil,\n                    width: image.width,\n                    height: image.height,\n                    bitsPerComponent: 8,\n                    bytesPerRow: 0,\n                    space: CGColorSpaceCreateDeviceGray(),\n                    bitmapInfo: CGImageAlphaInfo.alphaOnly.rawValue\n                ),\n                cgContext.data != nil,\n                let zeroByteBlock = calloc(image.width, MemoryLayout<UInt8>.size)\n            else {\n                return nil\n            }\n\n            cgContext.draw(image, in: CGRect(x: 0, y: 0, width: image.width, height: image.height))\n\n            self.image = image\n            self.maxAlpha = maxAlpha\n            self.cgContext = cgContext\n            self.zeroByteBlock = zeroByteBlock\n            self.rowRange = (0..<image.height).lazy\n            self.columnRange = (0..<image.width).lazy\n        }\n\n        deinit {\n            free(zeroByteBlock)\n        }\n\n        /// Trims transparent pixels from the context.\n        func trim(edges: Set<CGRectEdge>) -> CGImage? {\n            guard\n                maxAlpha < 255,\n                !edges.isEmpty\n            else {\n                return image // Nothing to trim.\n            }\n\n            guard\n                let minYInset = inset(for: .minYEdge, in: edges),\n                let maxYInset = inset(for: .maxYEdge, in: edges),\n                let minXInset = inset(for: .minXEdge, in: edges),\n                let maxXInset = inset(for: .maxXEdge, in: edges)\n            else {\n                return nil\n            }\n\n            guard (minYInset, maxYInset, minXInset, maxXInset) != (0, 0, 0, 0) else {\n                return image // Already trimmed.\n            }\n\n            let insetRect = CGRect(\n                x: minXInset,\n                y: maxYInset,\n                width: image.width - (minXInset + maxXInset),\n                height: image.height - (minYInset + maxYInset)\n            )\n\n            return image.cropping(to: insetRect)\n        }\n\n        private func inset(for edge: CGRectEdge, in edges: Set<CGRectEdge>) -> Int? {\n            guard edges.contains(edge) else {\n                return 0\n            }\n            return switch edge {\n            case .maxYEdge:\n                firstOpaqueRow(in: rowRange)\n            case .minYEdge:\n                firstOpaqueRow(in: rowRange.reversed()).map { (image.height - 1) - $0 }\n            case .minXEdge:\n                firstOpaqueColumn(in: columnRange)\n            case .maxXEdge:\n                firstOpaqueColumn(in: columnRange.reversed()).map { (image.width - 1) - $0 }\n            }\n        }\n\n        private func isPixelOpaque(row: Int, column: Int) -> Bool {\n            guard let bitmapData = cgContext.data else {\n                return false\n            }\n            let rawAlpha = bitmapData.load(fromByteOffset: (row * cgContext.bytesPerRow) + column, as: UInt8.self)\n            return rawAlpha > maxAlpha\n        }\n\n        private func firstOpaqueRow<S: Sequence>(in rowRange: S) -> Int? where S.Element == Int {\n            guard let bitmapData = cgContext.data else {\n                return nil\n            }\n            return rowRange.first { row in\n                // Use memcmp to efficiently check the entire row for zeroed out alpha.\n                let rowByteBlock = bitmapData + (row * cgContext.bytesPerRow)\n                if memcmp(rowByteBlock, zeroByteBlock, image.width) == 0 {\n                    return true\n                }\n                // We found a non-zero row. Check each pixel until we find one that is opaque.\n                return columnRange.contains { column in\n                    isPixelOpaque(row: row, column: column)\n                }\n            }\n        }\n\n        private func firstOpaqueColumn<S: Sequence>(in columnRange: S) -> Int? where S.Element == Int {\n            columnRange.first { column in\n                rowRange.contains { row in\n                    isPixelOpaque(row: row, column: column)\n                }\n            }\n        }\n    }\n\n    /// Returns an image that has been trimmed of transparency around the given edges.\n    ///\n    /// - Parameters:\n    ///   - edges: The edges to trim from around the image.\n    ///   - maxAlpha: The maximum alpha value to consider transparent. Pixels with alpha\n    ///     values above this value will be considered opaque, and will therefore remain\n    ///     in the image.\n    func trimmingTransparentPixels(\n        around edges: Set<CGRectEdge> = [.minXEdge, .maxXEdge, .minYEdge, .maxYEdge],\n        maxAlpha: CGFloat = 0\n    ) -> CGImage? {\n        let maxAlpha = UInt8(maxAlpha.clamped(to: 0...1) * 255)\n        let context = TransparencyContext(image: self, maxAlpha: maxAlpha)\n        return context?.trim(edges: edges)\n    }\n\n    /// Returns a Boolean value that indicates whether the image is transparent.\n    ///\n    /// - Parameter maxAlpha: The maximum alpha value to consider transparent.\n    ///   Pixels with alpha values above this value will be considered opaque.\n    func isTransparent(maxAlpha: CGFloat = 0) -> Bool {\n        // FIXME: This needs a dedicated implementation instead of relying on `trimmingTransparentPixels`\n        trimmingTransparentPixels(maxAlpha: maxAlpha) == nil\n    }\n}\n\n// MARK: - Collection where Element == MenuBarItem\n\nextension Collection where Element == MenuBarItem {\n    /// Returns the first index where the menu bar item matching the specified\n    /// info appears in the collection.\n    func firstIndex(matching info: MenuBarItemInfo) -> Index? {\n        firstIndex { $0.info == info }\n    }\n}\n\n// MARK: - Comparable\n\nextension Comparable {\n    /// Returns a copy of this value that has been clamped within the bounds\n    /// of the given limiting range.\n    ///\n    /// - Parameter limits: A closed range within which to clamp this value.\n    func clamped(to limits: ClosedRange<Self>) -> Self {\n        min(max(self, limits.lowerBound), limits.upperBound)\n    }\n}\n\n// MARK: - EdgeInsets\n\nextension EdgeInsets {\n    /// Creates edge insets with the given floating point value.\n    init(all: CGFloat) {\n        self.init(top: all, leading: all, bottom: all, trailing: all)\n    }\n}\n\n// MARK: - NSApplication\n\nextension NSApplication {\n    /// Returns the window with the given identifier.\n    func window(withIdentifier identifier: String) -> NSWindow? {\n        windows.first { $0.identifier?.rawValue == identifier }\n    }\n}\n\n// MARK: - NSBezierPath\n\nextension NSBezierPath {\n    /// Draws a shadow in the shape of the path.\n    ///\n    /// - Parameters:\n    ///   - color: The color of the drawn shadow.\n    ///   - radius: The radius of the drawn shadow.\n    func drawShadow(color: NSColor, radius: CGFloat) {\n        guard let context = NSGraphicsContext.current else {\n            return\n        }\n\n        let bounds = bounds.insetBy(dx: -radius, dy: -radius)\n\n        let shadow = NSShadow()\n        shadow.shadowBlurRadius = radius\n        shadow.shadowColor = color\n\n        // swiftlint:disable:next force_cast\n        let path = copy() as! NSBezierPath\n\n        context.saveGraphicsState()\n\n        shadow.set()\n        NSColor.black.set()\n        bounds.clip()\n        path.fill()\n\n        context.restoreGraphicsState()\n    }\n\n    /// Returns a new path filled with regions in either this path or the\n    /// given path.\n    ///\n    /// - Parameters:\n    ///   - other: A path to union with this path.\n    ///   - windingRule: The winding rule used to join the paths.\n    func union(_ other: NSBezierPath, using windingRule: WindingRule = .evenOdd) -> NSBezierPath {\n        let fillRule: CGPathFillRule = switch windingRule {\n        case .nonZero: .winding\n        case .evenOdd: .evenOdd\n        @unknown default: fatalError(\"Unknown winding rule \\(windingRule)\")\n        }\n        return NSBezierPath(cgPath: cgPath.union(other.cgPath, using: fillRule))\n    }\n}\n\n// MARK: - NSImage\n\nextension NSImage {\n    /// Returns a new image that has been resized to the given size.\n    ///\n    /// - Note: This method retains the ``isTemplate`` property.\n    ///\n    /// - Parameter size: The size to resize the current image to.\n    func resized(to size: CGSize) -> NSImage {\n        let resizedImage = NSImage(size: size, flipped: false) { bounds in\n            self.draw(in: bounds)\n            return true\n        }\n        resizedImage.isTemplate = isTemplate\n        return resizedImage\n    }\n}\n\n// MARK: - NSScreen\n\nextension NSScreen {\n    /// The screen containing the mouse pointer.\n    static var screenWithMouse: NSScreen? {\n        screens.first { $0.frame.contains(NSEvent.mouseLocation) }\n    }\n\n    /// The display identifier of the screen.\n    var displayID: CGDirectDisplayID {\n        // Value and type are guaranteed here, so force casting is okay.\n        // swiftlint:disable:next force_cast\n        deviceDescription[NSDeviceDescriptionKey(\"NSScreenNumber\")] as! CGDirectDisplayID\n    }\n\n    /// A Boolean value that indicates whether the screen has a notch.\n    var hasNotch: Bool {\n        safeAreaInsets.top != 0\n    }\n\n    /// The frame of the screen's notch, if it has one.\n    var frameOfNotch: CGRect? {\n        guard\n            let auxiliaryTopLeftArea,\n            let auxiliaryTopRightArea\n        else {\n            return nil\n        }\n        return CGRect(\n            x: auxiliaryTopLeftArea.maxX,\n            y: frame.maxY - safeAreaInsets.top,\n            width: auxiliaryTopRightArea.minX - auxiliaryTopLeftArea.maxX,\n            height: safeAreaInsets.top\n        )\n    }\n\n    /// Returns the height of the menu bar on this screen.\n    func getMenuBarHeight() -> CGFloat? {\n        let menuBarWindow = WindowInfo.getMenuBarWindow(for: displayID)\n        return menuBarWindow?.frame.height\n    }\n}\n\n// MARK: - NSStatusItem\n\nextension NSStatusItem {\n    /// Shows the given menu under the status item.\n    func showMenu(_ menu: NSMenu) {\n        let originalMenu = self.menu\n        defer {\n            self.menu = originalMenu\n        }\n        self.menu = menu\n        button?.performClick(nil)\n    }\n}\n\n// MARK: - Publisher\n\nextension Publisher {\n    /// Transforms all elements from the upstream publisher into `Void` values.\n    func mapToVoid() -> some Publisher<Void, Failure> {\n        map { _ in () }\n    }\n}\n\n// MARK: - Sequence where Element == MenuBarItem\n\nextension Sequence where Element == MenuBarItem {\n    /// Returns the menu bar items, sorted by their order in the menu bar.\n    func sortedByOrderInMenuBar() -> [MenuBarItem] {\n        sorted { lhs, rhs in\n            lhs.frame.maxX < rhs.frame.maxX\n        }\n    }\n}\n"
  },
  {
    "path": "Ice/Utilities/IconResource.swift",
    "content": "//\n//  IconResource.swift\n//  Ice\n//\n\nimport SwiftUI\n\n/// A type that produces a view representing an icon.\nenum IconResource: Hashable {\n    /// A resource derived from a system symbol.\n    case systemSymbol(_ name: String)\n\n    /// A resource derived from an asset catalog.\n    case assetCatalog(_ resource: ImageResource)\n\n    /// The view produced by the resource.\n    @ViewBuilder\n    var view: some View {\n        image\n            .resizable()\n            .aspectRatio(contentMode: .fit)\n    }\n\n    /// The image produced by the resource.\n    private var image: Image {\n        switch self {\n        case .systemSymbol(let name):\n            Image(systemName: name)\n        case .assetCatalog(let resource):\n            Image(resource)\n        }\n    }\n}\n"
  },
  {
    "path": "Ice/Utilities/Injection.swift",
    "content": "//\n//  Injection.swift\n//  Ice\n//\n\n/// Updates the given value in place using a closure.\n///\n/// Use this function to repeatedly update a value while ensuring it is only mutated once.\nfunc update<Value>(_ value: inout Value, body: (inout Value) throws -> Void) rethrows {\n    try body(&value)\n}\n\n/// Updates the given value in place using a closure.\n///\n/// Use this function to repeatedly update a value while ensuring it is only mutated once.\nfunc update<Value>(_ value: inout Value, body: (inout Value) async throws -> Void) async rethrows {\n    try await body(&value)\n}\n\n/// Updates a copy of the given value using a closure and returns the updated value.\n@discardableResult\nfunc with<Value>(_ value: Value, update: (inout Value) throws -> Void) rethrows -> Value {\n    var copy = value\n    try update(&copy)\n    return copy\n}\n\n/// Updates a copy of the given value using a closure and returns the updated value.\n@discardableResult\nfunc with<Value>(_ value: Value, update: (inout Value) async throws -> Void) async rethrows -> Value {\n    var copy = value\n    try await update(&copy)\n    return copy\n}\n"
  },
  {
    "path": "Ice/Utilities/LocalizedErrorWrapper.swift",
    "content": "//\n//  LocalizedErrorWrapper.swift\n//  Ice\n//\n\nimport Foundation\n\n/// A type that wraps the information of any error inside a `LocalizedError`.\n///\n/// If the error used to initialize the box is also a `LocalizedError`, its\n/// information is passed through to the box. Otherwise, a description of the\n/// error is passed to the wrapper.\nstruct LocalizedErrorWrapper: LocalizedError {\n    let errorDescription: String?\n    let failureReason: String?\n    let helpAnchor: String?\n    let recoverySuggestion: String?\n\n    /// Creates a wrapper with the given error.\n    init(_ error: any Error) {\n        if let error = error as? any LocalizedError {\n            self.errorDescription = error.errorDescription\n            self.failureReason = error.failureReason\n            self.helpAnchor = error.helpAnchor\n            self.recoverySuggestion = error.recoverySuggestion\n        } else {\n            self.errorDescription = error.localizedDescription\n            self.failureReason = nil\n            self.helpAnchor = nil\n            self.recoverySuggestion = nil\n        }\n    }\n}\n"
  },
  {
    "path": "Ice/Utilities/Logging.swift",
    "content": "//\n//  Logging.swift\n//  Ice\n//\n\nimport OSLog\n\n/// A type that encapsulates logging behavior for Ice.\nstruct Logger {\n    /// The unified logger at the base of this logger.\n    private let base: os.Logger\n\n    /// Creates a logger for Ice using the specified category.\n    init(category: String) {\n        self.base = os.Logger(subsystem: Constants.bundleIdentifier, category: category)\n    }\n\n    /// Logs the given informative message to the logger.\n    func info(_ message: String) {\n        base.info(\"\\(message, privacy: .public)\")\n    }\n\n    /// Logs the given debug message to the logger.\n    func debug(_ message: String) {\n        base.debug(\"\\(message, privacy: .public)\")\n    }\n\n    /// Logs the given error message to the logger.\n    func error(_ message: String) {\n        base.error(\"\\(message, privacy: .public)\")\n    }\n\n    /// Logs the given warning message to the logger.\n    func warning(_ message: String) {\n        base.warning(\"\\(message, privacy: .public)\")\n    }\n}\n"
  },
  {
    "path": "Ice/Utilities/MigrationManager.swift",
    "content": "//\n//  MigrationManager.swift\n//  Ice\n//\n\nimport Cocoa\n\n@MainActor\nstruct MigrationManager {\n    let appState: AppState\n    let encoder = JSONEncoder()\n    let decoder = JSONDecoder()\n}\n\n// MARK: - Migrate All\n\nextension MigrationManager {\n    /// Performs all migrations.\n    static func migrateAll(appState: AppState) {\n        let manager = MigrationManager(appState: appState)\n\n        do {\n            try performAll(blocks: [\n                manager.migrate0_8_0,\n                manager.migrate0_10_0,\n            ])\n        } catch {\n            logError(error)\n        }\n\n        let results = [\n            manager.migrate0_10_1(),\n            manager.migrate0_11_10(),\n        ]\n\n        for result in results {\n            switch result {\n            case .success:\n                break\n            case .successButShowAlert(let alert):\n                alert.runModal()\n            case .failureAndLogError(let error):\n                logError(error)\n            }\n        }\n    }\n\n    private static func logError(_ error: any Error) {\n        Logger.migration.error(\"Migration failed with error: \\(error)\")\n    }\n}\n\n// MARK: - Migrate 0.8.0\n\nextension MigrationManager {\n    /// Performs all migrations for the `0.8.0` release, catching any thrown\n    /// errors and rethrowing them as a combined error.\n    private func migrate0_8_0() throws {\n        guard !Defaults.bool(forKey: .hasMigrated0_8_0) else {\n            return\n        }\n        try MigrationManager.performAll(blocks: [\n            migrateHotkeys0_8_0,\n            migrateControlItems0_8_0,\n            migrateSections0_8_0,\n        ])\n        Defaults.set(true, forKey: .hasMigrated0_8_0)\n        Logger.migration.info(\"Successfully migrated to 0.8.0 settings\")\n    }\n\n    // MARK: Migrate Hotkeys\n\n    /// Migrates the user's saved hotkeys from the old method of storing\n    /// them in their corresponding menu bar sections to the new method\n    /// of storing them as stand-alone data in the `0.8.0` release.\n    private func migrateHotkeys0_8_0() throws {\n        let sectionsArray: [[String: Any]]\n        do {\n            guard let array = try getMenuBarSectionArray() else {\n                return\n            }\n            sectionsArray = array\n        } catch {\n            throw MigrationError.hotkeyMigrationError(error)\n        }\n\n        // get the hotkey data from the hidden and always-hidden sections,\n        // if available, and create equivalent key combinations to assign\n        // to the corresponding hotkeys\n        for name: MenuBarSection.Name in [.hidden, .alwaysHidden] {\n            guard\n                let sectionDict = sectionsArray.first(where: { $0[\"name\"] as? String == name.deprecatedRawValue }),\n                let hotkeyDict = sectionDict[\"hotkey\"] as? [String: Int],\n                let key = hotkeyDict[\"key\"],\n                let modifiers = hotkeyDict[\"modifiers\"]\n            else {\n                continue\n            }\n            let keyCombination = KeyCombination(\n                key: KeyCode(rawValue: key),\n                modifiers: Modifiers(rawValue: modifiers)\n            )\n            let hotkeySettingsManager = appState.settingsManager.hotkeySettingsManager\n            if case .hidden = name {\n                if let hotkey = hotkeySettingsManager.hotkey(withAction: .toggleHiddenSection) {\n                    hotkey.keyCombination = keyCombination\n                }\n            } else if case .alwaysHidden = name {\n                if let hotkey = hotkeySettingsManager.hotkey(withAction: .toggleAlwaysHiddenSection) {\n                    hotkey.keyCombination = keyCombination\n                }\n            }\n        }\n    }\n\n    // MARK: Migrate Control Items\n\n    /// Migrates the control items from their old serialized representations\n    /// to their new representations in the `0.8.0` release.\n    private func migrateControlItems0_8_0() throws {\n        let sectionsArray: [[String: Any]]\n        do {\n            guard let array = try getMenuBarSectionArray() else {\n                return\n            }\n            sectionsArray = array\n        } catch {\n            throw MigrationError.controlItemMigrationError(error)\n        }\n\n        var newSectionsArray = [[String: Any]]()\n\n        for name in MenuBarSection.Name.allCases {\n            guard\n                var sectionDict = sectionsArray.first(where: { $0[\"name\"] as? String == name.deprecatedRawValue }),\n                var controlItemDict = sectionDict[\"controlItem\"] as? [String: Any],\n                // remove the \"autosaveName\" key from the dictionary\n                let autosaveName = controlItemDict.removeValue(forKey: \"autosaveName\") as? String\n            else {\n                continue\n            }\n\n            let identifier = switch name {\n            case .visible:\n                ControlItem.Identifier.iceIcon.deprecatedRawValue\n            case .hidden:\n                ControlItem.Identifier.hidden.deprecatedRawValue\n            case .alwaysHidden:\n                ControlItem.Identifier.alwaysHidden.deprecatedRawValue\n            }\n\n            // add the \"identifier\" key to the dictionary\n            controlItemDict[\"identifier\"] = identifier\n\n            // migrate the old autosave name to the new autosave name in UserDefaults\n            StatusItemDefaults.migrate(key: .preferredPosition, from: autosaveName, to: identifier)\n            StatusItemDefaults.migrate(key: .visible, from: autosaveName, to: identifier)\n\n            // replace the old \"controlItem\" dictionary with the new one\n            sectionDict[\"controlItem\"] = controlItemDict\n            // add the section to the new array\n            newSectionsArray.append(sectionDict)\n        }\n\n        do {\n            let data = try JSONSerialization.data(withJSONObject: newSectionsArray)\n            Defaults.set(data, forKey: .sections)\n        } catch {\n            throw MigrationError.controlItemMigrationError(error)\n        }\n    }\n\n    /// Migrates away from storing the menu bar sections in UserDefaults\n    /// for the `0.8.0` release.\n    private func migrateSections0_8_0() {\n        Defaults.set(nil, forKey: .sections)\n    }\n}\n\n// MARK: - Migrate 0.10.0\n\nextension MigrationManager {\n    /// Performs all migrations for the `0.10.0` release, catching any thrown\n    /// errors and rethrowing them as a combined error.\n    private func migrate0_10_0() throws {\n        guard !Defaults.bool(forKey: .hasMigrated0_10_0) else {\n            return\n        }\n        try MigrationManager.performAll(blocks: [\n            migrateControlItems0_10_0,\n        ])\n        Defaults.set(true, forKey: .hasMigrated0_10_0)\n        Logger.migration.info(\"Successfully migrated to 0.10.0 settings\")\n    }\n\n    private func migrateControlItems0_10_0() throws {\n        for identifier in ControlItem.Identifier.allCases {\n            StatusItemDefaults.migrate(\n                key: .preferredPosition,\n                from: identifier.deprecatedRawValue,\n                to: identifier.rawValue\n            )\n        }\n    }\n}\n\n// MARK: - Migrate 0.10.1\n\nextension MigrationManager {\n    /// Performs all migrations for the `0.10.1` release.\n    private func migrate0_10_1() -> MigrationResult {\n        guard !Defaults.bool(forKey: .hasMigrated0_10_1) else {\n            return .success\n        }\n        let result = migrateControlItems0_10_1()\n        switch result {\n        case .success, .successButShowAlert:\n            Defaults.set(true, forKey: .hasMigrated0_10_1)\n            Logger.migration.info(\"Successfully migrated to 0.10.1 settings\")\n        case .failureAndLogError:\n            break\n        }\n        return result\n    }\n\n    private func migrateControlItems0_10_1() -> MigrationResult {\n        var needsResetPreferredPositions = false\n\n        for identifier in ControlItem.Identifier.allCases {\n            if\n                StatusItemDefaults[.visible, identifier.rawValue] == false,\n                StatusItemDefaults[.preferredPosition, identifier.rawValue] == nil\n            {\n                needsResetPreferredPositions = true\n            }\n            StatusItemDefaults[.visible, identifier.rawValue] = nil\n        }\n\n        if needsResetPreferredPositions {\n            for identifier in ControlItem.Identifier.allCases {\n                StatusItemDefaults[.preferredPosition, identifier.rawValue] = nil\n            }\n\n            let alert = NSAlert()\n            alert.messageText = \"Due to a bug in the 0.10.0 release, the data for Ice's menu bar items was corrupted and their positions had to be reset.\"\n            alert.informativeText = \"Our sincerest apologies for the inconvenience.\"\n\n            return .successButShowAlert(alert)\n        }\n\n        return .success\n    }\n}\n\n// MARK: - Migrate 0.11.10\n\nextension MigrationManager {\n    private func migrate0_11_10() -> MigrationResult {\n        guard !Defaults.bool(forKey: .hasMigrated0_11_10) else {\n            return .success\n        }\n        let result = migrateAppearanceConfiguration0_11_10()\n        switch result {\n        case .success, .successButShowAlert:\n            Defaults.set(true, forKey: .hasMigrated0_11_10)\n            Logger.migration.info(\"Successfully migrated to 0.11.10 settings\")\n        case .failureAndLogError:\n            break\n        }\n        return result\n    }\n\n    private func migrateAppearanceConfiguration0_11_10() -> MigrationResult {\n        guard let oldData = Defaults.data(forKey: .menuBarAppearanceConfiguration) else {\n            return .failureAndLogError(.appearanceConfigurationMigrationError(.missingConfiguration))\n        }\n        do {\n            let oldConfiguration = try decoder.decode(MenuBarAppearanceConfigurationV1.self, from: oldData)\n            let newConfiguration = with(MenuBarAppearanceConfigurationV2.defaultConfiguration) { configuration in\n                let partialConfiguration = MenuBarAppearancePartialConfiguration(\n                    hasShadow: oldConfiguration.hasShadow,\n                    hasBorder: oldConfiguration.hasBorder,\n                    borderColor: oldConfiguration.borderColor,\n                    borderWidth: oldConfiguration.borderWidth,\n                    tintKind: oldConfiguration.tintKind,\n                    tintColor: oldConfiguration.tintColor,\n                    tintGradient: oldConfiguration.tintGradient\n                )\n                configuration.lightModeConfiguration = partialConfiguration\n                configuration.darkModeConfiguration = partialConfiguration\n                configuration.staticConfiguration = partialConfiguration\n                configuration.shapeKind = oldConfiguration.shapeKind\n                configuration.fullShapeInfo = oldConfiguration.fullShapeInfo\n                configuration.splitShapeInfo = oldConfiguration.splitShapeInfo\n                configuration.isInset = oldConfiguration.isInset\n            }\n            let newData = try encoder.encode(newConfiguration)\n            Defaults.set(newData, forKey: .menuBarAppearanceConfigurationV2)\n        } catch {\n            return .failureAndLogError(.appearanceConfigurationMigrationError(.otherError(error)))\n        }\n        return .success\n    }\n}\n\n// MARK: - Helpers\n\nextension MigrationManager {\n    /// Performs every block in the given array, catching any thrown\n    /// errors and rethrowing them as a combined error.\n    private static func performAll(blocks: [() throws -> Void]) throws {\n        let results = blocks.map { block in\n            Result(catching: block)\n        }\n        let errors = results.compactMap { result in\n            if case .failure(let error) = result {\n                return error\n            }\n            return nil\n        }\n        if !errors.isEmpty {\n            throw MigrationError.combinedError(errors)\n        }\n    }\n\n    /// Returns an array of dictionaries that represent the sections in\n    /// the menu bar, as stored in UserDefaults.\n    private func getMenuBarSectionArray() throws -> [[String: Any]]? {\n        guard let data = Defaults.data(forKey: .sections) else {\n            return nil\n        }\n        let object = try JSONSerialization.jsonObject(with: data)\n        guard let array = object as? [[String: Any]] else {\n            throw MigrationError.invalidMenuBarSectionsJSONObject(object)\n        }\n        return array\n    }\n}\n\n// MARK: - MigrationResult\n\nextension MigrationManager {\n    enum MigrationResult {\n        case success\n        case successButShowAlert(NSAlert)\n        case failureAndLogError(MigrationError)\n    }\n}\n\n// MARK: - Errors\n\nextension MigrationManager {\n    enum MigrationError: Error, CustomStringConvertible {\n        case invalidMenuBarSectionsJSONObject(Any)\n        case hotkeyMigrationError(any Error)\n        case controlItemMigrationError(any Error)\n        case appearanceConfigurationMigrationError(AppearanceConfigurationMigrationError)\n        case combinedError([any Error])\n\n        var description: String {\n            switch self {\n            case .invalidMenuBarSectionsJSONObject(let object):\n                \"Invalid menu bar sections JSON object: \\(object)\"\n            case .hotkeyMigrationError(let error):\n                \"Error migrating hotkeys: \\(error)\"\n            case .controlItemMigrationError(let error):\n                \"Error migrating control items: \\(error)\"\n            case .appearanceConfigurationMigrationError(let error):\n                \"Error migrating menu bar appearance configuration: \\(error)\"\n            case .combinedError(let errors):\n                \"The following errors occurred: \\(errors)\"\n            }\n        }\n    }\n\n    enum AppearanceConfigurationMigrationError: Error, CustomStringConvertible {\n        case otherError(any Error)\n        case missingConfiguration\n\n        var description: String {\n            switch self {\n            case .otherError(let error):\n                error.localizedDescription\n            case .missingConfiguration:\n                \"Missing menu bar appearance configuration\"\n            }\n        }\n    }\n}\n\n// MARK: - ControlItem.Identifier Extension\n\nprivate extension ControlItem.Identifier {\n    var deprecatedRawValue: String {\n        switch self {\n        case .iceIcon: \"IceIcon\"\n        case .hidden: \"HItem\"\n        case .alwaysHidden: \"AHItem\"\n        }\n    }\n}\n\n// MARK: - MenuBarSection.Name Extension\n\nprivate extension MenuBarSection.Name {\n    var deprecatedRawValue: String {\n        switch self {\n        case .visible: \"Visible\"\n        case .hidden: \"Hidden\"\n        case .alwaysHidden: \"Always Hidden\"\n        }\n    }\n}\n\n// MARK: - Logger\nprivate extension Logger {\n    static let migration = Logger(category: \"Migration\")\n}\n"
  },
  {
    "path": "Ice/Utilities/MouseCursor.swift",
    "content": "//\n//  MouseCursor.swift\n//  Ice\n//\n\nimport CoreGraphics\n\n/// A namespace for mouse cursor operations.\nenum MouseCursor {\n    /// Returns the location of the mouse cursor in the coordinate space used by\n    /// the `AppKit` framework, with the origin at the bottom left of the screen.\n    static var locationAppKit: CGPoint? {\n        CGEvent(source: nil)?.unflippedLocation\n    }\n\n    /// Returns the location of the mouse cursor in the coordinate space used by\n    /// the `CoreGraphics` framework, with the origin at the top left of the screen.\n    static var locationCoreGraphics: CGPoint? {\n        CGEvent(source: nil)?.location\n    }\n\n    /// Hides the mouse cursor and increments the hide cursor count.\n    static func hide() {\n        let result = CGDisplayHideCursor(CGMainDisplayID())\n        if result != .success {\n            Logger.mouseCursor.error(\"CGDisplayHideCursor failed with error \\(result.logString)\")\n        }\n    }\n\n    /// Decrements the hide cursor count and shows the mouse cursor if the count is `0`.\n    static func show() {\n        let result = CGDisplayShowCursor(CGMainDisplayID())\n        if result != .success {\n            Logger.mouseCursor.error(\"CGDisplayShowCursor failed with error \\(result.logString)\")\n        }\n    }\n\n    /// Moves the mouse cursor to the given point without generating events.\n    ///\n    /// - Parameter point: The point to move the cursor to in global display coordinates.\n    static func warp(to point: CGPoint) {\n        let result = CGWarpMouseCursorPosition(point)\n        if result != .success {\n            Logger.mouseCursor.error(\"CGWarpMouseCursorPosition failed with error \\(result.logString)\")\n        }\n    }\n}\n\n// MARK: - Logger\nprivate extension Logger {\n    static let mouseCursor = Logger(category: \"MouseCursor\")\n}\n"
  },
  {
    "path": "Ice/Utilities/Notifications.swift",
    "content": "//\n//  Notifications.swift\n//  Ice\n//\n\nimport Foundation\n\nextension DistributedNotificationCenter {\n    /// A notification posted whenever the system-wide interface theme changes.\n    static let interfaceThemeChangedNotification = Notification.Name(\"AppleInterfaceThemeChangedNotification\")\n}\n"
  },
  {
    "path": "Ice/Utilities/ObjectStorage.swift",
    "content": "//\n//  ObjectStorage.swift\n//  Ice\n//\n\nimport ObjectiveC\n\n// MARK: - Object Storage\n\n/// A type that uses the Objective-C runtime to store values of a given\n/// type with an object.\nfinal class ObjectStorage<Value> {\n    /// The association policy to use for storage.\n    ///\n    /// - Note: Regardless of whether a value is stored with a strong or\n    ///   weak reference, the association is made strongly. Weak references\n    ///   are stored inside a `WeakReference` object.\n    private let policy = objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC\n\n    /// The key used for value lookup.\n    ///\n    /// The key is unique to this instance.\n    private var key: UnsafeRawPointer {\n        UnsafeRawPointer(Unmanaged.passUnretained(self).toOpaque())\n    }\n\n    /// Sets the value for the given object.\n    ///\n    /// If the value is an object, it is stored with a strong reference.\n    /// Use ``weakSet(_:for:)`` to store an object with a weak reference.\n    ///\n    /// - Parameters:\n    ///   - value: A value to set.\n    ///   - object: An object to set the value for.\n    func set(_ value: Value?, for object: AnyObject) {\n        objc_setAssociatedObject(object, key, value, policy)\n    }\n\n    /// Retrieves the value stored for the given object.\n    ///\n    /// - Parameter object: An object to retrieve the value for.\n    func value(for object: AnyObject) -> Value? {\n        let value = objc_getAssociatedObject(object, key)\n        return if let container = value as? WeakReference {\n            container.object as? Value\n        } else {\n            value as? Value\n        }\n    }\n}\n\n// MARK: - Weak Storage\n\n/// An object containing a weak reference to another object.\nprivate final class WeakReference {\n    /// A weak reference to an object.\n    private(set) weak var object: AnyObject?\n\n    /// Creates a weak reference to an object.\n    init(_ object: AnyObject) {\n        self.object = object\n    }\n}\n\nextension ObjectStorage where Value: AnyObject {\n    /// Sets a weak reference to an object.\n    ///\n    /// - Parameters:\n    ///   - value: An object to set a weak reference to.\n    ///   - object: An object to set the weak reference for.\n    func weakSet(_ value: Value?, for object: AnyObject) {\n        objc_setAssociatedObject(object, key, value.map(WeakReference.init), policy)\n    }\n}\n"
  },
  {
    "path": "Ice/Utilities/Predicates.swift",
    "content": "//\n//  Predicates.swift\n//  Ice\n//\n\nimport Cocoa\n\n/// A namespace for predicates.\nenum Predicates<Input> {\n    /// A throwing predicate that takes an input and returns a Boolean value.\n    typealias ThrowingPredicate = (Input) throws -> Bool\n\n    /// A predicate that takes an input and returns a Boolean value.\n    typealias NonThrowingPredicate = (Input) -> Bool\n\n    /// Creates a throwing predicate that takes an input and returns a Boolean value.\n    static func predicate(_ body: @escaping (Input) throws -> Bool) -> ThrowingPredicate {\n        return body\n    }\n\n    /// Creates a predicate takes an input and returns a Boolean value.\n    static func predicate(_ body: @escaping (Input) -> Bool) -> NonThrowingPredicate {\n        return body\n    }\n\n    /// Creates a throwing predicate that doesn't take an input and returns a Boolean value.\n    static func predicate(_ body: @escaping () throws -> Bool) -> ThrowingPredicate {\n        predicate { _ in try body() }\n    }\n\n    /// Creates a predicate that doesn't take an input and returns a Boolean value.\n    static func predicate(_ body: @escaping () -> Bool) -> NonThrowingPredicate {\n        predicate { _ in body() }\n    }\n}\n\n// MARK: - Window Predicates\n\nextension Predicates where Input == WindowInfo {\n    /// Creates a predicate that returns whether a window is the wallpaper window\n    /// for the given display.\n    static func wallpaperWindow(for display: CGDirectDisplayID) -> NonThrowingPredicate {\n        predicate { window in\n            // wallpaper window belongs to the Dock process\n            window.owningApplication?.bundleIdentifier == \"com.apple.dock\" &&\n            window.title?.hasPrefix(\"Wallpaper\") == true &&\n            CGDisplayBounds(display).contains(window.frame)\n        }\n    }\n\n    /// Creates a predicate that returns whether a window is the menu bar window for\n    /// the given display.\n    static func menuBarWindow(for display: CGDirectDisplayID) -> NonThrowingPredicate {\n        predicate { window in\n            // menu bar window belongs to the WindowServer process\n            window.isWindowServerWindow &&\n            window.isOnScreen &&\n            window.layer == kCGMainMenuWindowLevel &&\n            window.title == \"Menubar\" &&\n            CGDisplayBounds(display).contains(window.frame)\n        }\n    }\n}\n\n// MARK: - Menu Bar Item Predicates\n\nextension Predicates where Input == MenuBarItem {\n    /// A group of predicates that separates menu bar items into sections.\n    typealias SectionPredicates = (\n        isInVisibleSection: NonThrowingPredicate,\n        isInHiddenSection: NonThrowingPredicate,\n        isInAlwaysHiddenSection: NonThrowingPredicate\n    )\n\n    /// Creates a predicate that returns whether a menu bar item is in the visible section\n    /// using the control item for the hidden section as a delimiter.\n    static func isInVisibleSection(hiddenControlItem: MenuBarItem) -> NonThrowingPredicate {\n        predicate { item in\n            item.frame.minX >= hiddenControlItem.frame.maxX\n        }\n    }\n\n    /// Creates a predicate that returns whether a menu bar item is in the hidden section\n    /// using the control items for the hidden and always hidden sections as delimiters.\n    static func isInHiddenSection(hiddenControlItem: MenuBarItem, alwaysHiddenControlItem: MenuBarItem?) -> NonThrowingPredicate {\n        if let alwaysHiddenControlItem {\n            predicate { item in\n                item.frame.maxX <= hiddenControlItem.frame.minX &&\n                item.frame.minX >= alwaysHiddenControlItem.frame.maxX\n            }\n        } else {\n            predicate { item in\n                item.frame.maxX <= hiddenControlItem.frame.minX\n            }\n        }\n    }\n\n    /// Creates a predicate that returns whether a menu bar item is in the always-hidden\n    /// section using the control item for the always hidden section as a delimiter.\n    static func isInAlwaysHiddenSection(alwaysHiddenControlItem: MenuBarItem?) -> NonThrowingPredicate {\n        if let alwaysHiddenControlItem {\n            predicate { item in\n                item.frame.maxX <= alwaysHiddenControlItem.frame.minX\n            }\n        } else {\n            predicate { false }\n        }\n    }\n\n    /// Creates a group of predicates that separates menu bar items into sections.\n    static func sectionPredicates(hiddenControlItem: MenuBarItem, alwaysHiddenControlItem: MenuBarItem?) -> SectionPredicates {\n        SectionPredicates(\n            isInVisibleSection: isInVisibleSection(hiddenControlItem: hiddenControlItem),\n            isInHiddenSection: isInHiddenSection(hiddenControlItem: hiddenControlItem, alwaysHiddenControlItem: alwaysHiddenControlItem),\n            isInAlwaysHiddenSection: isInAlwaysHiddenSection(alwaysHiddenControlItem: alwaysHiddenControlItem)\n        )\n    }\n}\n\n// MARK: - Control Item Predicates\n\nextension Predicates where Input == NSLayoutConstraint {\n    static func controlItemConstraint(button: NSStatusBarButton) -> NonThrowingPredicate {\n        predicate { constraint in\n            constraint.secondItem === button.superview\n        }\n    }\n}\n"
  },
  {
    "path": "Ice/Utilities/RehideStrategy.swift",
    "content": "//\n//  RehideStrategy.swift\n//  Ice\n//\n\nimport SwiftUI\n\n/// A type that determines how the auto-rehide feature works.\nenum RehideStrategy: Int, CaseIterable, Identifiable {\n    /// Menu bar items are rehidden using a smart algorithm.\n    case smart = 0\n    /// Menu bar items are rehidden after a given time interval.\n    case timed = 1\n    /// Menu bar items are rehidden when the focused app changes.\n    case focusedApp = 2\n\n    var id: Int { rawValue }\n\n    /// Localized string key representation.\n    var localized: LocalizedStringKey {\n        switch self {\n        case .smart: \"Smart\"\n        case .timed: \"Timed\"\n        case .focusedApp: \"Focused app\"\n        }\n    }\n}\n"
  },
  {
    "path": "Ice/Utilities/ScreenCapture.swift",
    "content": "//\n//  ScreenCapture.swift\n//  Ice\n//\n\nimport CoreGraphics\nimport ScreenCaptureKit\n\n/// A namespace for screen capture operations.\nenum ScreenCapture {\n    /// Returns a Boolean value that indicates whether the app has been granted screen capture permissions.\n    static func checkPermissions() -> Bool {\n        for item in MenuBarItem.getMenuBarItems(onScreenOnly: false, activeSpaceOnly: true) {\n            // Don't check items owned by Ice.\n            if item.owningApplication == .current {\n                continue\n            }\n            return item.title != nil\n        }\n        // CGPreflightScreenCaptureAccess() only returns an initial value for whether the app\n        // has permissions, but we can use it as a fallback.\n        return CGPreflightScreenCaptureAccess()\n    }\n\n    /// Returns a Boolean value that indicates whether the app has been granted screen capture permissions.\n    ///\n    /// The first time this function is called, the permissions state is computed, cached, and returned.\n    /// Subsequent calls either return the cached value, or recompute the permissions state before caching\n    /// and returning it.\n    static func cachedCheckPermissions(reset: Bool = false) -> Bool {\n        enum Context {\n            static var lastCheckResult: Bool?\n        }\n\n        if !reset {\n            if let lastCheckResult = Context.lastCheckResult {\n                return lastCheckResult\n            }\n        }\n\n        let realResult = checkPermissions()\n        Context.lastCheckResult = realResult\n        return realResult\n    }\n\n    /// Requests screen capture permissions.\n    static func requestPermissions() {\n        if #available(macOS 15.0, *) {\n            // CGRequestScreenCaptureAccess() is broken on macOS 15. SCShareableContent requires\n            // screen capture permissions, and triggers a request if the user doesn't have them.\n            SCShareableContent.getWithCompletionHandler { _, _ in }\n        } else {\n            CGRequestScreenCaptureAccess()\n        }\n    }\n\n    /// Captures a composite image of an array of windows.\n    ///\n    /// - Parameters:\n    ///   - windowIDs: The identifiers of the windows to capture.\n    ///   - screenBounds: The bounds to capture. Pass `nil` to capture the minimum rectangle that encloses the windows.\n    ///   - option: Options that specify the image to be captured.\n    static func captureWindows(_ windowIDs: [CGWindowID], screenBounds: CGRect? = nil, option: CGWindowImageOption = []) -> CGImage? {\n        let pointer = UnsafeMutablePointer<UnsafeRawPointer?>.allocate(capacity: windowIDs.count)\n        for (index, windowID) in windowIDs.enumerated() {\n            pointer[index] = UnsafeRawPointer(bitPattern: UInt(windowID))\n        }\n        guard let windowArray = CFArrayCreate(kCFAllocatorDefault, pointer, windowIDs.count, nil) else {\n            return nil\n        }\n        return .windowListImage(from: screenBounds ?? .null, windowArray: windowArray, imageOption: option)\n    }\n\n    /// Captures an image of a window.\n    ///\n    /// - Parameters:\n    ///   - windowID: The identifier of the window to capture.\n    ///   - screenBounds: The bounds to capture. Pass `nil` to capture the minimum rectangle that encloses the window.\n    ///   - option: Options that specify the image to be captured.\n    static func captureWindow(_ windowID: CGWindowID, screenBounds: CGRect? = nil, option: CGWindowImageOption = []) -> CGImage? {\n        captureWindows([windowID], screenBounds: screenBounds, option: option)\n    }\n}\n\n/// A protocol used to suppress deprecation warnings for the `CGWindowList` screen capture APIs.\n///\n/// ScreenCaptureKit doesn't support capturing composite images of offscreen menu bar items, but\n/// this should be replaced once it does.\nprivate protocol WindowListImage {\n    init?(windowListFromArrayScreenBounds: CGRect, windowArray: CFArray, imageOption: CGWindowImageOption)\n}\n\nprivate extension WindowListImage {\n    static func windowListImage(from screenBounds: CGRect, windowArray: CFArray, imageOption: CGWindowImageOption) -> Self? {\n        Self(windowListFromArrayScreenBounds: screenBounds, windowArray: windowArray, imageOption: imageOption)\n    }\n}\n\nextension CGImage: WindowListImage { }\n"
  },
  {
    "path": "Ice/Utilities/StatusItemDefaults.swift",
    "content": "//\n//  StatusItemDefaults.swift\n//  Ice\n//\n\nimport Cocoa\n\n// MARK: - StatusItemDefaults\n\n/// Proxy getters and setters for a status item's user defaults values.\nenum StatusItemDefaults {\n    /// Accesses the value associated with the specified key and autosave name.\n    static subscript<Value>(key: Key<Value>, autosaveName: String) -> Value? {\n        get {\n            let stringKey = key.stringKey(for: autosaveName)\n            return UserDefaults.standard.object(forKey: stringKey) as? Value\n        }\n        set {\n            let stringKey = key.stringKey(for: autosaveName)\n            return UserDefaults.standard.set(newValue, forKey: stringKey)\n        }\n    }\n\n    /// Migrates the given status item defaults key from an old autosave name\n    /// to a new autosave name.\n    static func migrate<Value>(key: Key<Value>, from oldAutosaveName: String, to newAutosaveName: String) {\n        guard newAutosaveName != oldAutosaveName else {\n            return\n        }\n        Self[key, newAutosaveName] = Self[key, oldAutosaveName]\n        Self[key, oldAutosaveName] = nil\n    }\n}\n\n// MARK: - StatusItemDefaults.Key\n\nextension StatusItemDefaults {\n    /// Keys used to look up user defaults values for status items.\n    struct Key<Value> {\n        /// The raw value of the key.\n        let rawValue: String\n\n        /// Returns the full string key for the given autosave name.\n        func stringKey(for autosaveName: String) -> String {\n            return \"NSStatusItem \\(rawValue) \\(autosaveName)\"\n        }\n    }\n}\n\nextension StatusItemDefaults.Key<CGFloat> {\n    /// String key: \"NSStatusItem Preferred Position autosaveName\"\n    static let preferredPosition = Self(rawValue: \"Preferred Position\")\n}\n\nextension StatusItemDefaults.Key<Bool> {\n    /// String key: \"NSStatusItem Visible autosaveName\"\n    static let visible = Self(rawValue: \"Visible\")\n}\n"
  },
  {
    "path": "Ice/Utilities/SystemAppearance.swift",
    "content": "//\n//  SystemAppearance.swift\n//  Ice\n//\n\nimport SwiftUI\n\n/// A value corresponding to a light or dark appearance.\nenum SystemAppearance {\n    /// A light appearance.\n    case light\n    /// A dark appearance.\n    case dark\n\n    /// The names of the light appearances used by the system.\n    private static let systemLightAppearanceNames: Set<NSAppearance.Name> = [\n        .aqua,\n        .vibrantLight,\n        .accessibilityHighContrastAqua,\n        .accessibilityHighContrastVibrantLight,\n    ]\n\n    /// The names of the dark appearances used by the system.\n    private static let systemDarkAppearanceNames: Set<NSAppearance.Name> = [\n        .vibrantDark,\n        .darkAqua,\n        .accessibilityHighContrastDarkAqua,\n        .accessibilityHighContrastVibrantDark,\n    ]\n\n    /// Returns the system appearance that exactly matches the given appearance,\n    /// or `nil` if the system appearance cannot be determined.\n    private static func exactMatch(for appearance: NSAppearance) -> SystemAppearance? {\n        let name = appearance.name\n        if systemDarkAppearanceNames.contains(name) {\n            return .dark\n        }\n        if systemLightAppearanceNames.contains(name) {\n            return .light\n        }\n        return nil\n    }\n\n    /// Returns the system appearance that best matches the given appearance,\n    /// or `nil` if the system appearance cannot be determined.\n    private static func bestMatch(for appearance: NSAppearance) -> SystemAppearance? {\n        let lowercased = appearance.name.rawValue.lowercased()\n        if lowercased.contains(\"dark\") {\n            return .dark\n        }\n        if lowercased.contains(\"light\") || lowercased.contains(\"aqua\") {\n            return .light\n        }\n        return nil\n    }\n\n    /// Returns the system appearance of the given appearance.\n    ///\n    /// If a system appearance cannot be found that matches the given appearance,\n    /// the ``light`` system appearance is returned.\n    private static func systemAppearance(for appearance: NSAppearance) -> SystemAppearance {\n        if let match = exactMatch(for: appearance) {\n            return match\n        }\n        if let match = bestMatch(for: appearance) {\n            return match\n        }\n        return .light\n    }\n\n    /// The current system appearance.\n    static var current: SystemAppearance {\n        systemAppearance(for: NSApp.effectiveAppearance)\n    }\n\n    /// The title key to display in the interface.\n    var titleKey: LocalizedStringKey {\n        switch self {\n        case .light: \"Light Appearance\"\n        case .dark: \"Dark Appearance\"\n        }\n    }\n}\n"
  },
  {
    "path": "Ice/Utilities/TaskTimeout.swift",
    "content": "//\n//  TaskTimeout.swift\n//  Ice\n//\n\nimport Foundation\n\nextension Task where Failure == any Error {\n    /// Runs the given throwing operation asynchronously as part of a new top-level task\n    /// on behalf of the current actor.\n    ///\n    /// - Parameters:\n    ///   - priority: The priority of the task.\n    ///   - timeout: The amount of time to wait before throwing a ``TaskTimeoutError``.\n    ///   - tolerance: The tolerance of the clock.\n    ///   - clock: The clock to use in the timeout operation.\n    ///   - operation: The operation to perform.\n    @discardableResult\n    init<C: Clock>(\n        priority: TaskPriority? = nil,\n        timeout: C.Instant.Duration,\n        tolerance: C.Instant.Duration? = nil,\n        clock: C = ContinuousClock(),\n        operation: @escaping @Sendable () async throws -> Success\n    ) {\n        self.init(priority: priority) {\n            try await Task.run(operation: operation, withTimeout: timeout, tolerance: tolerance, clock: clock)\n        }\n    }\n\n    /// Runs the given throwing operation asynchronously as part of a new top-level task.\n    ///\n    /// - Parameters:\n    ///   - priority: The priority of the task.\n    ///   - timeout: The amount of time to wait before throwing a ``TaskTimeoutError``.\n    ///   - tolerance: The tolerance of the clock.\n    ///   - clock: The clock to use in the timeout operation.\n    ///   - operation: The operation to perform.\n    ///\n    /// - Returns: A reference to the task.\n    @discardableResult\n    static func detached<C: Clock>(\n        priority: TaskPriority? = nil,\n        timeout: C.Instant.Duration,\n        tolerance: C.Instant.Duration? = nil,\n        clock: C = ContinuousClock(),\n        operation: @escaping @Sendable () async throws -> Success\n    ) -> Task {\n        detached(priority: priority) {\n            try await run(operation: operation, withTimeout: timeout, tolerance: tolerance, clock: clock)\n        }\n    }\n\n    private static func run<C: Clock>(\n        operation: @escaping @Sendable () async throws -> Success,\n        withTimeout timeout: C.Instant.Duration,\n        tolerance: C.Instant.Duration?,\n        clock: C\n    ) async throws -> Success {\n        try await withThrowingTaskGroup(of: Success.self) { group in\n            group.addTask(operation: operation)\n            group.addTask {\n                try await _Concurrency.Task.sleep(for: timeout, tolerance: tolerance, clock: clock)\n                throw TaskTimeoutError()\n            }\n            guard let success = try await group.next() else {\n                throw _Concurrency.CancellationError()\n            }\n            group.cancelAll()\n            return success\n        }\n    }\n}\n\n// MARK: - TaskTimeoutError\n\n/// An error that indicates that a task timed out.\nstruct TaskTimeoutError: Error, CustomStringConvertible {\n    let description = \"Task timed out before completion\"\n}\n\n// MARK: TaskTimeoutError: LocalizedError\nextension TaskTimeoutError: LocalizedError {\n    var errorDescription: String? { description }\n}\n"
  },
  {
    "path": "Ice/Utilities/WindowInfo.swift",
    "content": "//\n//  WindowInfo.swift\n//  Ice\n//\n\nimport Cocoa\n\n/// Information for a window.\nstruct WindowInfo {\n    /// The window identifier associated with the window.\n    let windowID: CGWindowID\n\n    /// The frame of the window.\n    ///\n    /// The frame is specified in screen coordinates, where the origin\n    /// is at the upper left corner of the main display.\n    let frame: CGRect\n\n    /// The title of the window.\n    let title: String?\n\n    /// The layer number of the window.\n    let layer: Int\n\n    /// The alpha value of the window, ranging from `0.0` to `1.0`,\n    /// where `0.0` is fully transparent, and `1.0` is fully opaque.\n    let alpha: Double\n\n    /// The process identifier of the application that owns the window.\n    let ownerPID: pid_t\n\n    /// The name of the application that owns the window.\n    ///\n    /// This may have a value when ``owningApplication`` does not have a\n    /// localized name.\n    let ownerName: String?\n\n    /// The sharing mode used by the window.\n    let sharingState: CGWindowSharingType\n\n    /// The backing type of the window.\n    let backingStoreType: CGWindowBackingType\n\n    /// An estimate of the amount of memory in bytes used by the window.\n    let memoryUsage: Measurement<UnitInformationStorage>\n\n    /// A Boolean value that indicates whether the window is on screen.\n    let isOnScreen: Bool\n\n    /// A Boolean value that indicates whether the window's backing store\n    /// is located in video memory.\n    let isBackedByVideoMemory: Bool\n\n    /// The application that owns the window.\n    var owningApplication: NSRunningApplication? {\n        NSRunningApplication(processIdentifier: ownerPID)\n    }\n\n    /// A Boolean value that indicates whether the window represents a\n    /// menu bar item.\n    var isMenuBarItem: Bool {\n        layer == kCGStatusWindowLevel\n    }\n\n    /// A Boolean value that indicates whether the window belongs to the\n    /// window server.\n    var isWindowServerWindow: Bool {\n        ownerName == \"Window Server\"\n    }\n\n    /// A Boolean value that indicates whether the window is on the active space.\n    var isOnActiveSpace: Bool {\n        Bridging.isWindowOnActiveSpace(windowID)\n    }\n\n    /// Creates a window with the given dictionary.\n    private init?(dictionary: CFDictionary) {\n        guard\n            let info = dictionary as? [CFString: CFTypeRef],\n            let windowID = info[kCGWindowNumber] as? CGWindowID,\n            let boundsDict = info[kCGWindowBounds] as? NSDictionary,\n            let frame = CGRect(dictionaryRepresentation: boundsDict),\n            let layer = info[kCGWindowLayer] as? Int,\n            let alpha = info[kCGWindowAlpha] as? Double,\n            let ownerPID = info[kCGWindowOwnerPID] as? pid_t,\n            let rawSharingState = info[kCGWindowSharingState] as? UInt32,\n            let rawBackingStoreType = info[kCGWindowStoreType] as? UInt32,\n            let sharingState = CGWindowSharingType(rawValue: rawSharingState),\n            let backingStoreType = CGWindowBackingType(rawValue: rawBackingStoreType),\n            let memoryUsage = info[kCGWindowMemoryUsage] as? Double\n        else {\n            return nil\n        }\n        self.windowID = windowID\n        self.frame = frame\n        self.title = info[kCGWindowName] as? String\n        self.layer = layer\n        self.alpha = alpha\n        self.ownerPID = ownerPID\n        self.ownerName = info[kCGWindowOwnerName] as? String\n        self.sharingState = sharingState\n        self.backingStoreType = backingStoreType\n        self.memoryUsage = Measurement(value: memoryUsage, unit: .bytes)\n        self.isOnScreen = info[kCGWindowIsOnscreen] as? Bool ?? false\n        self.isBackedByVideoMemory = info[kCGWindowBackingLocationVideoMemory] as? Bool ?? false\n    }\n\n    /// Creates a window with the given window identifier.\n    init?(windowID: CGWindowID) {\n        var pointer = UnsafeRawPointer(bitPattern: Int(windowID))\n        guard\n            let array = CFArrayCreate(kCFAllocatorDefault, &pointer, 1, nil),\n            let list = CGWindowListCreateDescriptionFromArray(array) as? [CFDictionary],\n            let dictionary = list.first\n        else {\n            return nil\n        }\n        self.init(dictionary: dictionary)\n    }\n}\n\n// MARK: - WindowList Operations\n\n// MARK: Private\nextension WindowInfo {\n    /// Options to use to retrieve on screen windows.\n    private enum OnScreenWindowListOption {\n        case above(_ window: WindowInfo, includeWindow: Bool)\n        case below(_ window: WindowInfo, includeWindow: Bool)\n        case onScreenOnly\n    }\n\n    /// A context that contains the information needed to retrieve a window list.\n    private struct WindowListContext {\n        let windowListOption: CGWindowListOption\n        let referenceWindow: WindowInfo?\n\n        init(windowListOption: CGWindowListOption, referenceWindow: WindowInfo?) {\n            self.windowListOption = windowListOption\n            self.referenceWindow = referenceWindow\n        }\n\n        init(onScreenOption: OnScreenWindowListOption, excludeDesktopWindows: Bool) {\n            var windowListOption: CGWindowListOption = []\n            var referenceWindow: WindowInfo?\n            switch onScreenOption {\n            case .above(let window, let includeWindow):\n                windowListOption.insert(.optionOnScreenAboveWindow)\n                if includeWindow {\n                    windowListOption.insert(.optionIncludingWindow)\n                }\n                referenceWindow = window\n            case .below(let window, let includeWindow):\n                windowListOption.insert(.optionOnScreenBelowWindow)\n                if includeWindow {\n                    windowListOption.insert(.optionIncludingWindow)\n                }\n                referenceWindow = window\n            case .onScreenOnly:\n                windowListOption.insert(.optionOnScreenOnly)\n            }\n            if excludeDesktopWindows {\n                windowListOption.insert(.excludeDesktopElements)\n            }\n            self.init(windowListOption: windowListOption, referenceWindow: referenceWindow)\n        }\n    }\n\n    /// Retrieves a copy of the current window list as an array of dictionaries.\n    private static func copyWindowListArray(context: WindowListContext) -> [CFDictionary] {\n        let option = context.windowListOption\n        let windowID = context.referenceWindow?.windowID ?? kCGNullWindowID\n        guard let list = CGWindowListCopyWindowInfo(option, windowID) as? [CFDictionary] else {\n            return []\n        }\n        return list\n    }\n\n    /// Returns the current window list using the given context.\n    private static func getWindowList(context: WindowListContext) -> [WindowInfo] {\n        let list = copyWindowListArray(context: context)\n        return list.compactMap { WindowInfo(dictionary: $0) }\n    }\n}\n\n// MARK: All Windows\nextension WindowInfo {\n    /// Returns the current windows.\n    ///\n    /// - Parameter excludeDesktopWindows: A Boolean value that indicates whether\n    ///   to exclude desktop owned windows, such as the wallpaper and desktop icons.\n    static func getAllWindows(excludeDesktopWindows: Bool = false) -> [WindowInfo] {\n        var option = CGWindowListOption.optionAll\n        if excludeDesktopWindows {\n            option.insert(.excludeDesktopElements)\n        }\n        let context = WindowListContext(windowListOption: option, referenceWindow: nil)\n        return getWindowList(context: context)\n    }\n}\n\n// MARK: On Screen Windows\nextension WindowInfo {\n    /// Returns the on screen windows.\n    ///\n    /// - Parameter excludeDesktopWindows: A Boolean value that indicates whether\n    ///   to exclude desktop owned windows, such as the wallpaper and desktop icons.\n    static func getOnScreenWindows(excludeDesktopWindows: Bool = false) -> [WindowInfo] {\n        let context = WindowListContext(\n            onScreenOption: .onScreenOnly,\n            excludeDesktopWindows: excludeDesktopWindows\n        )\n        return getWindowList(context: context)\n    }\n\n    /// Returns the on screen windows above the given window.\n    ///\n    /// - Parameters:\n    ///   - window: The window to use as a reference point when determining which\n    ///     windows to return.\n    ///   - includeWindow: A Boolean value that indicates whether to include the\n    ///     window in the result.\n    ///   - excludeDesktopWindows: A Boolean value that indicates whether to exclude\n    ///     desktop owned windows, such as the wallpaper and desktop icons.\n    static func getOnScreenWindows(\n        above window: WindowInfo,\n        includeWindow: Bool = false,\n        excludeDesktopWindows: Bool = false\n    ) -> [WindowInfo] {\n        let context = WindowListContext(\n            onScreenOption: .above(window, includeWindow: includeWindow),\n            excludeDesktopWindows: excludeDesktopWindows\n        )\n        return getWindowList(context: context)\n    }\n\n    /// Returns the on screen windows below the given window.\n    ///\n    /// - Parameters:\n    ///   - window: The window to use as a reference point when determining which\n    ///     windows to return.\n    ///   - includeWindow: A Boolean value that indicates whether to include the\n    ///     window in the result.\n    ///   - excludeDesktopWindows: A Boolean value that indicates whether to exclude\n    ///     desktop owned windows, such as the wallpaper and desktop icons.\n    static func getOnScreenWindows(\n        below window: WindowInfo,\n        includeWindow: Bool = false,\n        excludeDesktopWindows: Bool = false\n    ) -> [WindowInfo] {\n        let context = WindowListContext(\n            onScreenOption: .below(window, includeWindow: includeWindow),\n            excludeDesktopWindows: excludeDesktopWindows\n        )\n        return getWindowList(context: context)\n    }\n}\n\n// MARK: Wallpaper Window\nextension WindowInfo {\n    /// Returns the wallpaper window in the given windows for the given display.\n    static func getWallpaperWindow(from windows: [WindowInfo], for display: CGDirectDisplayID) -> WindowInfo? {\n        windows.first(where: Predicates.wallpaperWindow(for: display))\n    }\n\n    /// Returns the wallpaper window for the given display.\n    static func getWallpaperWindow(for display: CGDirectDisplayID) -> WindowInfo? {\n        getWallpaperWindow(from: getOnScreenWindows(), for: display)\n    }\n}\n\n// MARK: Menu Bar Window\nextension WindowInfo {\n    /// Returns the menu bar window for the given display.\n    static func getMenuBarWindow(from windows: [WindowInfo], for display: CGDirectDisplayID) -> WindowInfo? {\n        windows.first(where: Predicates.menuBarWindow(for: display))\n    }\n\n    /// Returns the menu bar window for the given display.\n    static func getMenuBarWindow(for display: CGDirectDisplayID) -> WindowInfo? {\n        getMenuBarWindow(from: getOnScreenWindows(excludeDesktopWindows: true), for: display)\n    }\n}\n\n// MARK: WindowInfo: Equatable\nextension WindowInfo: Equatable {\n    static func == (lhs: WindowInfo, rhs: WindowInfo) -> Bool {\n        lhs.windowID == rhs.windowID &&\n        NSStringFromRect(lhs.frame) == NSStringFromRect(rhs.frame) &&\n        lhs.title == rhs.title &&\n        lhs.layer == rhs.layer &&\n        lhs.alpha == rhs.alpha &&\n        lhs.ownerPID == rhs.ownerPID &&\n        lhs.ownerName == rhs.ownerName &&\n        lhs.sharingState == rhs.sharingState &&\n        lhs.backingStoreType == rhs.backingStoreType &&\n        lhs.memoryUsage == rhs.memoryUsage &&\n        lhs.isOnScreen == rhs.isOnScreen &&\n        lhs.isBackedByVideoMemory == rhs.isBackedByVideoMemory\n    }\n}\n\n// MARK: WindowInfo: Hashable\nextension WindowInfo: Hashable {\n    func hash(into hasher: inout Hasher) {\n        hasher.combine(windowID)\n        hasher.combine(NSStringFromRect(frame))\n        hasher.combine(title)\n        hasher.combine(layer)\n        hasher.combine(alpha)\n        hasher.combine(ownerPID)\n        hasher.combine(ownerName)\n        hasher.combine(sharingState)\n        hasher.combine(backingStoreType)\n        hasher.combine(memoryUsage)\n        hasher.combine(isOnScreen)\n        hasher.combine(isBackedByVideoMemory)\n    }\n}\n"
  },
  {
    "path": "Ice.xcodeproj/project.pbxproj",
    "content": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 70;\n\tobjects = {\n\n/* Begin PBXBuildFile section */\n\t\t170423D92B56DE78004A2549 /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = 170423D82B56DE78004A2549 /* Sparkle */; };\n\t\t175061912B1543DD003144CD /* LaunchAtLogin in Frameworks */ = {isa = PBXBuildFile; productRef = 175061902B1543DD003144CD /* LaunchAtLogin */; };\n\t\t1787C4272B16890B002F50DF /* AXSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 1787C4262B16890B002F50DF /* AXSwift */; };\n\t\t17F71BB52B880B4500905CBA /* CompactSlider in Frameworks */ = {isa = PBXBuildFile; productRef = 17F71BB42B880B4500905CBA /* CompactSlider */; };\n\t\t7127A9FF2C4886D100D99DEF /* IfritStatic in Frameworks */ = {isa = PBXBuildFile; productRef = 7127A9FE2C4886D100D99DEF /* IfritStatic */; };\n/* End PBXBuildFile section */\n\n/* Begin PBXFileReference section */\n\t\t7166832A2A767E6A006ABF84 /* Ice.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Ice.app; sourceTree = BUILT_PRODUCTS_DIR; };\n/* End PBXFileReference section */\n\n/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */\n\t\t71BDFC6C2C978E2A00EF145F /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {\n\t\t\tisa = PBXFileSystemSynchronizedBuildFileExceptionSet;\n\t\t\tmembershipExceptions = (\n\t\t\t\tInfo.plist,\n\t\t\t\tResources/Acknowledgements.rtf,\n\t\t\t);\n\t\t\ttarget = 716683292A767E6A006ABF84 /* Ice */;\n\t\t};\n/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */\n\n/* Begin PBXFileSystemSynchronizedRootGroup section */\n\t\t71BDFBE12C978E2A00EF145F /* Ice */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (71BDFC6C2C978E2A00EF145F /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = Ice; sourceTree = \"<group>\"; };\n/* End PBXFileSystemSynchronizedRootGroup section */\n\n/* Begin PBXFrameworksBuildPhase section */\n\t\t716683272A767E6A006ABF84 /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t170423D92B56DE78004A2549 /* Sparkle in Frameworks */,\n\t\t\t\t7127A9FF2C4886D100D99DEF /* IfritStatic in Frameworks */,\n\t\t\t\t175061912B1543DD003144CD /* LaunchAtLogin in Frameworks */,\n\t\t\t\t1787C4272B16890B002F50DF /* AXSwift in Frameworks */,\n\t\t\t\t17F71BB52B880B4500905CBA /* CompactSlider in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXFrameworksBuildPhase section */\n\n/* Begin PBXGroup section */\n\t\t716683212A767E6A006ABF84 = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t71BDFBE12C978E2A00EF145F /* Ice */,\n\t\t\t\t7166832B2A767E6A006ABF84 /* Products */,\n\t\t\t);\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t7166832B2A767E6A006ABF84 /* Products */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t7166832A2A767E6A006ABF84 /* Ice.app */,\n\t\t\t);\n\t\t\tname = Products;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXGroup section */\n\n/* Begin PBXNativeTarget section */\n\t\t716683292A767E6A006ABF84 /* Ice */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 716683392A767E6B006ABF84 /* Build configuration list for PBXNativeTarget \"Ice\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t716683262A767E6A006ABF84 /* Sources */,\n\t\t\t\t716683272A767E6A006ABF84 /* Frameworks */,\n\t\t\t\t716683282A767E6A006ABF84 /* Resources */,\n\t\t\t\t1720D48F2BB9B60500A7AC63 /* SwiftLint */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tfileSystemSynchronizedGroups = (\n\t\t\t\t71BDFBE12C978E2A00EF145F /* Ice */,\n\t\t\t);\n\t\t\tname = Ice;\n\t\t\tpackageProductDependencies = (\n\t\t\t\t175061902B1543DD003144CD /* LaunchAtLogin */,\n\t\t\t\t1787C4262B16890B002F50DF /* AXSwift */,\n\t\t\t\t170423D82B56DE78004A2549 /* Sparkle */,\n\t\t\t\t17F71BB42B880B4500905CBA /* CompactSlider */,\n\t\t\t\t7127A9FE2C4886D100D99DEF /* IfritStatic */,\n\t\t\t);\n\t\t\tproductName = Ice;\n\t\t\tproductReference = 7166832A2A767E6A006ABF84 /* Ice.app */;\n\t\t\tproductType = \"com.apple.product-type.application\";\n\t\t};\n/* End PBXNativeTarget section */\n\n/* Begin PBXProject section */\n\t\t716683222A767E6A006ABF84 /* Project object */ = {\n\t\t\tisa = PBXProject;\n\t\t\tattributes = {\n\t\t\t\tBuildIndependentTargetsInParallel = 1;\n\t\t\t\tLastSwiftUpdateCheck = 1430;\n\t\t\t\tLastUpgradeCheck = 1640;\n\t\t\t\tTargetAttributes = {\n\t\t\t\t\t716683292A767E6A006ABF84 = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 14.3.1;\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t\tbuildConfigurationList = 716683252A767E6A006ABF84 /* Build configuration list for PBXProject \"Ice\" */;\n\t\t\tcompatibilityVersion = \"Xcode 14.0\";\n\t\t\tdevelopmentRegion = en;\n\t\t\thasScannedForEncodings = 0;\n\t\t\tknownRegions = (\n\t\t\t\ten,\n\t\t\t\tBase,\n\t\t\t);\n\t\t\tmainGroup = 716683212A767E6A006ABF84;\n\t\t\tpackageReferences = (\n\t\t\t\t1750618F2B1543DD003144CD /* XCRemoteSwiftPackageReference \"LaunchAtLogin-Modern\" */,\n\t\t\t\t1787C4252B16890B002F50DF /* XCRemoteSwiftPackageReference \"AXSwift\" */,\n\t\t\t\t170423D72B56DE78004A2549 /* XCRemoteSwiftPackageReference \"Sparkle\" */,\n\t\t\t\t17F71BB32B880B4500905CBA /* XCRemoteSwiftPackageReference \"CompactSlider\" */,\n\t\t\t\t7127A9FB2C4881BC00D99DEF /* XCRemoteSwiftPackageReference \"Ifrit\" */,\n\t\t\t);\n\t\t\tproductRefGroup = 7166832B2A767E6A006ABF84 /* Products */;\n\t\t\tprojectDirPath = \"\";\n\t\t\tprojectRoot = \"\";\n\t\t\ttargets = (\n\t\t\t\t716683292A767E6A006ABF84 /* Ice */,\n\t\t\t);\n\t\t};\n/* End PBXProject section */\n\n/* Begin PBXResourcesBuildPhase section */\n\t\t716683282A767E6A006ABF84 /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXResourcesBuildPhase section */\n\n/* Begin PBXShellScriptBuildPhase section */\n\t\t1720D48F2BB9B60500A7AC63 /* SwiftLint */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\talwaysOutOfDate = 1;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t);\n\t\t\tname = SwiftLint;\n\t\t\toutputFileListPaths = (\n\t\t\t);\n\t\t\toutputPaths = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"if [[ \\\"$(uname -m)\\\" == arm64 ]]; then\\n    export PATH=\\\"/opt/homebrew/bin:$PATH\\\"\\nfi\\n\\nif which swiftlint > /dev/null; then\\n  swiftlint\\nelse\\n  echo \\\"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\\\"\\nfi\\n\";\n\t\t};\n/* End PBXShellScriptBuildPhase section */\n\n/* Begin PBXSourcesBuildPhase section */\n\t\t716683262A767E6A006ABF84 /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXSourcesBuildPhase section */\n\n/* Begin XCBuildConfiguration section */\n\t\t716683372A767E6B006ABF84 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++20\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = dwarf;\n\t\t\t\tDEVELOPMENT_TEAM = K2ATHQPJDP;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_TESTABILITY = YES;\n\t\t\t\tENABLE_USER_SCRIPT_SANDBOXING = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGCC_DYNAMIC_NO_PIC = NO;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_OPTIMIZATION_LEVEL = 0;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 14.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tONLY_ACTIVE_ARCH = YES;\n\t\t\t\tSDKROOT = macosx;\n\t\t\t\tSWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-Onone\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t716683382A767E6B006ABF84 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++20\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tDEVELOPMENT_TEAM = K2ATHQPJDP;\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_USER_SCRIPT_SANDBOXING = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 14.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tSDKROOT = macosx;\n\t\t\t\tSWIFT_COMPILATION_MODE = wholemodule;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-O\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t7166833A2A767E6B006ABF84 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = Ice/Ice.entitlements;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=macosx*]\" = \"Apple Development\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1117;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEVELOPMENT_ASSET_PATHS = \"\";\n\t\t\t\tENABLE_HARDENED_RUNTIME = YES;\n\t\t\t\tENABLE_PREVIEWS = YES;\n\t\t\t\tENABLE_USER_SCRIPT_SANDBOXING = NO;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tINFOPLIST_FILE = Ice/Info.plist;\n\t\t\t\tINFOPLIST_KEY_LSApplicationCategoryType = \"public.app-category.utilities\";\n\t\t\t\tINFOPLIST_KEY_LSUIElement = YES;\n\t\t\t\tINFOPLIST_KEY_NSHumanReadableCopyright = \"Copyright © 2025 Jordan Baird\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/../Frameworks\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = 0.11.12;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.jordanbaird.Ice;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t7166833B2A767E6B006ABF84 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = Ice/Ice.entitlements;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=macosx*]\" = \"Apple Development\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1117;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEVELOPMENT_ASSET_PATHS = \"\";\n\t\t\t\tENABLE_HARDENED_RUNTIME = YES;\n\t\t\t\tENABLE_PREVIEWS = YES;\n\t\t\t\tENABLE_USER_SCRIPT_SANDBOXING = NO;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tINFOPLIST_FILE = Ice/Info.plist;\n\t\t\t\tINFOPLIST_KEY_LSApplicationCategoryType = \"public.app-category.utilities\";\n\t\t\t\tINFOPLIST_KEY_LSUIElement = YES;\n\t\t\t\tINFOPLIST_KEY_NSHumanReadableCopyright = \"Copyright © 2025 Jordan Baird\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/../Frameworks\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = 0.11.12;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.jordanbaird.Ice;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n/* End XCBuildConfiguration section */\n\n/* Begin XCConfigurationList section */\n\t\t716683252A767E6A006ABF84 /* Build configuration list for PBXProject \"Ice\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t716683372A767E6B006ABF84 /* Debug */,\n\t\t\t\t716683382A767E6B006ABF84 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t716683392A767E6B006ABF84 /* Build configuration list for PBXNativeTarget \"Ice\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t7166833A2A767E6B006ABF84 /* Debug */,\n\t\t\t\t7166833B2A767E6B006ABF84 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n/* End XCConfigurationList section */\n\n/* Begin XCRemoteSwiftPackageReference section */\n\t\t170423D72B56DE78004A2549 /* XCRemoteSwiftPackageReference \"Sparkle\" */ = {\n\t\t\tisa = XCRemoteSwiftPackageReference;\n\t\t\trepositoryURL = \"https://github.com/sparkle-project/Sparkle\";\n\t\t\trequirement = {\n\t\t\t\tkind = upToNextMajorVersion;\n\t\t\t\tminimumVersion = 2.5.2;\n\t\t\t};\n\t\t};\n\t\t1750618F2B1543DD003144CD /* XCRemoteSwiftPackageReference \"LaunchAtLogin-Modern\" */ = {\n\t\t\tisa = XCRemoteSwiftPackageReference;\n\t\t\trepositoryURL = \"https://github.com/sindresorhus/LaunchAtLogin-Modern\";\n\t\t\trequirement = {\n\t\t\t\tkind = upToNextMajorVersion;\n\t\t\t\tminimumVersion = 1.0.0;\n\t\t\t};\n\t\t};\n\t\t1787C4252B16890B002F50DF /* XCRemoteSwiftPackageReference \"AXSwift\" */ = {\n\t\t\tisa = XCRemoteSwiftPackageReference;\n\t\t\trepositoryURL = \"https://github.com/tmandry/AXSwift\";\n\t\t\trequirement = {\n\t\t\t\tkind = upToNextMajorVersion;\n\t\t\t\tminimumVersion = 0.3.2;\n\t\t\t};\n\t\t};\n\t\t17F71BB32B880B4500905CBA /* XCRemoteSwiftPackageReference \"CompactSlider\" */ = {\n\t\t\tisa = XCRemoteSwiftPackageReference;\n\t\t\trepositoryURL = \"https://github.com/buh/CompactSlider\";\n\t\t\trequirement = {\n\t\t\t\tkind = upToNextMajorVersion;\n\t\t\t\tminimumVersion = 1.1.5;\n\t\t\t};\n\t\t};\n\t\t7127A9FB2C4881BC00D99DEF /* XCRemoteSwiftPackageReference \"Ifrit\" */ = {\n\t\t\tisa = XCRemoteSwiftPackageReference;\n\t\t\trepositoryURL = \"https://github.com/ukushu/Ifrit\";\n\t\t\trequirement = {\n\t\t\t\tkind = upToNextMajorVersion;\n\t\t\t\tminimumVersion = 2.0.3;\n\t\t\t};\n\t\t};\n/* End XCRemoteSwiftPackageReference section */\n\n/* Begin XCSwiftPackageProductDependency section */\n\t\t170423D82B56DE78004A2549 /* Sparkle */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = 170423D72B56DE78004A2549 /* XCRemoteSwiftPackageReference \"Sparkle\" */;\n\t\t\tproductName = Sparkle;\n\t\t};\n\t\t175061902B1543DD003144CD /* LaunchAtLogin */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = 1750618F2B1543DD003144CD /* XCRemoteSwiftPackageReference \"LaunchAtLogin-Modern\" */;\n\t\t\tproductName = LaunchAtLogin;\n\t\t};\n\t\t1787C4262B16890B002F50DF /* AXSwift */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = 1787C4252B16890B002F50DF /* XCRemoteSwiftPackageReference \"AXSwift\" */;\n\t\t\tproductName = AXSwift;\n\t\t};\n\t\t17F71BB42B880B4500905CBA /* CompactSlider */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = 17F71BB32B880B4500905CBA /* XCRemoteSwiftPackageReference \"CompactSlider\" */;\n\t\t\tproductName = CompactSlider;\n\t\t};\n\t\t7127A9FE2C4886D100D99DEF /* IfritStatic */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = 7127A9FB2C4881BC00D99DEF /* XCRemoteSwiftPackageReference \"Ifrit\" */;\n\t\t\tproductName = IfritStatic;\n\t\t};\n/* End XCSwiftPackageProductDependency section */\n\t};\n\trootObject = 716683222A767E6A006ABF84 /* Project object */;\n}\n"
  },
  {
    "path": "Ice.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"self:\">\n   </FileRef>\n</Workspace>\n"
  },
  {
    "path": "Ice.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>IDEDidComputeMac32BitWarning</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "Ice.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved",
    "content": "{\n  \"originHash\" : \"a7567d11f06745371832127a8ce2132148ef6a89fb55ecc72d6c313b688387fa\",\n  \"pins\" : [\n    {\n      \"identity\" : \"axswift\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/tmandry/AXSwift\",\n      \"state\" : {\n        \"revision\" : \"81dcc36aced905d6464cc25e35f8d13184bbf21c\",\n        \"version\" : \"0.3.2\"\n      }\n    },\n    {\n      \"identity\" : \"compactslider\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/buh/CompactSlider\",\n      \"state\" : {\n        \"revision\" : \"abe4d1df6f0c85dcb133266cc07c2a5d08295726\",\n        \"version\" : \"1.1.6\"\n      }\n    },\n    {\n      \"identity\" : \"ifrit\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/ukushu/Ifrit\",\n      \"state\" : {\n        \"revision\" : \"e610cdf4eddec1e76a9c7ae5db37738c7f73150b\",\n        \"version\" : \"2.0.3\"\n      }\n    },\n    {\n      \"identity\" : \"launchatlogin-modern\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/sindresorhus/LaunchAtLogin-Modern\",\n      \"state\" : {\n        \"revision\" : \"a04ec1c363be3627734f6dad757d82f5d4fa8fcc\",\n        \"version\" : \"1.1.0\"\n      }\n    },\n    {\n      \"identity\" : \"sparkle\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/sparkle-project/Sparkle\",\n      \"state\" : {\n        \"revision\" : \"0ef1ee0220239b3776f433314515fd849025673f\",\n        \"version\" : \"2.6.4\"\n      }\n    }\n  ],\n  \"version\" : 3\n}\n"
  },
  {
    "path": "Ice.xcodeproj/xcshareddata/xcschemes/Ice.xcscheme",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1640\"\n   version = \"1.7\">\n   <BuildAction\n      parallelizeBuildables = \"YES\"\n      buildImplicitDependencies = \"YES\">\n      <BuildActionEntries>\n         <BuildActionEntry\n            buildForTesting = \"YES\"\n            buildForRunning = \"YES\"\n            buildForProfiling = \"YES\"\n            buildForArchiving = \"YES\"\n            buildForAnalyzing = \"YES\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"716683292A767E6A006ABF84\"\n               BuildableName = \"Ice.app\"\n               BlueprintName = \"Ice\"\n               ReferencedContainer = \"container:Ice.xcodeproj\">\n            </BuildableReference>\n         </BuildActionEntry>\n      </BuildActionEntries>\n   </BuildAction>\n   <TestAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\"\n      shouldAutocreateTestPlan = \"YES\">\n   </TestAction>\n   <LaunchAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      launchStyle = \"0\"\n      useCustomWorkingDirectory = \"NO\"\n      ignoresPersistentStateOnLaunch = \"NO\"\n      debugDocumentVersioning = \"YES\"\n      debugServiceExtension = \"internal\"\n      allowLocationSimulation = \"YES\">\n      <BuildableProductRunnable\n         runnableDebuggingMode = \"0\">\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"716683292A767E6A006ABF84\"\n            BuildableName = \"Ice.app\"\n            BlueprintName = \"Ice\"\n            ReferencedContainer = \"container:Ice.xcodeproj\">\n         </BuildableReference>\n      </BuildableProductRunnable>\n   </LaunchAction>\n   <ProfileAction\n      buildConfiguration = \"Release\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\"\n      savedToolIdentifier = \"\"\n      useCustomWorkingDirectory = \"NO\"\n      debugDocumentVersioning = \"YES\">\n      <BuildableProductRunnable\n         runnableDebuggingMode = \"0\">\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"716683292A767E6A006ABF84\"\n            BuildableName = \"Ice.app\"\n            BlueprintName = \"Ice\"\n            ReferencedContainer = \"container:Ice.xcodeproj\">\n         </BuildableReference>\n      </BuildableProductRunnable>\n   </ProfileAction>\n   <AnalyzeAction\n      buildConfiguration = \"Debug\">\n   </AnalyzeAction>\n   <ArchiveAction\n      buildConfiguration = \"Release\"\n      revealArchiveInOrganizer = \"YES\">\n   </ArchiveAction>\n</Scheme>\n"
  },
  {
    "path": "LICENSE",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    Ice - A powerful menu bar manager for macOS\n    Copyright (C) 2024 Jordan Baird\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    Ice Copyright (C) 2024 Jordan Baird\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<https://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<https://www.gnu.org/licenses/why-not-lgpl.html>.\n"
  },
  {
    "path": "README.md",
    "content": "<div align=\"center\">\n    <img src=\"Ice/Assets.xcassets/AppIcon.appiconset/icon_256x256.png\" width=200 height=200>\n    <h1>Ice</h1>\n</div>\n\nIce is a powerful menu bar management tool. While its primary function is hiding and showing menu bar items, it aims to cover a wide variety of additional features to make it one of the most versatile menu bar tools available.\n\n![Banner](https://github.com/user-attachments/assets/4423085c-4e4b-4f3d-ad0f-90a217c03470)\n\n[![Download](https://img.shields.io/badge/download-latest-brightgreen?style=flat-square)](https://github.com/jordanbaird/Ice/releases/latest)\n![Platform](https://img.shields.io/badge/platform-macOS-blue?style=flat-square)\n![Requirements](https://img.shields.io/badge/requirements-macOS%2014%2B-fa4e49?style=flat-square)\n[![Sponsor](https://img.shields.io/badge/Sponsor%20%E2%9D%A4%EF%B8%8F-8A2BE2?style=flat-square)](https://github.com/sponsors/jordanbaird)\n[![Website](https://img.shields.io/badge/Website-015FBA?style=flat-square)](https://icemenubar.app)\n[![License](https://img.shields.io/github/license/jordanbaird/Ice?style=flat-square)](LICENSE)\n\n> [!NOTE]\n> Ice is currently in active development. Some features have not yet been implemented. Download the latest release [here](https://github.com/jordanbaird/Ice/releases/latest) and see the roadmap below for upcoming features.\n\n<a href=\"https://www.buymeacoffee.com/jordanbaird\" target=\"_blank\">\n    <img src=\"https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png\" alt=\"Buy Me A Coffee\" style=\"height: 60px !important;width: 217px !important;\">\n</a>\n\n## Install\n\n### Manual Installation\n\nDownload the \"Ice.zip\" file from the [latest release](https://github.com/jordanbaird/Ice/releases/latest) and move the unzipped app into your `Applications` folder.\n\n### Homebrew\n\nInstall Ice using the following command:\n\n```sh\nbrew install --cask jordanbaird-ice\n```\n\n## Features/Roadmap\n\n### Menu bar item management\n\n- [x] Hide menu bar items\n- [x] \"Always-hidden\" menu bar section\n- [x] Show hidden menu bar items when hovering over the menu bar\n- [x] Show hidden menu bar items when an empty area in the menu bar is clicked\n- [x] Show hidden menu bar items by scrolling or swiping in the menu bar\n- [x] Automatically rehide menu bar items\n- [x] Hide application menus when they overlap with shown menu bar items\n- [x] Drag and drop interface to arrange individual menu bar items\n- [x] Display hidden menu bar items in a separate bar (e.g. for MacBooks with the notch)\n- [x] Search menu bar items\n- [x] Menu bar item spacing (BETA)\n- [ ] Profiles for menu bar layout\n- [ ] Individual spacer items\n- [ ] Menu bar item groups\n- [ ] Show menu bar items when trigger conditions are met\n\n### Menu bar appearance\n\n- [x] Menu bar tint (solid and gradient)\n- [x] Menu bar shadow\n- [x] Menu bar border\n- [x] Custom menu bar shapes (rounded and/or split)\n- [ ] Remove background behind menu bar\n- [ ] Rounded screen corners\n- [ ] Different settings for light/dark mode\n\n### Hotkeys\n\n- [x] Toggle individual menu bar sections\n- [x] Show the search panel\n- [x] Enable/disable the Ice Bar\n- [x] Show/hide section divider icons\n- [x] Toggle application menus\n- [ ] Enable/disable auto rehide\n- [ ] Temporarily show individual menu bar items\n\n### Other\n\n- [x] Launch at login\n- [x] Automatic updates\n- [ ] Menu bar widgets\n\n## Why does Ice only support macOS 14 and later?\n\nIce uses a number of system APIs that are available starting in macOS 14. As such, there are no plans to support earlier versions of macOS.\n\n## Gallery\n\n#### Show hidden menu bar items below the menu bar\n\n![Ice Bar](https://github.com/user-attachments/assets/f1429589-6186-4e1b-8aef-592219d49b9b)\n\n#### Drag-and-drop interface to arrange menu bar items\n\n![Menu Bar Layout](https://github.com/user-attachments/assets/095442ba-f2d0-4bb4-9632-91e26ef8d45b)\n\n#### Customize the menu bar's appearance\n\n![Menu Bar Appearance](https://github.com/user-attachments/assets/8c22c185-c3d2-49bb-971e-e1fc17df04b3)\n\n#### Menu bar item search\n\n![Menu Bar Item Search](https://github.com/user-attachments/assets/d1a7df3a-4989-4077-a0b1-8e7d5a1ba5b8)\n\n#### Custom menu bar item spacing\n\n![Menu Bar Item Spacing](https://github.com/user-attachments/assets/b196aa7e-184a-4d4c-b040-502f4aae40a6)\n\n## License\n\nIce is available under the [GPL-3.0 license](LICENSE).\n"
  }
]