Overview
KeystrokeListener is a singleton ObservableObject that:- Monitors all keyboard input system-wide
- Filters for valid characters (letters, numbers, punctuation, whitespace)
- Detects the active application and project
- Respects pause state
- Manages accessibility permissions
KeystrokeListener.swift:5-12
Accessibility Permissions
Checking Permissions
TypeSteps requires Accessibility permissions to monitor global keyboard events. There are two methods:With Prompt (Interactive)
KeystrokeListener.swift:15-21
Silent Check
KeystrokeListener.swift:23-29
AXIsProcessTrusted
TheAXIsProcessTrusted() function from ApplicationServices checks if the app has been granted Accessibility permissions in:
System Settings > Privacy & Security > Accessibility
Without this permission, NSEvent.addGlobalMonitorForEvents will not receive any events.
Global Event Monitoring
Starting the Listener
KeystrokeListener.swift:31-36
NSEvent.addGlobalMonitorForEvents
This AppKit API monitors events across all applications:- matching: .keyDown: Only captures key press events (not releases)
- Global scope: Receives events from all apps, not just TypeSteps
- Event handler: Closure called for each matching event
- Returns: Monitor object that must be retained
Stopping the Listener
KeystrokeListener.swift:38-43
Event Processing
Handle Method
Processes each keyboard event:KeystrokeListener.swift:45-62
Character Filtering
Only specific character types are counted:- Letters:
a-z,A-Z - Numbers:
0-9 - Punctuation:
,,.,!,?,;,:, etc. - Whitespace: Space, Tab, Enter
- Modifier keys alone: Shift, Ctrl, Cmd, Option
- Function keys: F1-F12
- Arrow keys
- Escape, Delete, etc.
- Media keys
Application Detection
NSWorkspace.shared.frontmostApplication
Detects which app is currently active:- localizedName: User-facing app name (e.g., “Visual Studio Code”)
- bundleIdentifier: Unique app ID (e.g., “com.visualstudio.code”)
Why Both Name and Bundle ID?
- Name: For display in UI
- Bundle ID: For reliable identification and categorization
- Apps can be renamed
- Bundle IDs never change
- Used to fetch app icons
- Used in categorization logic
Project Name Extraction
Strategy
Extracts project names from window titles using app-specific patterns:KeystrokeListener.swift:64-90
Accessibility API Usage
AXUIElementCreateSystemWide()- Creates a reference to the system-wide accessibility element
- Gets the currently focused application’s accessibility element
- Retrieves the window title of the focused app
App-Specific Parsing
Xcode
Format:"ProjectName — FileName.swift"
Extraction: Take first component before —
Example: "typesteps — KeystrokeListener.swift" → "typesteps"
Visual Studio Code / Cursor
Format:"FileName - ProjectName - Visual Studio Code"
Extraction: Take second-to-last component
Example: "main.ts - my-app - Visual Studio Code" → "my-app"
Limitations
- Only works for apps with known title formats
- Requires Accessibility permissions
- Returns
nilfor unsupported apps - Terminal apps don’t expose project names reliably
Pause/Resume Feature
Users can pause tracking from the menu bar:handle() method returns early:
Performance Considerations
Main Queue Dispatch
The event handler runs on a background thread, so UI updates are dispatched to main:Minimal Processing
The event handler is extremely lightweight:- Check pause state (single boolean check)
- Get frontmost app (cached by system)
- Get window title (only when needed)
- Filter character
- Increment counter