Building CatPaws: A macOS App That Protects Your Work from Curious Cats

Building CatPaws: A macOS App That Protects Your Work from Curious Cats
CatPaws Logo: Wookie's paw

Every cat owner who works from home knows the struggle: you're deep in concentration, crafting the perfect email or debugging complex code, when suddenly your feline friend decides that your keyboard is the most desirable spot in the entire house. The result? Random characters flooding your document, accidentally sent emails, or worse—deleted code.

Young Leia helping me code.

I was pretty amazed to find out that there is no app for that on macOS. That's when I decided to build CatPaws, a free, open-source macOS menu bar app that solves this problem by detecting when a cat walks on your keyboard and automatically locking input to prevent unwanted keystrokes.

Download CatPaws here.

Why CatPaws?

Working from home with cats presents a unique challenge. Cats are drawn to keyboards for several reasons: the warmth of the laptop, the attention they get when they walk across it, or simply because it's where you're focused and they want to mirror your behavior. Traditional solutions like closing the laptop lid or pushing the cat away interrupt your workflow.

CatPaws takes a different approach: let the cat do what cats do, while protecting your work automatically.

Key Features

  • Smart Cat Detection – Recognizes cat-like keyboard patterns and distinguishes them from normal typing
  • Instant Protection – Blocks all keyboard input within milliseconds of detecting your cat
  • Privacy First – No data collection, no network access, everything stays on your Mac
  • Statistics – Track how often your cat visits your keyboard (spoiler: probably more than you think)

How Does Cat Detection Work?

The core challenge of CatPaws is distinguishing between a cat walking on a keyboard and a human typing. It turns out there's a reliable pattern difference.

Human Typing vs. Cat Paws

When humans type, keys are pressed sequentially—one after another, rarely more than two at a time (think: Shift+letter). Even fast typists don't press multiple non-modifier keys simultaneously.

Cats, however, are different. A single paw placement typically presses 3 or more adjacent keys simultaneously. Multiple paws or a cat sitting on the keyboard can trigger 10+ keys at once across different regions of the keyboard.

The Detection Algorithm

CatPaws monitors keyboard events at the system level and analyzes key patterns in real-time:

func analyzePattern(pressedKeys: Set<UInt16>) -> DetectionEvent? {
    // Filter out modifier keys (Shift, Command, etc.)
    let nonModifierKeys = KeyboardAdjacencyMap.filterModifiers(from: pressedKeys)
    
    // Need at least 3 non-modifier keys for detection
    guard nonModifierKeys.count >= minimumKeyCount else {
        return nil
    }
    
    // Check for sitting pattern (10+ keys)
    if nonModifierKeys.count >= sittingThreshold {
        return DetectionEvent(type: .sitting, keyCount: nonModifierKeys.count)
    }
    
    // Find connected clusters of adjacent keys
    let clusters = findClusters(in: nonModifierKeys, layout: currentLayout)
    
    // Check for multi-paw pattern (2+ clusters each with 3+ keys)
    let significantClusters = clusters.filter { $0.count >= minimumKeyCount }
    if significantClusters.count >= 2 {
        return DetectionEvent(type: .multiPaw, keyCount: nonModifierKeys.count)
    }
    
    // Check for single paw pattern (one cluster with 3+ adjacent keys)
    if let largestCluster = clusters.max(by: { $0.count < $1.count }),
       largestCluster.count >= minimumKeyCount {
        return DetectionEvent(type: .paw, keyCount: largestCluster.count)
    }
    
    return nil
}

Key Adjacency Mapping

A critical component is determining whether pressed keys are adjacent on the physical keyboard. CatPaws maintains a complete map of key positions in "key-width units":

struct KeyPosition {
    let posX: Double  // Horizontal position
    let posY: Double  // Row (0 = number row, 1 = QWERTY, etc.)
}

// Example positions for the QWERTY row
0x0C: KeyPosition(posX: 1.5, posY: 1),   // Q
0x0D: KeyPosition(posX: 2.5, posY: 1),   // W
0x0E: KeyPosition(posX: 3.5, posY: 1),   // E
0x0F: KeyPosition(posX: 4.5, posY: 1),   // R

Two keys are considered adjacent if their Euclidean distance is ≤1.6 key-widths, which captures immediate neighbors and diagonal keys:

static func areAdjacent(_ key1: UInt16, _ key2: UInt16, layout: Layout) -> Bool {
    guard let dist = distance(between: key1, and: key2, layout: layout) else {
        return false
    }
    return dist <= adjacencyThreshold  // 1.6 key-widths
}

Multi-Layout Support

CatPaws supports multiple keyboard layouts (QWERTY, AZERTY, QWERTZ, Dvorak) and automatically detects the current layout using macOS's Text Input Source Services:

private func detectCurrentLayout() -> KeyboardAdjacencyMap.Layout {
    guard let inputSource = TISCopyCurrentKeyboardLayoutInputSource()?.takeRetainedValue(),
          let sourceIdRef = TISGetInputSourceProperty(inputSource, kTISPropertyInputSourceID),
          let sourceId = Unmanaged<CFString>.fromOpaque(sourceIdRef).takeUnretainedValue() as String?
    else {
        return .qwerty
    }
    return KeyboardAdjacencyMap.Layout.from(inputSourceId: sourceId)
}

Technical Implementation Deep Dive

System-Level Keyboard Monitoring

CatPaws uses macOS's CGEvent tap API to intercept keyboard events at the system level. This requires Input Monitoring permission from the user:

func startMonitoring() throws {
    guard hasPermission() else {
        throw PermissionError.accessibilityNotGranted
    }
    
    let eventMask: CGEventMask = (1 << CGEventType.keyDown.rawValue) |
                                  (1 << CGEventType.keyUp.rawValue) |
                                  (1 << CGEventType.flagsChanged.rawValue)
    
    guard let tap = CGEvent.tapCreate(
        tap: .cgSessionEventTap,
        place: .headInsertEventTap,
        options: .defaultTap,  // Can modify/block events
        eventsOfInterest: eventMask,
        callback: keyboardCallback,
        userInfo: Unmanaged.passUnretained(self).toOpaque()
    ) else {
        throw PermissionError.eventTapCreationFailed
    }
    
    // Add to run loop for event processing
    runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, tap, 0)
    CFRunLoopAddSource(CFRunLoopGetMain(), runLoopSource!, .commonModes)
    CGEvent.tapEnable(tap: tap, enable: true)
}

Event Blocking

When a cat is detected, the keyboard lock service blocks events by returning nil from the event callback, preventing them from reaching any application:

private func keyboardCallback(...) -> Unmanaged<CGEvent>? {
    // ESC key always allowed through for emergency unlock
    let escapeKeyCode: UInt16 = 53
    
    switch type {
    case .keyDown:
        monitor.handleKeyDown(keyCode)
        if monitor.shouldBlockEvent() && keyCode != escapeKeyCode {
            return nil  // Block the event
        }
    // ...
    }
    return Unmanaged.passUnretained(event)
}

Debouncing and Cooldown

To prevent false positives from brief accidental multi-key touches, CatPaws implements:

  1. Debounce period (200-500ms): A cat pattern must persist before triggering a lock
  2. Cooldown period (5-10 seconds): After manual unlock, detection pauses briefly to prevent immediate re-locking if the cat is still present

Architecture

CatPaws follows MVVM architecture with clear separation of concerns:

CatPaws/
├── App/              # App entry point and delegates
├── Services/
│   ├── KeyboardMonitor.swift      # CGEvent tap management
│   ├── CatDetectionService.swift  # Pattern analysis
│   ├── KeyboardLockService.swift  # Event blocking
│   ├── KeyboardAdjacencyMap.swift # Key position data
│   └── StatisticsService.swift    # Usage tracking
├── Models/
│   ├── DetectionEvent.swift       # Cat detection types
│   ├── LockState.swift            # Lock state management
│   └── KeyboardState.swift        # Current key state
├── ViewModels/                    # MVVM view models
└── Views/                         # SwiftUI views

Privacy by Design

CatPaws is designed with privacy as a core principle:

  • No keylogging: The app monitors key patterns and codes, not the actual characters being typed
  • No network access: Zero network connections; everything stays on your Mac
  • No telemetry: No analytics, crash reporting, or data collection
  • Open source: Full transparency—you can audit every line of code

System Requirements

  • macOS 14 (Sonoma) or later
  • Apple Silicon or Intel Mac
  • Input Monitoring and Accessibility permissions

Getting CatPaws

Download

Download the latest release from the GitHub Releases page.

  1. Download CatPaws-x.x.x.dmg
  2. Open the DMG and drag CatPaws to Applications
  3. Launch and grant Input Monitoring permission in System Settings

Build from Source

# Clone the repository
git clone https://github.com/TechPreacher/CatPaws.git
cd CatPaws

# Open in Xcode
open CatPaws/CatPaws.xcodeproj

# Or build from command line
xcodebuild build -scheme CatPaws -configuration Release

Contributing

CatPaws is open source under the MIT License, and contributions are welcome!

Ways to Contribute

  • Report bugs: Open an issue on GitHub
  • Suggest features: Share your ideas in GitHub Discussions
  • Submit code: Fork the repo, make changes, and open a Pull Request
  • Improve documentation: Help make the README and wiki better

Development Setup

# Clone and open
git clone https://github.com/TechPreacher/CatPaws.git
cd CatPaws
open CatPaws/CatPaws.xcodeproj

# Run tests
xcodebuild -scheme CatPaws -configuration Debug test

# Run SwiftLint
swiftlint

Project Guidelines

  • Swift 5.9+, following Apple's Swift API Design Guidelines
  • SwiftUI for UI components, AppKit only when SwiftUI lacks functionality
  • MVVM architecture with @Observable view models
  • Prefix UserDefaults keys with catpaws.

Conclusion

CatPaws demonstrates that with the right approach, you can solve a common problem elegantly. By understanding the physical difference between human typing and cat paw placement, we can build smart detection that works reliably without invasive monitoring.

If you've ever had a cat-generated email sent to your boss, CatPaws might just save your day—and your keyboard.


Links:

CatPaws is free and open source, built with SwiftUI and love for cats. 🐱