首页 > 解决方案 > 在不满足 Apple 要求的情况下启用封闭显示模式

问题描述

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

从历史上看/AFAIK,让您的 Mac 在关闭显示模式下保持清醒并且不满足Apple 的要求,只能通过内核扩展(kext) 或以 root 身份运行的命令来实现。然而,最近,我发现必须有另一种方法。我真的可以使用一些帮助来弄清楚如何让它在(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.app 的消息日志图像

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

编辑:有了@pmdj 的贡献/答案,这已经解决了!

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

这最终变得非常简单/直接:

1.导入头:

#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);

标签: macoscocoakerneliokit

解决方案


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

  • 你想IOPMrootDomain::setDisableClamShellSleep()被召唤。
  • setDisableClamShellSleep()快速搜索调用站点的代码会显示RootDomainUserClient::externalMethod()文件中的位置iokit/Kernel/RootDomainUserClient.cpp。这当然是有希望的,正如externalMethod()响应调用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具有 的IOUserClientClass属性RootDomainUserClient,因此IOServiceOpen()默认情况下会从用户空间创建一个RootDomainUserClient实例。
    • IOPMrootDomain不会覆盖newUserClient成员函数,因此那里没有访问控制。
    • RootDomainUserClient::initWithTask()似乎没有对连接的用户空间进程施加任何限制(例如 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()它。

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


推荐阅读