macos - 在不满足 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 文档中的函数,例如setAggressiveness
或setPMAssertionLevel
。根据控制台,这里有一些幕后发生的事情的证据:
通过将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);
解决方案
我对 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()
您需要:
- 打开与 的用户客户端连接
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);
- 调用适当的外部方法。
- 从前面的代码摘录中,我们知道选择器必须是
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);
}
- 当你完成你的
io_connect_t
手柄时,不要忘记IOServiceClose()
它。
这应该可以让您打开或关闭翻盖式睡眠。请注意,似乎没有任何规定可以自动将值重置为其原始状态,因此如果您的程序崩溃或退出而没有自行清理,则最后设置的任何状态都将保留。从用户体验的角度来看,这可能不是很好,因此可能会尝试以某种方式防御它,例如在崩溃处理程序中。