AgentSkillsCN

script-kit-menu-bar

macOS 菜单栏无障碍系统,适用于 Script Kit GPUI。涵盖基于 AX 的菜单读取、递归菜单解析、菜单动作执行及 SQLite 缓存。适用于: (1) 通过无障碍 API 读取应用程序菜单栏 (2) 以编程方式执行菜单动作 (3) 缓存菜单结构以提升性能 (4) 处理 AXUIElement 层次结构(App -> MenuBar -> MenuBarItem -> Menu -> MenuItem) (5) 处理键盘快捷键及修饰符标志

SKILL.md
--- frontmatter
name: script-kit-menu-bar
description: |
  macOS menu bar accessibility system for Script Kit GPUI. Covers AX-based menu reading,
  recursive menu parsing, menu action execution, and SQLite caching. Use when:
  (1) Reading application menu bars via Accessibility APIs
  (2) Executing menu actions programmatically
  (3) Caching menu structures for performance
  (4) Working with AXUIElement hierarchy (App -> MenuBar -> MenuBarItem -> Menu -> MenuItem)
  (5) Handling keyboard shortcuts and modifier flags

Script Kit Menu Bar System

macOS Accessibility API-based menu bar reading and execution for Script Kit GPUI.

Architecture Overview

code
AXApplication
    |
    +-- AXMenuBar
           |
           +-- AXMenuBarItem (File, Edit, View...)
                   |
                   +-- AXMenu (container)
                           |
                           +-- AXMenuItem (with shortcuts, children)

Core Modules

ModulePurposeReference
menu_bar.rsRead menu hierarchies via AX APIsmenu-bar-reader.md
menu_executor.rsExecute menu actions via AXPressmenu-executor.md
menu_cache.rsSQLite persistence for menu datamenu-cache.md

Quick Reference

Reading Menus

rust
use script_kit_gpui::menu_bar::{get_frontmost_menu_bar, get_menu_bar_for_pid};

// Read menu bar of current menu owner (not frontmost app!)
let items = get_frontmost_menu_bar()?;

// Read specific app's menu by PID
let items = get_menu_bar_for_pid(pid)?;

Executing Actions

rust
use script_kit_gpui::menu_executor::execute_menu_action;

// App MUST be frontmost for this to work
execute_menu_action("com.apple.Safari", &["File", "New Window"])?;

Using Cache

rust
use script_kit_gpui::menu_cache::*;

init_menu_cache_db()?;  // Call once at startup

// Check cache first
if let Some(items) = get_cached_menu("com.apple.Safari")? {
    if is_cache_valid("com.apple.Safari", 3600)? {
        return Ok(items);
    }
}

// Scan and cache
let items = scan_menu_bar()?;
set_cached_menu("com.apple.Safari", &items, Some("17.0"))?;

Key Concepts

Menu Bar Owner vs Frontmost App

Script Kit runs as LSUIElement (accessory app) and doesn't take menu bar ownership:

  • Menu bar owner: App whose menus appear in system menu bar
  • Frontmost app: App receiving keyboard/mouse input
  • Use menuBarOwningApplication to get the correct target

AX Element Path

Each MenuBarItem stores ax_element_path: Vec<usize> - indices to navigate back to the element:

rust
// Path [0, 2] means: menu bar item at index 0, child at index 2
pub struct MenuBarItem {
    pub ax_element_path: Vec<usize>,
    // ...
}

Keyboard Shortcuts

rust
pub struct KeyboardShortcut {
    pub key: String,           // "S", "N", "Q"
    pub modifiers: ModifierFlags,
}

// ModifierFlags uses bitflags
ModifierFlags::COMMAND  // 256 - Cmd
ModifierFlags::SHIFT    // 512 - Shift  
ModifierFlags::OPTION   // 2048 - Option
ModifierFlags::CONTROL  // 4096 - Control

Error Handling

MenuExecutorError Variants

ErrorCauseSolution
AccessibilityPermissionDeniedNo AX permissionSystem Preferences > Privacy > Accessibility
AppNotFrontmostTarget app not activeActivate app before executing
MenuItemNotFoundPath doesn't existRescan menu, path may have changed
MenuItemDisabledItem is grayed outCheck enabled state before executing
MenuStructureChangedMenu differs from cacheInvalidate cache, rescan

Prerequisites

  1. Accessibility Permission - Required in System Preferences > Privacy & Security > Accessibility
  2. Target App Active - For execution, target must be frontmost (not just menu bar owner)
  3. SQLite - For caching (rusqlite crate)