CGEventTap

Định nghĩa

CGEventTap là cơ chế của macOS cho phép một tiến trình chèn mình vào dòng sự kiện đầu vào (chuột, bàn phím) trước khi ứng dụng đích nhận được, để quan sát, sửa đổi hoặc nuốt sự kiện đó. Đây là đường duy nhất còn lại để thay đổi những hành vi hệ thống mà Apple không mở tuỳ chọn — khi không có key defaults nào tác động được, vẫn có thể can thiệp vào dữ liệu đầu vào của quyết định đó.

Biến thể

  • Vị trí chèn: .cgSessionEventTap (mức phiên đăng nhập, thường dùng) hoặc .cghidEventTap (sớm nhất, ngay sau tầng HID).
  • Thứ tự: .headInsertEventTap đặt tap lên đầu hàng đợi, .tailAppendEventTap đặt xuống cuối.
  • Quyền hạn: .defaultTap cho phép sửa và nuốt sự kiện; .listenOnly chỉ quan sát.

Ba cách xử lý một sự kiện

CáchHệ quả
Cho quaTrả lại sự kiện nguyên vẹn
SửaGhi đè thuộc tính, ví dụ event.location, rồi trả lại
NuốtTrả nil; sự kiện biến mất hoàn toàn

Với con trỏ chuột, sửa thường tốt hơn nuốt: nuốt một sự kiện mouseMoved sẽ chặn luôn cả chuyển động ngang, còn sửa toạ độ thì chỉ ràng buộc đúng chiều mình muốn.

Nguồn chính

Khái niệm liên quan

Ghi chú

  • Bắt buộc có quyền Accessibility. CGEvent.tapCreate trả nil nếu chưa được cấp. Kiểm tra bằng AXIsProcessTrusted().
  • Quyền Accessibility gắn với chữ ký mã, không chỉ đường dẫn. App ký ad-hoc đổi cdhash mỗi lần dựng lại nên phải cấp quyền lại sau mỗi lần build.
  • Hệ toạ độ: CGEvent.locationCGDisplayBounds cùng dùng toạ độ toàn cục với gốc ở góc trên-trái màn hình chính và +Y hướng xuống — ngược với NSScreen của AppKit (gốc dưới-trái). Trộn hai hệ này là lỗi phổ biến nhất; nên tách phần hình học thành hàm thuần để test được.
  • Hệ thống tự tắt tap nếu callback xử lý quá lâu (.tapDisabledByTimeout) hoặc khi người dùng thu quyền (.tapDisabledByUserInput). Callback phải bắt hai loại sự kiện này và gọi lại CGEvent.tapEnable để bật lại, nếu không app sẽ âm thầm ngừng hoạt động.
  • Callback là con trỏ hàm C, không capture được context — truyền self qua tham số userInfo bằng Unmanaged.passUnretained.
  • Trong Swift 6, CGEvent không phải Sendable. Muốn chạm state của main actor trong callback thì chỉ nên chuyển các giá trị Sendable (như CGPoint) qua ranh giới actor, rồi mới áp kết quả lên CGEvent ở phía ngoài.