启用不满足 Apple 要求的封闭显示模式

x74*_*353 5 macos cocoa kernel iokit

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

从历史上看/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);
       }
    }
}
Run Code Online (Sandbox Code Playgroud)

我想尝试一下,看看是否只需要这样,但我真的不知道如何调用这个函数。它当然不是IOPMrootDomain文档的一部分,而且我似乎找不到任何有用的示例代码用于 IOPMrootDomain 文档中的函数,例如setAggressivenesssetPMAssertionLevel。根据 Console 的说法,这里有一些关于幕后发生的事情的证据:

来自 Console.app 的消息日志图像

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

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

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

这最终出人意料地简单/直截了当:

1. 导入标题:

#import <IOKit/pwr_mgt/IOPMLib.h>
Run Code Online (Sandbox Code Playgroud)

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);
}
Run Code Online (Sandbox Code Playgroud)

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);
Run Code Online (Sandbox Code Playgroud)

pmd*_*mdj 5

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

\n\n
    \n
  • 你想IOPMrootDomain::setDisableClamShellSleep()被召唤。
  • \n
  • 对调用站点的代码搜索可以快速显示文件中setDisableClamShellSleep()的位置。这当然是有前途的,就像响应用户空间程序调用函数系列时所调用的那样。RootDomainUserClient::externalMethod()iokit/Kernel/RootDomainUserClient.cppexternalMethod()IOConnectCall*()
  • \n
\n\n

让我们深入研究一下:

\n\n
IOReturn RootDomainUserClient::externalMethod(\n    uint32_t selector,\n    IOExternalMethodArguments * arguments,\n    IOExternalMethodDispatch * dispatch __unused,\n    OSObject * target __unused,\n    void * reference __unused )\n{\n    IOReturn    ret = kIOReturnBadArgument;\n\n    switch (selector)\n    {\n\xe2\x80\xa6\n\xe2\x80\xa6\n\xe2\x80\xa6\n        case kPMSetClamshellSleepState:\n            fOwner->setDisableClamShellSleep(arguments->scalarInput[0] ? true : false);\n            ret = kIOReturnSuccess;\n            break;\n\xe2\x80\xa6\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

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

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

\n

  • @x74353 不客气,很高兴我能提供帮助。感谢您对我的认可!请注明我为“Phil Dennis-Jordan” - 如果您网站上的某个位置可以通过网络搜索将这些致谢信息编入索引,那就更好了。 (2认同)