Claude-skill-registry-data macos-core-frameworks
macOS Core Graphics and Core Foundation Rust bindings for display management, keyboard simulation, and event handling
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry-data
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry-data "$T" && mkdir -p ~/.claude/skills && cp -r "$T/data/macos-core-frameworks" ~/.claude/skills/majiayu000-claude-skill-registry-data-macos-core-frameworks && rm -rf "$T"
manifest:
data/macos-core-frameworks/SKILL.mdsource content
macos-core-frameworks
Rust bindings to macOS Core Graphics and Core Foundation frameworks via the
core-graphics (0.24) and core-foundation (0.10) crates. These provide low-level access to display information, keyboard simulation, event taps, and run loop management.
Crate Dependencies
[dependencies] core-graphics = "0.24" core-foundation = "0.10" foreign-types = "0.5" # For ForeignType trait (CGEvent pointer access)
Core Graphics
The
core-graphics crate provides bindings to macOS Core Graphics (Quartz) framework.
Key Modules
| Module | Purpose |
|---|---|
| Display enumeration, bounds, screenshots |
| CGEvent creation, posting, event taps |
| CGEventSource for keyboard/mouse events |
| CGPoint, CGSize, CGRect |
CGEvent for Keyboard Simulation
Create and post keyboard events to simulate keystrokes system-wide.
use core_graphics::event::{CGEvent, CGEventFlags, CGEventTapLocation, CGKeyCode}; use core_graphics::event_source::{CGEventSource, CGEventSourceStateID}; /// Simulate a keystroke with optional modifiers fn simulate_key(keycode: CGKeyCode, modifiers: CGEventFlags) -> anyhow::Result<()> { // Create event source - HIDSystemState is standard for synthetic events let source = CGEventSource::new(CGEventSourceStateID::HIDSystemState) .ok() .context("Failed to create CGEventSource")?; // Key down event let key_down = CGEvent::new_keyboard_event(source.clone(), keycode, true) .ok() .context("Failed to create key down event")?; key_down.set_flags(modifiers); // Key up event let key_up = CGEvent::new_keyboard_event(source, keycode, false) .ok() .context("Failed to create key up event")?; key_up.set_flags(modifiers); // Post to HID system (reaches all applications) key_down.post(CGEventTapLocation::HID); std::thread::sleep(std::time::Duration::from_millis(5)); key_up.post(CGEventTapLocation::HID); Ok(()) } // Common keycodes const KEY_BACKSPACE: CGKeyCode = 51; const KEY_V: CGKeyCode = 9; const KEY_C: CGKeyCode = 8; const KEY_TAB: CGKeyCode = 48; const KEY_RETURN: CGKeyCode = 36; const KEY_ESCAPE: CGKeyCode = 53;
Simulating Cmd+V (Paste)
fn simulate_paste() -> anyhow::Result<()> { use core_graphics::event::{CGEvent, CGEventFlags, CGEventTapLocation, CGKeyCode}; use core_graphics::event_source::{CGEventSource, CGEventSourceStateID}; const KEY_V: CGKeyCode = 9; let source = CGEventSource::new(CGEventSourceStateID::HIDSystemState) .ok() .context("Failed to create CGEventSource")?; let key_down = CGEvent::new_keyboard_event(source.clone(), KEY_V, true) .ok() .context("Failed to create paste key down event")?; key_down.set_flags(CGEventFlags::CGEventFlagCommand); let key_up = CGEvent::new_keyboard_event(source, KEY_V, false) .ok() .context("Failed to create paste key up event")?; key_up.set_flags(CGEventFlags::CGEventFlagCommand); key_down.post(CGEventTapLocation::HID); std::thread::sleep(std::time::Duration::from_millis(5)); key_up.post(CGEventTapLocation::HID); Ok(()) }
CGEventFlags Reference
use core_graphics::event::CGEventFlags; // Modifier flags CGEventFlags::CGEventFlagShift // Shift key CGEventFlags::CGEventFlagControl // Control key CGEventFlags::CGEventFlagAlternate // Option/Alt key CGEventFlags::CGEventFlagCommand // Command key // Check flags on received events let flags = event.get_flags(); let has_cmd = flags.contains(CGEventFlags::CGEventFlagCommand);
CGEventTap for Global Keyboard Monitoring
Capture keyboard events system-wide (requires Accessibility permission).
use core_graphics::event::{ CGEvent, CGEventTap, CGEventTapLocation, CGEventTapOptions, CGEventTapPlacement, CGEventType, EventField, }; use core_foundation::runloop::{kCFRunLoopCommonModes, CFRunLoop}; fn create_keyboard_monitor(callback: impl Fn(&CGEvent) + Send + 'static) -> Result<()> { let event_tap = CGEventTap::new( CGEventTapLocation::HID, // Capture at HID level CGEventTapPlacement::HeadInsertEventTap, CGEventTapOptions::ListenOnly, // Don't modify events vec![CGEventType::KeyDown], // Only key down events move |_proxy, event_type, event: &CGEvent| { // Handle tap disabled events match event_type { CGEventType::TapDisabledByTimeout | CGEventType::TapDisabledByUserInput => { // Re-enable tap (see reenable_tap pattern below) return None; } _ => {} } // Extract key information let keycode = event.get_integer_value_field( EventField::KEYBOARD_EVENT_KEYCODE ) as u16; let is_repeat = event.get_integer_value_field( EventField::KEYBOARD_EVENT_AUTOREPEAT ) != 0; callback(event); None // Return None to not modify the event }, )?; // Create run loop source and add to current run loop let source = event_tap.mach_port.create_runloop_source(0)?; unsafe { CFRunLoop::get_current().add_source(&source, kCFRunLoopCommonModes); } event_tap.enable(); Ok(()) }
Getting Unicode Character from CGEvent
The
core-graphics crate doesn't expose CGEventKeyboardGetUnicodeString, so use FFI:
fn get_character_from_event(event: &CGEvent) -> Option<String> { extern "C" { fn CGEventKeyboardGetUnicodeString( event: core_graphics::sys::CGEventRef, max_len: libc::c_ulong, actual_len: *mut libc::c_ulong, buffer: *mut u16, ); } const BUFFER_SIZE: usize = 32; let mut buffer: [u16; BUFFER_SIZE] = [0; BUFFER_SIZE]; let mut actual_len: libc::c_ulong = 0; unsafe { use foreign_types::ForeignType; let event_ptr = event.as_ptr(); CGEventKeyboardGetUnicodeString( event_ptr, BUFFER_SIZE as libc::c_ulong, &mut actual_len, buffer.as_mut_ptr(), ); } if actual_len > 0 && (actual_len as usize) <= BUFFER_SIZE { String::from_utf16(&buffer[..actual_len as usize]).ok() } else { None } }
Display Management
use core_graphics::display::{CGDisplay, CGRect}; /// Get all active displays fn get_displays() -> Result<Vec<u32>> { CGDisplay::active_displays() .map_err(|e| anyhow::anyhow!("Failed to get displays: error {}", e)) } /// Get main display fn get_main_display() -> CGDisplay { CGDisplay::main() } /// Get display bounds fn get_display_bounds(display_id: u32) -> CGRect { let display = CGDisplay::new(display_id); display.bounds() // Returns CGRect with origin and size } /// Check if display is main fn is_main_display(display_id: u32) -> bool { display_id == CGDisplay::main().id }
CGPoint and CGSize for Window Position/Size
use core_graphics::geometry::{CGPoint, CGSize}; // Create point let point = CGPoint::new(100.0, 200.0); // Create size let size = CGSize::new(800.0, 600.0); // Used with AXValue for accessibility APIs use std::ffi::c_void; extern "C" { fn AXValueCreate(value_type: i32, value: *const c_void) -> *const c_void; } const kAXValueTypeCGPoint: i32 = 1; const kAXValueTypeCGSize: i32 = 2; let point = CGPoint::new(100.0, 200.0); let ax_point = unsafe { AXValueCreate(kAXValueTypeCGPoint, &point as *const _ as *const c_void) };
Core Foundation
The
core-foundation crate provides bindings to macOS Core Foundation framework.
Key Modules
| Module | Purpose |
|---|---|
| CFRunLoop for event loop management |
| CFMachPort for event tap sources |
| TCFType trait for CF object management |
| CFString operations |
CFRunLoop for Event Processing
Required for CGEventTap to receive events.
use core_foundation::runloop::{ kCFRunLoopCommonModes, kCFRunLoopDefaultMode, CFRunLoop, CFRunLoopRunResult, }; use std::time::Duration; // Get current thread's run loop let run_loop = CFRunLoop::get_current(); // Add event source to run loop unsafe { run_loop.add_source(&run_loop_source, kCFRunLoopCommonModes); } // Run loop with timeout (non-blocking check) let result = CFRunLoop::run_in_mode( unsafe { kCFRunLoopDefaultMode }, Duration::from_millis(100), true, // return after source handled ); match result { CFRunLoopRunResult::Stopped => { /* Loop was stopped */ } CFRunLoopRunResult::TimedOut => { /* Timeout elapsed */ } CFRunLoopRunResult::HandledSource => { /* Source was handled */ } _ => {} } // Stop the run loop (from another thread) run_loop.stop();
CFMachPort for Event Tap
use core_foundation::base::TCFType; use core_foundation::mach_port::CFMachPortRef; // After creating CGEventTap, get mach port let mach_port_ref = event_tap.mach_port.as_concrete_TypeRef(); // Re-enable a disabled tap extern "C" { fn CGEventTapEnable(tap: CFMachPortRef, enable: bool); } unsafe { CGEventTapEnable(mach_port_ref, true); }
Usage in script-kit-gpui
Keyboard Monitoring (keyboard_monitor.rs
)
keyboard_monitor.rsGlobal keystroke capture for text expansion triggers:
withCGEventTap
for system-wide captureCGEventTapLocation::HID
on dedicated background threadCFRunLoop- Handles tap disable/re-enable for reliability
- Extracts key codes, modifiers, and Unicode characters
Text Injection (text_injector.rs
)
text_injector.rsDelete trigger text and paste replacement:
- Simulates backspace key events via
CGEvent - Simulates Cmd+V for clipboard paste
- Uses
CGEventSourceStateID::HIDSystemState
Selected Text (selected_text.rs
)
selected_text.rsGet/set selected text in any application:
- Simulates Cmd+C/Cmd+V with modifier flags
- Posts to
CGEventTapLocation::HID
Display Info (window_control.rs
, display.rs
)
window_control.rsdisplay.rsQuery display configuration:
for all monitorsCGDisplay::active_displays()
for screen dimensionsCGDisplay::bounds()- Coordinate conversion between CG and Cocoa systems
CGEventTapLocation Options
| Location | Description | Use Case |
|---|---|---|
| Hardware level | Keyboard simulation, global monitoring |
| User session | App-specific events |
| With annotations | Accessibility apps |
Anti-patterns
Don't Use CombinedSessionState for Simulation
// WRONG - May not work reliably for simulation let source = CGEventSource::new(CGEventSourceStateID::CombinedSessionState); // CORRECT - Use HIDSystemState for synthetic keyboard events let source = CGEventSource::new(CGEventSourceStateID::HIDSystemState);
Don't Forget to Handle Tap Disable Events
// Event taps can be disabled by system timeout or user input // Always handle CGEventType::TapDisabledByTimeout and TapDisabledByUserInput match event_type { CGEventType::TapDisabledByTimeout | CGEventType::TapDisabledByUserInput => { // Re-enable the tap! unsafe { CGEventTapEnable(port, true); } return None; } _ => {} }
Don't Block the Main Thread with CFRunLoop
// WRONG - Blocks main thread CFRunLoop::run_in_mode(kCFRunLoopDefaultMode, Duration::MAX, false); // CORRECT - Run on dedicated thread with periodic checks std::thread::spawn(move || { while running.load(Ordering::SeqCst) { CFRunLoop::run_in_mode( kCFRunLoopDefaultMode, Duration::from_millis(100), true, ); } });
Don't Forget Key Up Events
// WRONG - Key gets stuck let key_down = CGEvent::new_keyboard_event(source, keycode, true)?; key_down.post(CGEventTapLocation::HID); // CORRECT - Always send down AND up let key_down = CGEvent::new_keyboard_event(source.clone(), keycode, true)?; key_down.post(CGEventTapLocation::HID); std::thread::sleep(Duration::from_millis(5)); let key_up = CGEvent::new_keyboard_event(source, keycode, false)?; key_up.post(CGEventTapLocation::HID);
Don't Forget Accessibility Permissions
Event taps require Accessibility permission:
use macos_accessibility_client::accessibility; if !accessibility::application_is_trusted() { // Show permission dialog or return error accessibility::application_is_trusted_with_prompt(); return Err("Accessibility permission required"); }
Memory Management
Core Foundation objects follow reference counting:
// Objects from "Copy" functions are owned - must release extern "C" { fn CFRelease(cf: *const c_void); } // Objects from "Get" functions are borrowed - don't release // CFArrayGetValueAtIndex returns borrowed reference // Retain to extend lifetime beyond container extern "C" { fn CFRetain(cf: *const c_void) -> *const c_void; }
Common Key Codes
| Key | Code | Key | Code |
|---|---|---|---|
| A-Z | 0-25 (varies) | Backspace | 51 |
| Return | 36 | Tab | 48 |
| Escape | 53 | Space | 49 |
| V | 9 | C | 8 |
| X | 7 | Z | 6 |