macoskeyboarddrivermouseiokit

How To Remove a Property From an IORegistryEntry (From User Space)


I'm trying to remove a key-value-pair from the property table of an IORegistryEntry, but I just can't figure out how. I can use IORegistryEntrySetCFProperty() or IORegistryEntrySetCFProperties() to either add or edit key-value-pairs – but I cannot figure out a way to remove existing pairs.


Background info

The IORegistryEntry that I'm trying to edit has the class AppleUserHIDEventDriver. For every external keyboard or mouse you attach, an instance of AppleUserHIDEventDriver seems to appear in the registry. My macOS version is Ventura 13.3.1. I'm trying to do this from a program running in user space, not kernel space. If you need a minimal working example, let me know.

When I use IORegistryEntrySetCFProperty() or IORegistryEntrySetCFProperties() to set values on the IORegistryEntry, the values are actually not set in the top level of the property dictionary of the IORegistryEntry but instead they are set inside a subdictionary with the key HIDEventServiceProperties. I'm not sure why this happens, but it's exactly what I want in my case, so it's not a problem.


Things I tried / thought about

I tried extracting the whole dictionary using IORegistryEntryCreateCFProperties(), then remove the unwanted values and then write the whole dictionary back using IORegistryEntrySetCFProperties(). This works to change values, but the removed values are just ignored.

I managed to obtain a an HID Event Service Client reference instance (IOHIDServiceClientRef) to the same driver. It can also be used to add or edit key-value-pairs inside the HIDEventServiceProperties subdict. You can do this using the method IOHIDServiceClientSetProperty(). But I couldn't find a way to remove values using IOHIDServiceClientRef, so far, either.

I thought about using IOConnect (instead of IORegistryEntry) and it's dedicated methods for setting properties, but I don't think it would make a difference.

In the Apple docs there is IORegistryEntry::removeProperty() but it's a C++ method and can only be used from kernel space as far as I understand. So I haven't tried to use it, yet.

Maybe I could try to set the values on some other IORegistryEntry that is adjacent (a parent, child, grandchild, etc.) to the AppleUserHIDEventDriver instance which I've been working with so far. Not sure if that makes sense. I could also try to use (user space) IOHIDDeviceRef which is an interface to talk to one of those adjacent IORegistryEntries as far as I understand. IOHIDDeviceRef internally uses some COM interfaces like IOHIDDeviceDeviceInterface to commicate with the IORegistryEntry. Maybe those interface provide some methods to remove the properties.


Update: Higher-level problem

What I'm actually trying to do is to customize the pointer acceleration algorithm for external mice. Preferably without writing my own kernel level/DriverKit driver but instead by parametrizing Apple's driver from user space. The pointer acceleration algorithm is implemented inside the open-source IOHIDPointerScrollFilter.cpp (You can also find an implementation in IOHIPointing.cpp but I think it's deprecated and unused.) Apples code contains two separate mechanisms for specifying the mathematical curve mapping the physical speed of the mouse to the on-screen speed of the mouse pointer. The first mechanism is the parametric curve and the second mechanism is the table-based curve.

Parametric curves are basically simple 4th degree polynomial curves, while the table-based curves are basically specified through a number of points which are then connected with straight line segments to form a curve. (It's a bit more complicated in reality but that's irrelevant here). The parametric curves have the advantage of being completely smooth (they have a continuous derivative) and being - I believe – more efficient than the table-based curves. But the table-based curves offer much more freedom when it comes to the shape of the curve. Their derivatives are discontinuous but you can specify the curve with thousands of points making the derivative de-facto continuous and there is no discernible performance difference on CPU load (at least on my M1 MacBook).

So in order to freely customize the pointer acceleration curves I would like to first make the Apple Driver use the table-based curves (instead of the parametric curves) and then customize the points defining the table-based curve to my own liking.

The parameters for the parametric curves and the table-based curves can be found inside the HIDEventServiceProperties subdict contained inside the property dict of the IORegistryEntry of class AppleUserHIDEventDriver which (as mentioned above) shows up for every external mouse you connect.

You can find the params for the parametric curve under the key HIDAccelCurves (aka kHIDAccelParametricCurvesKey) and you can find the params for the table-based curve under HIDPointerAccelerationTable (aka kIOHIDPointerAccelerationTableKey).

In order to customize the acceleration curve of your mouse, you need to first change the curve-defining parameters mentioned above, then, for the changes to actually be loaded by the driver, you need to change another value. kIOHIDPointerAccelerationTypeKey has been working for me so far. As far as I understand it can be any of the "cachedProperties" defined in IOHIDPointerScrollFilter.cpp_cachedPropertyList. When you look at IOHIDPointerScrollFilter.cppsetPropertyForClient it seems that the acceleration algorithm is only re-initialized when one of those cached properties changes. (It is re-initialized by calling IOHIDPointerScrollFilter.cppsetupPointerAcceleration())

Now this was all working fine under macOS Monterey, but the big problem that I'm having is that I don't know how to activate the table-based curves under macOS Ventura.

That's because it seems that as soon as any value is present for the HIDAccelCurves key, the Apple driver attempts to use parametric curves instead of table-based curves. Pre-Ventura, the HIDAccelCurves key didn't seem to be present for third party mice by default, but under Ventura HIDAccelCurves is present by default.

Sidenote: Strangely the HIDAccelCurves key doesn't seem to be present even under Ventura 13.3.1 when you start the computer without any mice attached, and only attach your mouse after the computer finished starting up. But if you start the computer with your mouse attached, then the HIDAccelCurves key seems to always be present, even after detaching and re-attaching the mouse – not sure what's going on here – haven't investigated too much. I think I also saw a discrepancy there after booting with a mouse attached, where the eventService (IOHIDServiceClientRef) seems to have the HIDAccelCurves key but the corresponding IORegistryEntrys HIDEventServiceProperties subdict didn't have the HIDAccelCurves key. Usually, these are always in sync. But I'm not sure if I really saw this, I might just have been confused or misremembering. I should test this more.

So that's my big problem: I'm trying to remove the HIDAccelCurves key from the IORegistryEntry of class AppleUserHIDEventDriver so that the code inside IOHIDPointerScrollFilter.cppsetupPointerAcceleration() uses the table-based curve instead of the parametric curve.

But I just can't figure out how! I can set HIDAccelCurves to some bullshit, then the WindowServer crashes after I make the driver re-initialize the acceleration algorithm (as mentioned, I get it to re-initialize by modifying kIOHIDPointerAccelerationTypeKey), but I just can't completely remove the value for the key HIDAccelCurves once it has been set – which would be necessary for the driver to use the table-based curves.

Sidenote: It feels like I could find some security exploit using this which would force Apple to improve the code so I can do my pointer acceleration. That would be such an absurd solution haha.

It might be worth investigating why HIDAccelCurves seems to be missing even under Ventura when you start without a mouse attached, but it doesn't seem like it would lead to a robust solution.

Another idea: IOHIDPointerScrollFilter.cpp also supports Microsoft COM interfaces. Specifically IOHIDServiceFilterPlugInInterface which is unfortunately private but shouldn't be hard to reverse-engineer. If we can get a reference to the instance implemented by IOHIDPointerScrollFilter we should be able to use the reverse engineered COM interface to set properties on it from user space. Could the IORegistryEntry of class AppleUserHIDEventDriver be the instance implementing IOHIDPointerScrollFilter? Not sure how this whole "filter" architecture works. Maybe we could try using IOCreatePlugInInterfaceForService() on the IORegistryEntry, but I really don't know how all this COM interface stuff works and there aren't many examples on the internet, so I haven't tried, yet.


Solution

  • General answer

    So, the first thing you need to know about IORegistryEntrySetCFProperties is that the only thing you can know for certain is that it calls setProperties on the corresponding kernel object (IORegistryEntry subclass). By default, this does nothing at all. (IORegistryEntrySetCFProperty in turn is just a wrapper around IORegistryEntrySetCFProperties.)

    So, in the general case, you can neither set, update, nor remove properties on IORegistryEntry objects from user space.

    Attempt at an answer for your specific subclass

    The above is of course technically a 100% correct answer, but not terribly helpful one. Clearly, your object has more than a no-op implementation of setProperties.

    For setting or removing properties on objects of specific classes, we need to look at the behaviour of their overridden setProperties implementation.

    The I/O Registry objects named AppleUserHIDEventDriver appear to actually be instances of the (kernel) class AppleUserHIDEventService:

    $ ioreg -irn AppleUserHIDEventDriver
    +-o AppleUserHIDEventDriver  <class IORegistryEntry:IOService:IOHIDEventService:IOHIDEventDriver:AppleUserHIDEventService, id 0x100000c1f, registered, matched, active, busy 0 (0 ms), retain 12>
    […]
    # see here: ->----------->------------>----------------------------------------------------------^^^^^^^^^^^^^^^^^^^^^^^^
    

    Luckily, the source code for this class is open, and so we can look at its setProperties implementation. Unfortunately however, this class is just a kernel<->DriverKit glue class, so all it ultimately does is call

    SetUserProperties(propDict, (uint64_t)&caller, _setPropertiesAction);
    

    which in turn calls SetProperties in the DriverKit extension implementing the IOUserClass, which is AppleUserHIDEventDriver, and which does not appear to be open source, so we're at an impasse.

    OK what next?

    This seems a good opportunity to take a step back and think about the higher level problem you're ultimately trying to solve - right now, we can't see the forest for the trees. You haven't elaborated on your ultimate goal, but it greatly influences what steps are most sensible to take next. For example, if you're (working for) a device vendor, and you are trying to change the behaviour of your HID device on macOS, you may want to take a completely different approach and create a DriverKit Extension for your device, in effect replacing AppleUserHIDEventDriver entirely with your own logic.