Imported from ryusaksun/MyIce (
AGENTS.md). Install upstream withnpx skills add ryusaksun/MyIce. Copyright stays with the author.
AGENTS.md
This file provides guidance to Codex (Codex.ai/code) when working with code in this repository.
Project Overview
MyIce is a native macOS menu bar management tool built with Swift/SwiftUI. It hides/shows menu bar items and provides appearance customization (tint, shadow, border, shapes). Requires macOS 14+ (Sonoma). Licensed under GPL-3.0.
Build & Run
This is an Xcode project (MyIce.xcodeproj) — there is no Package.swift at the root. Bundle identifier: com.ryuichi.MyIce.
- Build:
xcodebuild -project MyIce.xcodeproj -scheme MyIce -configuration Debug build - Build (Release, arm64 only):
xcodebuild -project MyIce.xcodeproj -scheme MyIce -configuration Release build -derivedDataPath build ARCHS=arm64 ONLY_ACTIVE_ARCH=YES - Build with signing (required to persist TCC permissions across rebuilds):
xcodebuild -project MyIce.xcodeproj -scheme MyIce -configuration Release build -derivedDataPath build \ CODE_SIGN_IDENTITY="Apple Development: <YOUR_ID>" \ DEVELOPMENT_TEAM="2FJFJ2WAF8" CODE_SIGN_STYLE="Manual" \ ARCHS=arm64 ONLY_ACTIVE_ARCH=YES - Install:
cp -R build/Build/Products/Release/MyIce.app /Applications/MyIce.app - Run:
open /Applications/MyIce.app - Lint:
swiftlint --strict(CI enforces--strictmode)
No test targets exist in this project.
CI: .github/workflows/lint.yml runs SwiftLint --strict on push to main and all PRs (paths: **/*.swift, .swiftlint.yml).
Important: Ad-hoc signing (CODE_SIGN_IDENTITY="-") causes TCC (Accessibility/Screen Recording) permissions to be lost on every rebuild. Always use a proper Apple Development certificate for iterative development.
Linting Rules
SwiftLint is configured in .swiftlint.yml. Key conventions:
- File header required — every Swift file must start with:
// // FileName.swift // MyIce // - Trailing commas mandatory in multi-line collections
- 4-space indentation (no tabs)
@objc dynamic—dynamicmust immediately follow@objc- Force unwrapping and implicitly unwrapped optionals are opt-in warnings
- Modifier order:
acl → setterACL → override → mutators → lazy → final → required → convenience → typeMethods → owned
Architecture
Core Pattern
Manager-based architecture with a central AppState singleton (@MainActor). Managers own their domains and communicate via Combine publishers. SwiftUI for views, AppKit for system integration.
Key Modules
| Module | Path | Purpose |
|---|---|---|
| AppState | MyIce/Main/AppState.swift |
Central state, owns all managers |
| MenuBar | MyIce/MenuBar/ |
Menu bar item management, appearance, control items, search, spacing |
| Events | MyIce/Events/ |
Event monitors (global, local, universal) and CGEvent taps |
| Hotkeys | MyIce/Hotkeys/ |
Global hotkey registration via Carbon framework |
| Settings | MyIce/Settings/ |
Settings panes (SwiftUI) and settings managers |
| Permissions | MyIce/Permissions/ |
Accessibility (required) and screen recording (optional) permission checks |
| Bridging | MyIce/Bridging/ |
CoreGraphics Server (CGS) private API bindings for window/connection manipulation |
| UI | MyIce/UI/ |
Reusable SwiftUI components: IceBar, LayoutBar, pickers, hotkey recorder |
| Utilities | MyIce/Utilities/ |
Constants, UserDefaults wrappers, extensions, logging, migration |
How Menu Bar Hiding Works
The core mechanism uses three NSStatusItem control items as section delimiters:
| Identifier | rawValue | Purpose |
|---|---|---|
.iceIcon |
"SItem" |
MyIce icon in the visible section (always visible) |
.hidden |
"HItem" |
Divider between visible and hidden sections |
.alwaysHidden |
"AHItem" |
Divider between hidden and always-hidden sections |
Hiding mechanism: When items are hidden, HItem's length is set to 10,000 pixels, physically pushing items to its left off-screen. When shown, HItem shrinks to standard width, letting items appear.
Item discovery: MenuBarItem.getMenuBarItems() → Bridging.getWindowList(option: .menuBarItems) → CGSGetProcessMenuBarWindowList (private API) → creates MenuBarItem from each WindowInfo.
Section assignment: Items are assigned to sections based on their X-position relative to control items (visible = right of HItem, hidden = between HItem and AHItem).
Image cache: MenuBarItemImageCache captures screenshots of menu bar items using CGWindowListCreateImageFromArray (deprecated, wrapped via protocol trick) or SCScreenshotManager. Images are keyed by CGWindowID.
Dependencies (SPM)
- LaunchAtLogin-Modern — login item support
- AXSwift — Accessibility API wrapper (used to interact with menu bar items)
- CompactSlider — custom slider UI
- Ifrit — JSON utilities
Key Technical Details
- No sandbox — the app needs direct access to Accessibility APIs and CGS private APIs
- Accessibility permission is required for core functionality (menu bar item control)
- Screen recording permission is optional (for computing menu bar average color and capturing item images)
- Uses method swizzling on
NSSplitViewItem(MyIce/Swizzling/) MigrationManagerhandles version-to-version data migrations- The project uses
PBXFileSystemSynchronizedRootGroup(Xcode 16+), so files added underMyIce/are automatically included in the build - Rebranded from Ice → MyIce — the project was recently renamed; some internal path references (e.g.
Ice/Assets.xcassets) may still use the old name - Localization: only Simplified Chinese (
MyIce/Resources/zh-Hans.lproj/Localizable.strings) exists; English strings are hardcoded as the development language fallback. When adding user-facing strings, useNSLocalizedStringand add corresponding entries to the zh-Hans.stringsfile
macOS 26 (Tahoe) Compatibility Notes
Critical behavioral changes on macOS 26 that affect this codebase:
NSStatusItem Namespace Change
All NSStatusItem windows report owningApplication.bundleIdentifier as "com.apple.controlcenter" instead of the actual app's bundle ID. This breaks MenuBarItemInfo matching by namespace. Always match control items by title (e.g., item.info.title == ControlItem.Identifier.hidden.rawValue) rather than by full info equality (item.info == .hiddenControlItem).
Generic Window Titles
Many status items report title "Item-0" instead of unique names, causing MenuBarItemInfo collisions. The image cache uses CGWindowID as key instead of MenuBarItemInfo to avoid this.
CGWindowID Safety
NSWindow.windowNumber (Int) can be negative on macOS 26. Always use CGWindowID(exactly: windowNumber) instead of CGWindowID(windowNumber) to prevent integer overflow crashes.
Deprecated Screen Capture APIs
CGWindowListCreateImage / CGWindowListCreateImageFromArray are marked obsoleted in macOS 15+ SDK. The project uses a protocol conformance trick (WindowListImage) to suppress the compiler error. These APIs still function at runtime but may return images with the menu bar's liquid glass background baked in.
Permission Detection
CGPreflightScreenCaptureAccess() may return stale values. Use SCShareableContent.getExcludingDesktopWindows() for reliable permission checks. The cachedCheckPermissions() must be called with reset: true to avoid stale cached results.
Settings Window Tracking
AppState.settingsWindow may be nil at setup time, causing isSettingsPresented to never become true. The image cache update logic includes fallback checks that bypass this.
ControlItem Constraint Hack
The constraint-based zero-width hiding (ControlItem.swift line ~106) is fragile. On macOS 26, constraint deactivation can cascade and hide the MyIce icon. The MyIce icon (.iceIcon) has a special guard to prevent isVisible from being set to false.