Force Click is detected through a CGEventTap, but the event type used by the tap is not the same as the AppKit event type used for the final judgment.
The current rule is:
Subscribe to CGEvent raw type 29
-> convert CGEvent to NSEvent
-> process only NSEvent.EventType.pressure
-> treat stage == 2 as Force Click active
In the event tap, Force Click related pressure events arrive as CGEventType(rawValue: 29).
After converting the event:
let nsEvent = NSEvent(cgEvent: cgEvent)the same event can become:
nsEvent.type == .pressure
nsEvent.type.rawValue == 34This means there are two different raw values involved:
CGEvent raw 29
Event tap input type. This is what must be subscribed to.
NSEvent raw 34
AppKit pressure event type after NSEvent(cgEvent:) conversion.
Subscribing to CGEvent raw 34 does not work because that is not the event tap input type observed for pressure events.
The app only handles converted NSEvent.EventType.pressure events.
guard let event = NSEvent(cgEvent: cgEvent), event.type == .pressure else {
return
}
let isForceClickStage = event.stage == 2The state transition is:
stage != 2 -> stage == 2
Force Click began.
Press Cmd+Shift.
stage == 2 -> stage != 2
Force Click pressure was released back toward normal click pressure.
Send Cmd+Shift+leftMouseUp.
Release Cmd+Shift.
leftMouseUp from the system is not used as the Force Click release trigger. It happens later, when the finger is actually lifted, and is too late for the intended behavior.
Representative sequence from the working analysis:
NSEvent pressure stage: 1
NSEvent pressure stage: 2
-> Force Click began
NSEvent pressure stage: 2
NSEvent pressure stage: 1
-> Force Click ended / pressure weakened
leftMouseUp
-> finger lifted, later than the desired release point
The stable judgment should be based on NSEvent.EventType.pressure and event.stage, not on interpreting all raw CGEvent values directly.
CGEvent raw 29 is only the subscription entry point needed to receive the events. The actual semantic check remains:
event.type == .pressure
event.stage == 2