启用 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 文档中找到任何有用的函数示例代码,例如 setAggressiveness
或 setPMAssertionLevel
。根据 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()
,您需要:
- 打开到
IOPMrootDomain
的用户客户端连接。这看起来很简单,因为:
- 经检查,
IOPMrootDomain
具有 RootDomainUserClient
的 IOUserClientClass
属性,因此用户 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);
- 调用适当的外部方法。
- 从前面的代码摘录中,我们知道选择器必须是
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()
它。
这应该可以让您打开或关闭翻盖式睡眠。请注意,似乎没有任何自动将值重置为其原始状态的规定,因此如果您的程序崩溃或退出而没有自行清理,上次设置的任何状态都将保留。从用户体验的角度来看,这可能不是很好,因此也许尝试以某种方式防御它,例如在崩溃处理程序中。
编辑: 在做出一些重要的新发现并且该问题还没有任何答案之后,我对这个问题进行了大量编辑。
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 文档中找到任何有用的函数示例代码,例如 setAggressiveness
或 setPMAssertionLevel
。根据 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()
,您需要:
- 打开到
IOPMrootDomain
的用户客户端连接。这看起来很简单,因为:- 经检查,
IOPMrootDomain
具有RootDomainUserClient
的IOUserClientClass
属性,因此用户 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);
- 调用适当的外部方法。
- 从前面的代码摘录中,我们知道选择器必须是
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()
它。
这应该可以让您打开或关闭翻盖式睡眠。请注意,似乎没有任何自动将值重置为其原始状态的规定,因此如果您的程序崩溃或退出而没有自行清理,上次设置的任何状态都将保留。从用户体验的角度来看,这可能不是很好,因此也许尝试以某种方式防御它,例如在崩溃处理程序中。