Low-level event-posting help needed.

Hi there,

I am working on a little application which processes cursor and graphics tablet data and adds some extra control to the output.

So far it makes use of...

if let eventTap = CGEvent.tapCreate(tap: .cgSessionEventTap, //.cghidEventTap place: .headInsertEventTap, options: .defaultTap, eventsOfInterest: eventMask, callback: handleTapEvent, userInfo: userInfo)

... to modify existing events.

The issue that in some cases arises (it's a globally working app) - that some other applications pull and process pointer-data aside the event stream and therefor create conflicting values.

Would creating and posting events to a 'virtual pointing device' on a lower system level (kext) help?

Let's discuss. BR, E

Answered by Eilert_Janssen in 905237022

Update — root cause found (macOS 26.2, disassembly + repro):

The pressure is lost inside SkyLight's HID→NX translation, not in the descriptor and not in the DriverKit driver.

  1. AppleUserHIDEventDriver (HIDDriverKit IOUserHIDEventDriver::parseDigitizerElement) calibrates Tip Pressure to 0…1 with saturation at Logical Min/Max (setCalibration(0, 1, logMin, logMax)), and publishes DigitizerTipThreshold = 75 under HIDEventServiceProperties if absent. So the IOHIDEvent's kIOHIDEventFieldDigitizerPressure is a normalized float 0.0…1.0. (This also explains the "click at 75 % pressure" behaviour — documented in IOHIDEventServiceKeys.h.)

  2. SkyLight EventTranslator::hidevent_to_nxevent_translation fills the 16-bit NXTabletPointData.pressure like this:

add  w1, w24, #0x9          ; 0xB0001 + 9 = kIOHIDEventFieldDigitizerPressure (0xB000A)
mov  x0, x28
bl   _IOHIDEventGetIntegerValue
strh w0, [sp, #0x166]       ; NXEventExt.tablet.pressure (offset verified via offsetof against IOLLEvent.h)

IOHIDEventGetIntegerValue truncates the 0…1 float → the tablet event carries pressure 0 for every value < 1.0 and 1 for exactly 1.0, i.e. NSEvent.pressure is at most 1/65535. Repro without any device (IOHIDEventCreateDigitizerEvent(..., tipPressure: 0.999, ...)GetIntegerValue = 0, GetDoubleValue = 0.999). The tip-threshold comparison right next to it correctly uses GetDoubleValue() * 100 >= threshold — only the tablet field is wrong. The same pattern applies to tablet.tilt (trunc(GetFloatValue) into int16) and rotation.

That matches the old kernel path, which scaled pressure to EV_MAXPRESSURE (0…65535) before handing it to the NX layer (IOHIDEventService::dispatchTabletPointerEvent, active up to IOHIDFamily-701 / 10.11, commented out since 870 / 10.12) — the translator still expects that integer domain while the DriverKit path delivers normalized floats. It also matches the 2016–2019 forum reports ("works on El Capitan, not on 10.12+").

So: no report descriptor can make a generic HID pen deliver pressure to apps on current macOS; vendor drivers work only because they post NX tablet events themselves. Filed as FB24785043 with disassembly offsets and the repro program. A one-line fix (GetDoubleValue() * 65535) would make HIDVirtualDevice pens fully usable for tablet-style apps.

Accepted Answer

Update — root cause found (macOS 26.2, disassembly + repro):

The pressure is lost inside SkyLight's HID→NX translation, not in the descriptor and not in the DriverKit driver.

  1. AppleUserHIDEventDriver (HIDDriverKit IOUserHIDEventDriver::parseDigitizerElement) calibrates Tip Pressure to 0…1 with saturation at Logical Min/Max (setCalibration(0, 1, logMin, logMax)), and publishes DigitizerTipThreshold = 75 under HIDEventServiceProperties if absent. So the IOHIDEvent's kIOHIDEventFieldDigitizerPressure is a normalized float 0.0…1.0. (This also explains the "click at 75 % pressure" behaviour — documented in IOHIDEventServiceKeys.h.)

  2. SkyLight EventTranslator::hidevent_to_nxevent_translation fills the 16-bit NXTabletPointData.pressure like this:

add  w1, w24, #0x9          ; 0xB0001 + 9 = kIOHIDEventFieldDigitizerPressure (0xB000A)
mov  x0, x28
bl   _IOHIDEventGetIntegerValue
strh w0, [sp, #0x166]       ; NXEventExt.tablet.pressure (offset verified via offsetof against IOLLEvent.h)

IOHIDEventGetIntegerValue truncates the 0…1 float → the tablet event carries pressure 0 for every value < 1.0 and 1 for exactly 1.0, i.e. NSEvent.pressure is at most 1/65535. Repro without any device (IOHIDEventCreateDigitizerEvent(..., tipPressure: 0.999, ...)GetIntegerValue = 0, GetDoubleValue = 0.999). The tip-threshold comparison right next to it correctly uses GetDoubleValue() * 100 >= threshold — only the tablet field is wrong. The same pattern applies to tablet.tilt (trunc(GetFloatValue) into int16) and rotation.

That matches the old kernel path, which scaled pressure to EV_MAXPRESSURE (0…65535) before handing it to the NX layer (IOHIDEventService::dispatchTabletPointerEvent, active up to IOHIDFamily-701 / 10.11, commented out since 870 / 10.12) — the translator still expects that integer domain while the DriverKit path delivers normalized floats. It also matches the 2016–2019 forum reports ("works on El Capitan, not on 10.12+").

So: no report descriptor can make a generic HID pen deliver pressure to apps on current macOS; vendor drivers work only because they post NX tablet events themselves. Filed as FB24785043 with disassembly offsets and the repro program. A one-line fix (GetDoubleValue() * 65535) would make HIDVirtualDevice pens fully usable for tablet-style apps.

Low-level event-posting help needed.
 
 
Q