C.W.K.
Stream
Lesson 02 of 04 · published

RegisterEventHotKey 는 실제로 어떻게 도나

~13 min · carbon, register-event-hotkey, event-handler, modifiers

Level 0차가운 부싯돌
0 XP0/34 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"조합을 등록하고, 핸들러 하나를 설치하고, 모디파이어를 번역해 — 그리고 Carbon 이 이 파일을 넘어 새지 않게."

등록 호출

메커니즘엔 움직이는 부분이 셋 있어. 먼저, 조합을 등록해: RegisterEventHotKey 는 가상 키 코드, 모디파이어 마스크, 네가 만드는 EventHotKeyID(시그니처 + 작은 정수, 조합을 구분하려고), 이벤트를 라우팅할 타깃을 받고, 나중에 등록 해제하려고 네가 쥐는 레퍼런스를 반환해. 등록은 OSStatus 를 반환해 — noErr 이 아닌 결과가 충돌의 첫 신호고, 다음 레슨이 다뤄.

모든 조합에 핸들러 하나

둘째, kEventHotKeyPressed 에 Carbon 이벤트 핸들러를 하나만 설치해. 등록된 모든 조합이 그 핸들러 하나로 라우팅돼; 그 안에서 이벤트로부터 EventHotKeyID 를 도로 꺼내, 그 id 가 어느 매크로에 매핑되는지 찾고, 그 액션을 돌려. 단축키마다 핸들러를 설치하지 마 — 핸들러 하나를 설치하고 id 로 디스패치해. 그게 Carbon 표면을 작게 유지해: 설치 하나, 콜백 하나, id 에서 클로저로 가는 딕셔너리 하나.

Carbon 모디파이어 마스크는 Cocoa 모디파이어 플래그가 아냐. 이게 그 고전 버그야. NSEvent.ModifierFlags 와 Carbon 의 cmdKey / optionKey / controlKey / shiftKey 는 다른 비트 배치야. Cocoa 플래그 값을 RegisterEventHotKey 에 바로 넣으면, 조합이 조용히 엉뚱한 조합으로 등록돼 — Cmd+K 가 딴 키가 되거나, 아무것도 안 돼. 경계에서 한 모디파이어씩 명시적으로 번역해야 해.

등록, 해제, 재바인드

셋째, 레퍼런스를 쥐어. 매크로의 조합이 바뀌면, UnregisterEventHotKey 로 옛 레퍼런스를 해제하고 새 걸 등록해 — 매달린 등록을 절대 남기지 마, OS 가 네가 해제 잊은 조합을 계속 쏘니까. 이 전부를 타입 하나 — 핫키 센터 — 에 감싸, id-대-레퍼런스랑 id-대-액션 맵을 쥔. 모든 Carbon 이 그 안에 살고; 앱 나머지는 키 코드, 모디파이어 집합, 클로저를 건네고 Carbon 을 아예 import 안 해.

Code

HotkeyCenter — Carbon 을 만지는 유일한 파일·swift
import Carbon.HIToolbox

final class HotkeyCenter {
    private var refs: [UInt32: EventHotKeyRef] = [:]    // 우리 id -> OS 등록
    private var actions: [UInt32: () -> Void] = [:]     // 우리 id -> 돌릴 것

    init() {
        var spec = EventTypeSpec(eventClass: OSType(kEventClassKeyboard),
                                 eventKind: UInt32(kEventHotKeyPressed))
        // 모든 조합에 핸들러 하나; 그 안에서 id 로 디스패치.
        InstallEventHandler(GetEventDispatcherTarget(), { _, event, userData in
            var hkID = EventHotKeyID()
            GetEventParameter(event, EventParamName(kEventParamDirectObject),
                              EventParamType(typeEventHotKeyID), nil,
                              MemoryLayout<EventHotKeyID>.size, nil, &hkID)
            let center = Unmanaged<HotkeyCenter>
                .fromOpaque(userData!).takeUnretainedValue()
            center.actions[hkID.id]?()      // 이 조합이 매핑된 매크로 실행
            return noErr
        }, 1, &spec, Unmanaged.passUnretained(self).toOpaque(), nil)
    }

    func register(id: UInt32, keyCode: UInt32, carbonModifiers: UInt32,
                  action: @escaping () -> Void) {
        let hkID = EventHotKeyID(signature: OSType(0x464C4E54), id: id)  // 'FLNT'
        var ref: EventHotKeyRef?
        let status = RegisterEventHotKey(keyCode, carbonModifiers, hkID,
                                         GetEventDispatcherTarget(), 0, &ref)
        guard status == noErr, let ref else { return }   // noErr 아니면 => 충돌
        refs[id] = ref
        actions[id] = action
    }

    func unregister(id: UInt32) {
        if let ref = refs.removeValue(forKey: id) { UnregisterEventHotKey(ref) }
        actions[id] = nil    // 해제 후 조합이 계속 쏘게 절대 두지 마
    }
}
경계에서 Cocoa 모디파이어를 Carbon 마스크로 번역·swift
// 오후를 잡아먹는 버그: NSEvent 플래그를 바로 넣기.
// Carbon 마스크는 다른 비트야 — 한 번에 하나씩 번역.
func carbonModifiers(from flags: NSEvent.ModifierFlags) -> UInt32 {
    var m: UInt32 = 0
    if flags.contains(.command) { m |= UInt32(cmdKey) }
    if flags.contains(.option)  { m |= UInt32(optionKey) }
    if flags.contains(.control) { m |= UInt32(controlKey) }
    if flags.contains(.shift)   { m |= UInt32(shiftKey) }
    return m
}

External links

Exercise

사용자가 Cmd+Shift+K 를 배정한 순간부터 Cmd+Shift+L 로 바꾸는 순간까지, 매크로 하나의 단축키 수명을 스케치해. Carbon 이 관여하는 모든 단계를 이름 붙여: 모디파이어 번역, 등록, 핸들러 발화, 옛 조합 해제, 새 조합 등록. Cocoa-대-Carbon 모디파이어 불일치가 정확히 어디서 조용히 깨고, 잊은 해제가 어디서 유령 조합을 쏘게 남길까?
Hint
모디파이어 불일치는 등록에서 물어: 틀린 비트가 사용자가 고른 것과 다른 조합을 등록해서, 걔네 키는 아무것도 안 하고 다른 조합이 발화해. 유령 조합은 재바인드에서 나타나: 옛 레퍼런스를 해제 안 하고 새 조합을 등록하면, OS 가 옛 걸 계속 쏘아 — 그래서 해제-그다음-등록, 항상 그 순서로, 매크로의 안정적인 id 로 키잡아.

Progress

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

댓글 0

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

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