启用 Closed-Display 模式 w/o 满足 Apple 的要求

Enabling Closed-Display Mode w/o Meeting Apple's Requirements

编辑: 在做出一些重要的新发现并且该问题还没有任何答案之后,我对这个问题进行了大量编辑。

Historically/AFAIK,在 closed-display 模式下让你的 Mac 保持清醒并且不以 root 身份遇到 Apple's requirements, has only been possible with a kernel extension (kext), or a command 运行。但是最近,我发现必须有另一种方法。我真的可以使用一些帮助来弄清楚如何让它在(100% 免费,无 IAP)沙盒 Mac App Store (MAS) 兼容应用程序中使用。

我已经确认其他一些 MAS 应用程序能够执行此操作,看起来它们可能正在将 YES 写入名为 clamshellSleepDisabled 的密钥。或者可能有一些其他的技巧导致键值被设置为 YES?我在 IOPMrootDomain.cpp:

中找到了函数
void IOPMrootDomain::setDisableClamShellSleep( bool val )
{
    if (gIOPMWorkLoop->inGate() == false) {

       gIOPMWorkLoop->runAction(
               OSMemberFunctionCast(IOWorkLoop::Action, this, &IOPMrootDomain::setDisableClamShellSleep),
               (OSObject *)this,
               (void *)val);

       return;
    }
    else {
       DLOG("setDisableClamShellSleep(%x)\n", (uint32_t) val);
       if ( clamshellSleepDisabled != val )
       {
           clamshellSleepDisabled = val;
           // If clamshellSleepDisabled is reset to 0, reevaluate if
           // system need to go to sleep due to clamshell state
           if ( !clamshellSleepDisabled && clamshellClosed)
              handlePowerNotification(kLocalEvalClamshellCommand);
       }
    }
}

我想试一试,看看这是否足够,但我真的不知道如何调用这个函数。它肯定不是 IOPMrootDomain 文档的一部分,而且我似乎无法在 IOPMrootDomain 文档中找到任何有用的函数示例代码,例如 setAggressivenesssetPMAssertionLevel。根据 Console,这里有一些关于幕后发生的事情的证据:

我有一些使用 IOMProotDomain 的经验,通过为另一个项目改编一些 ControlPlane 的源代码,但我不知道如何开始这个。任何帮助将不胜感激。谢谢!

编辑: 使用@pmdj 的contribution/answer,这已经解决了!

完整示例项目: https://github.com/x74353/CDMManager

结果令人惊讶 simple/straightforward:

1.导入 header:

#import <IOKit/pwr_mgt/IOPMLib.h>

2。在您的实现文件中添加此函数:

IOReturn RootDomain_SetDisableClamShellSleep (io_connect_t root_domain_connection, bool disable)
{
    uint32_t num_outputs = 0;
    uint32_t input_count = 1;
    uint64_t input[input_count];
    input[0] = (uint64_t) { disable ? 1 : 0 };

    return IOConnectCallScalarMethod(root_domain_connection, kPMSetClamshellSleepState, input, input_count, NULL, &num_outputs);
}

3。使用以下代码从您实现中的其他地方调用上述函数:

io_connect_t connection = IO_OBJECT_NULL;
io_service_t pmRootDomain =  IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPMrootDomain"));

IOServiceOpen (pmRootDomain, current_task(), 0, &connection);

// 'enable' is a bool you should assign a YES or NO value to prior to making this call
RootDomain_SetDisableClamShellSleep(connection, enable);

IOServiceClose(connection);

我对 PM 根域没有个人经验,但我对 IOKit 有丰富的经验,所以这里是:

  • 您希望 IOPMrootDomain::setDisableClamShellSleep() 被调用。
  • 对调用 setDisableClamShellSleep() 的站点进行代码搜索后,很快就会在文件 iokit/Kernel/RootDomainUserClient.cpp 中的 RootDomainUserClient::externalMethod() 中找到一个位置。这当然是有希望的,因为 externalMethod() 是响应用户 space 程序调用 IOConnectCall*() 函数族的调用。

让我们深入挖掘:

IOReturn RootDomainUserClient::externalMethod(
    uint32_t selector,
    IOExternalMethodArguments * arguments,
    IOExternalMethodDispatch * dispatch __unused,
    OSObject * target __unused,
    void * reference __unused )
{
    IOReturn    ret = kIOReturnBadArgument;

    switch (selector)
    {
…
…
…
        case kPMSetClamshellSleepState:
            fOwner->setDisableClamShellSleep(arguments->scalarInput[0] ? true : false);
            ret = kIOReturnSuccess;
            break;
…

因此,要调用 setDisableClamShellSleep(),您需要:

  1. 打开到 IOPMrootDomain 的用户客户端连接。这看起来很简单,因为:
    • 经检查,IOPMrootDomain 具有 RootDomainUserClientIOUserClientClass 属性,因此用户 space 的 IOServiceOpen() 将默认创建一个RootDomainUserClient 个实例。
    • IOPMrootDomain 不会覆盖 newUserClient 成员函数,因此那里没有访问控制。
    • RootDomainUserClient::initWithTask() 似乎没有对连接用户 space 进程施加任何限制(例如 root 用户、代码签名)。
    • 所以它应该只是 运行 程序中这段代码的情况:
    io_connect_t connection = IO_OBJECT_NULL;
    IOReturn ret = IOServiceOpen(
      root_domain_service,
      current_task(),
      0, // user client type, ignored
      &connection);
  1. 调用适当的外部方法。
    • 从前面的代码摘录中,我们知道选择器必须是kPMSetClamshellSleepState
    • arguments->scalarInput[0] 为零将调用 setDisableClamShellSleep(false),而非零值将调用 setDisableClamShellSleep(true).
    • 这相当于:
IOReturn RootDomain_SetDisableClamShellSleep(io_connect_t root_domain_connection, bool disable)
{
    uint32_t num_outputs = 0;
    uint64_t inputs[] = { disable ? 1 : 0 };
    return IOConnectCallScalarMethod(
        root_domain_connection, kPMSetClamshellSleepState,
        &inputs, 1, // 1 = length of array 'inputs'
        NULL, &num_outputs);
}
  1. 完成 io_connect_t 句柄后,别忘了 IOServiceClose() 它。

这应该可以让您打开或关闭翻盖式睡眠。请注意,似乎没有任何自动将值重置为其原始状态的规定,因此如果您的程序崩溃或退出而没有自行清理,上次设置的任何状态都将保留。从用户体验的角度来看,这可能不是很好,因此也许尝试以某种方式防御它,例如在崩溃处理程序中。