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:
.defaultTapcho phép sửa và nuốt sự kiện;.listenOnlychỉ quan sát.
Ba cách xử lý một sự kiện
| Cách | Hệ quả |
|---|---|
| Cho qua | Trả lại sự kiện nguyên vẹn |
| Sửa | Ghi đè thuộc tính, ví dụ event.location, rồi trả lại |
| Nuốt | Trả 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.tapCreatetrảnilnếu chưa được cấp. Kiểm tra bằngAXIsProcessTrusted(). - 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.locationvàCGDisplayBoundscù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à+Yhướng xuống — ngược vớiNSScreencủ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ạiCGEvent.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
selfqua tham sốuserInfobằngUnmanaged.passUnretained. - Trong Swift 6,
CGEventkhông phảiSendable. 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ênCGEventở phía ngoài.