Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to reset HIDIdleTime on macOS 10.14

Tags:

xcode

macos

swift

For the past couple of days I've been trying to write an application that would reset the IORegistry > IOHIDSystem > HIDIdleTime entry. The end goal would be to prevent other applications that read this value from marking the user as idle (it's not only about power management or preventing sleep). Assume that sandboxing is disabled and the application has all necessary permissions (such as accessibility access).

Here are my attempts at doing this (unsuccessful so far):

Attempt 1 - move the mouse cursor to simulate activity:

Variant 1:

let mouseCursorPosition = CGPoint(x: Int.random(in: 0...500), y: Int.random(in: 0...500))
CGWarpMouseCursorPosition(mouseCursorPosition)

Variant 2:

CGDisplayMoveCursorToPoint(CGMainDisplayID(), mouseCursorPosition)

Variant 3 (using CGEvent by itself or together with one of the 2 variants above):

let moveEvent = CGEvent(mouseEventSource: nil, mouseType: 
CGEventType.mouseMoved, mouseCursorPosition: mouseCursorPosition, 
mouseButton: CGMouseButton.left)
moveEvent?.post(tap: CGEventTapLocation.cghidEventTap)

Variant 4 (using IOHIDSetMouseLocation / IOHIDPostEvent):

func moveCursor() {
    let service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOHIDSystem"))
    if (service == 0) { return }

    var connect:io_connect_t = 0

    let result = IOServiceOpen(service, mach_task_self_, UInt32(kIOHIDParamConnectType), &connect)
    IOObjectRelease(service)

    if (result == kIOReturnSuccess) {
        let cursorX = Int16.random(in: 0...100)
        let cursorY = Int16.random(in: 0...100)

        IOHIDSetMouseLocation(connect, Int32(cursorX), Int32(cursorY))

        let cursorLocation:IOGPoint = IOGPoint(x: cursorX, y: cursorY)

        var event:NXEventData = NXEventData()
        IOHIDPostEvent(connect, UInt32(NX_MOUSEMOVED), cursorLocation, &event, 0, 0, 0)
    }
}

NOTE: I've later learned that starting with macOS 10.12, IOHIDPostEvent doesn't reset HIDIdleTime (source: https://github.com/tekezo/Karabiner-Elements/issues/385). Also tried simulating keypresses without success.

Attempt 2 - overwrite the value directly in the IORegistry

func overwriteValue() -> Bool {
    var iterator: io_iterator_t = 0
    defer { IOObjectRelease(iterator) }
    guard IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceMatching("IOHIDSystem"), &iterator) == kIOReturnSuccess else { return false }

    let entry: io_registry_entry_t = IOIteratorNext(iterator)
    defer { IOObjectRelease(entry) }
    guard entry != 0 else { return false }

    var value:NSInteger = 0;
    var convertedValue:CFNumber = CFNumberCreate(kCFAllocatorDefault, CFNumberType.nsIntegerType, &value);
    let result = IORegistryEntrySetCFProperty(entry, "HIDIdleTime" as CFString, convertedValue)

    if (result != kIOReturnSuccess) { return false }

    return true
}

While this seems to work (the function above returns true), the value is then overwritten by the system, which keeps track of the actual idle time in memory. Got a bit of insight into this from the source code release by Apple for IOHIDSystem here. Currently using this script to easily monitor system idle time and test solutions.

Any suggestions are greatly appreciated. If at all possible, I'm trying to avoid writing my own virtual driver (although I'm open to hooking into an existing one and simulating events if at all possible).

like image 214
Sebastian Avatar asked Oct 16 '22 11:10

Sebastian


2 Answers

The thing is that the registry property isn't a normal property, but is generated on the fly every time properties are queried (see _idleTimeSerializerCallback in the source).
Long story short, you need to force lastUndimEvent to be reset, which you can do with external method 6 of an IOHIDParamUserClient.

I don't speak Swift, but here is some C code that does precisely that:

// clang -o t t.c -Wall -O3 -framework CoreFoundation -framework IOKit
#include <stdio.h>
#include <stdint.h>
#include <mach/mach.h>
#include <CoreFoundation/CoreFoundation.h>

extern const mach_port_t kIOMasterPortDefault;

typedef mach_port_t io_object_t;
typedef io_object_t io_service_t;
typedef io_object_t io_connect_t;

kern_return_t IOObjectRelease(io_object_t object);
CFMutableDictionaryRef IOServiceMatching(const char *name) CF_RETURNS_RETAINED;
io_service_t IOServiceGetMatchingService(mach_port_t master, CFDictionaryRef matching CF_RELEASES_ARGUMENT);
kern_return_t IOServiceOpen(io_service_t service, task_t task, uint32_t type, io_connect_t *client);
kern_return_t IOServiceClose(io_connect_t client);
kern_return_t IOConnectCallScalarMethod(io_connect_t client, uint32_t selector, const uint64_t *in, uint32_t inCnt, uint64_t *out, uint32_t *outCnt);

const uint32_t kIOHIDParamConnectType             = 1;
const uint32_t kIOHIDActivityUserIdle             = 3;
const uint32_t kIOHIDActivityReport               = 0;
const uint32_t kIOHIDParam_extSetStateForSelector = 6;

#define LOG(str, args...) do { fprintf(stderr, str "\n", ##args); } while(0)

int hid_reset(void)
{
    int retval = -1;
    kern_return_t ret = 0;
    io_service_t service = MACH_PORT_NULL;
    io_connect_t client = MACH_PORT_NULL;

    service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOHIDSystem"));
    LOG("service: %x", service);
    if(!MACH_PORT_VALID(service)) goto out;

    ret = IOServiceOpen(service, mach_task_self(), kIOHIDParamConnectType, &client);
    LOG("client: %x, %s", client, mach_error_string(ret));
    if(ret != KERN_SUCCESS || !MACH_PORT_VALID(client)) goto out;

    uint64_t in[] = { kIOHIDActivityUserIdle, kIOHIDActivityReport };
    ret = IOConnectCallScalarMethod(client, kIOHIDParam_extSetStateForSelector, in, 2, NULL, NULL);
    LOG("extSetStateForSelector: %s", mach_error_string(ret));
    if(ret != KERN_SUCCESS) goto out;

    retval = 0;

out:;
    if(MACH_PORT_VALID(client)) IOServiceClose(client);
    if(MACH_PORT_VALID(service)) IOObjectRelease(service);
    return retval;
}

int main(void)
{
    return hid_reset();
}

It works for me on High Sierra as non-root, haven't tested it elsewhere. I do run a non-standard system configuration though, so if you get an error saying (iokit/common) not permitted on the external method, it's likely you're hitting mac_iokit_check_hid_control and might need additional entitlements, accessibility clearance, or something like that.

like image 104
Siguza Avatar answered Oct 23 '22 13:10

Siguza


For Wine, we've discovered that we needed to use two different functions to get the full effects we were looking for. One is deprecated, but I could find no non-deprecated replacement. Maybe one of them will be enough for your purposes:

    /* This wakes from display sleep, but doesn't affect the screen saver. */
    static IOPMAssertionID assertion;
    IOPMAssertionDeclareUserActivity(CFSTR("Wine user input"), kIOPMUserActiveLocal, &assertion);

    /* This prevents the screen saver, but doesn't wake from display sleep. */
    /* It's deprecated, but there's no better alternative. */
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
    UpdateSystemActivity(UsrActivity);
#pragma clang diagnostic pop
like image 1
Ken Thomases Avatar answered Oct 23 '22 11:10

Ken Thomases