본문 바로가기
C.W.K.
Stream
Lesson 04 of 05 · published

모두의 키보드에 톨게이트를 놓지 마

~11 min · hotkey, event-tap, permissions, flagschanged

Level 0불씨 없음
0 XP0/36 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"키를 삼키는 이벤트 탭은 이 기계가 만드는 모든 키 입력의 톨게이트야. 그 일을 정말 맡고 싶은지 아주 확실히 해."

요구사항

Firekeeper 의 기본 활성화는 맨 Fn 홀드 야. 조합키 없이, 안 쓰는 키 하나 누르고 말하는 거. 인터랙션은 사랑스러운데 구현은 진짜 어색해. 맨 모디파이어는 평범한 단축키가 아니거든. 평범한 단축키 API 엔 'Fn 눌림' 이벤트라는 게 없어. 모디파이어는 플래그가 바뀌는 걸로 자기를 알리니까, 누르고 있는 상태를 거기서 직접 재구성해야 해.

넘어가기 쉬운 틀린 도구

뻔한 답은 CGEventTap 이야. 시스템 전역 이벤트 스트림 위에 앉아서 모든 키를 보고, 내가 가져가겠다는 건 삼켜. 되긴 돼. 그리고 그 순간 네 앱이 이 기계가 만드는 모든 키 입력의 톨게이트가 돼. 탭이 잘못 굴면 네 앱이 깨지는 게 아니야. 어디서든 타이핑 이 깨져. 가정이 아니고, 경쟁자의 삼키는 탭이 유저들 스페이스바를 먹은 게 정확히 그거였어. 위험은 그렇다 치고, 탭은 Input Monitoring 권한까지 필요해. 삽입 때문에 이미 받아 둔 접근성 허가 위에 무서운 프롬프트를 하나 더 띄우는 거지.

CGEventTap (거부됨):          flagsChanged + keyState (선택됨):
  모든 키 입력에 앉음           모디파이어 플래그 변화만 관찰
  시스템 전역 억제 가능         아무 키도 못 삼킴
  Input Monitoring 필요         이미 가진 접근성 권한만 씀
  경쟁자 게 스페이스바 먹음     가로채지 않는 걸 못 먹음

고른 길

Firekeeper 는 대신 NSEvent flagsChanged 모니터를 봐. 그리고 플래그만으로는 못 가리는 경우를 CGEventSource.keyState 로 구분해. 특히 쌍둥이 모디파이어가, 그러니까 반대쪽 Command 키 같은 게 아직 눌려 있을 때 누름과 놓음을 갈라내는 일. 앱이 삽입 때문에 이미 받아 둔 접근성 권한만으로 돌아가고, 새 권한을 하나도 안 더하고, 애초에 스트림을 안 가로채니까 구조적으로 키 입력을 삼킬 수가 없어. 같은 인터랙션인데 폭발 반경은 조각이야.

가로채기보다 관찰을 골라. 시스템 이벤트를 알아야 할 때, 보고만 있는 게 길목에 서는 것보다 훨씬 안전해. 잘못 구는 관찰자는 틀린 값을 로그에 남길 뿐이야. 잘못 구는 가로채기는 모든 앱한테 시스템을 망가뜨리고. 이벤트를 진짜로 소비해야 할 때만 가로채기를 집어. 눌러서 말하기 키한텐 그럴 이유가 없어.
Fn 키는 🌐 키 노릇도 같이 해. 요즘 macOS 에선 Fn 키가 Globe 키이기도 해서, 맨 Fn 홀드가 받아쓰기 위에 시스템 이모지나 입력 소스 피커를 띄울 수 있어. 코드로는 못 고쳐. 유저가 시스템 설정에서 '🌐 키를 누를 때: 아무 동작 안 함' 을 골라야 해. 그래서 모든 바인딩을 바꿀 수 있게 만들었고 Setup 패널이 이 힌트를 띄우는 거야. OS 설정에 기대는 인터랙션이라면 그 설정을 유저한테 알려 줘야 하니까.

Code

모디파이어 플래그는 보기만, 스트림은 절대 안 가로챔·swift
// 플래그 변화를 봄 — 관찰하지, 절대 소비 안 함.
NSEvent.addGlobalMonitorForEvents(matching: [.flagsChanged]) { event in
    let fnHeld = event.modifierFlags.contains(.function)

    // flagsChanged 만으론 쌍둥이 모디파이어가 아직 눌렸을 때
    // 누름과 놓음을 늘 구별 못 함 — keyState 가 구분해줌.
    let physicallyDown = CGEventSource.keyState(.combinedSessionState,
                                                key: CGKeyCode(kVK_Function))
    switch (fnHeld, physicallyDown) {
    case (true, true):   controller.start(mode: .dictate)
    case (false, false): Task { await controller.stop() }
    default: break
    }
}
// CGEventTap 없음. 가로채지 않는 키를 삼킬 수 없음.

External links

Exercise

앱이 뭔가를 시스템 전역으로 알아야 하는 경우를 찾아봐. 키 입력이든 클립보드든 창 변화든. 그게 관찰이 필요한 일인지 가로채기가 필요한 일인지 물어봐. 각 설계에서 버그의 폭발 반경이 뭐고, 가로채기를 고르면 어떤 권한을 추가로 치러야 해?
Hint
관찰에서 제일 나쁜 버그는 네 앱이 이벤트를 놓치거나 잘못 읽는 거야. 피해가 네 앱 안에 갇혀 있지. 가로채기에서 제일 나쁜 버그는 OS 전체가 이벤트를 잃는 거고. 피해가 남들 타이핑이야. 게다가 가로채기는 보통 더 넓고 무서운 권한도 요구해. 그러니 두 번 값을 치르는 셈이야. 위험이 더 크고, 설치할 때 마찰도 더 크고.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.