不需要满足苹果的要求如何启用闭合显示模式

7

编辑: 在没有任何答案的情况下,我做了一些重大的新发现并对这个问题进行了大量的编辑。

历史上/据我所知,在关闭显示模式下让您的Mac保持清醒,并且不符合Apple的要求,只能通过一个内核扩展(kext)或以root身份运行的命令来实现。然而,最近我发现还必须有另一种方法。我真的需要一些帮助,找出如何使其在(100%免费,无IAP)沙箱化的Mac App Store(MAS)兼容应用程序中使用。

我已确认其他MAS应用程序能够做到这一点,看起来它们可能会向名为clamshellSleepDisabled的键中写入YES。或者也许有其他的诡计导致键值被设置为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根据幕后情况的一些证据:

Image of message logs from Console.app

我有一点使用IOMProotDomain的经验,通过为另一个项目调整ControlPlane的源代码来适应它,但我不知道如何开始。任何帮助都将不胜感激。谢谢!
编辑: 在@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);

1
你可以尝试使用DTrace或类似工具来弄清其他应用程序是如何做的。不幸的是,我不知道有没有现成的DTrace脚本可以深入挖掘IOKit调用;制作这样的脚本已经在我的待办列表上有一段时间了,但我的待办列表很长...... - pmdj
谢谢您的建议,我非常感激。我想我可能已经找到了答案,或者部分答案。键“setDisableClamShellSleep”的值正在被更改。它似乎是RootDomain.h的一部分(https://opensource.apple.com/source/xnu/xnu-2422.100.13/iokit/IOKit/pwr_mgt/RootDomain.h)。我不确定如何编写一个新值来更改此键,但这看起来是使其正常工作的下一步。 - William Gustafson
1个回答

5

我个人没有使用过PM根域,但我在IOKit方面拥有丰富的经验,所以接下来就介绍一下:

  • 你想要调用IOPMrootDomain::setDisableClamShellSleep()
  • 对于调用setDisableClamShellSleep()的位置进行代码搜索,很快在文件iokit/Kernel/RootDomainUserClient.cpp中找到了一个名为RootDomainUserClient::externalMethod()的位置。这非常有希望,因为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具有RootDomainUserClientIOUserClientClass属性,因此用户空间中的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()关闭它。

这应该让你能够切换Clamshell睡眠的开关。请注意,似乎没有任何自动重置值为其原始状态的规定,因此,如果您的程序崩溃或退出而没有清理自己,最后设置的状态将保持不变。从用户体验的角度来看,这可能不是很好,因此可以在崩溃处理程序中尝试对其进行防御。


1
这太棒了。非常感谢您的帮助。通过对您提供的代码进行一些小修改,我成功地让它工作了。我很快就会发布一个链接到一个可工作的示例项目。您希望在Amphetamine中如何署名? :) - William Gustafson
2
@x74353 不用谢,很高兴能帮到你。感谢你愿意给我信用!请将我的名字写为“Phil Dennis-Jordan”,如果你在网站上有一个可以被网络搜索引擎索引的信用页面,那就更好了。 - pmdj

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接