如何以编程的方式防止Mac进入睡眠状态?

36

有没有办法使用Objective-C编程阻止Mac进入睡眠状态?苹果公司的开发网站上的I/O kit基础知识部分告诉我,驱动程序会收到空闲/系统睡眠的通知,但我找不到阻止系统进入睡眠状态的方法。这是否可能?

我已经找到一些使用Caffeine、jiggler、sleepless甚至AppleScript的解决方案,但我想用Objective-C来实现。谢谢。

3个回答

35

这里是包括代码片段在内的苹果官方文档:
Technical Q&A QA1340 - 如何防止睡眠?

引用: 在Mac OS X 10.6 Snow Leopard使用I/O Kit来防止睡眠:

#import <IOKit/pwr_mgt/IOPMLib.h>

// kIOPMAssertionTypeNoDisplaySleep prevents display sleep,
// kIOPMAssertionTypeNoIdleSleep prevents idle sleep

// reasonForActivity is a descriptive string used by the system whenever it needs 
// to tell the user why the system is not sleeping. For example, 
// "Mail Compacting Mailboxes" would be a useful string.

// NOTE: IOPMAssertionCreateWithName limits the string to 128 characters. 
CFStringRef* reasonForActivity= CFSTR("Describe Activity Type");

IOPMAssertionID assertionID;
IOReturn success = IOPMAssertionCreateWithName(kIOPMAssertionTypeNoDisplaySleep, 
                                    kIOPMAssertionLevelOn, reasonForActivity, &assertionID); 
if (success == kIOReturnSuccess)
{
    //  Add the work you need to do without 
    //  the system sleeping here.

    success = IOPMAssertionRelease(assertionID);
    //  The system will be able to sleep again. 
}

对于较旧的OSX版本,请查看以下内容:
技术问答QA1160 - 我如何在我的应用程序运行时防止系统休眠?

引用: UpdateSystemActivity的使用示例(<10.6的规范方式)

#include <CoreServices/CoreServices.h>

void
MyTimerCallback(CFRunLoopTimerRef timer, void *info)
{
    UpdateSystemActivity(OverallAct);
}


int
main (int argc, const char * argv[])
{
    CFRunLoopTimerRef timer;
    CFRunLoopTimerContext context = { 0, NULL, NULL, NULL, NULL };

    timer = CFRunLoopTimerCreate(NULL, CFAbsoluteTimeGetCurrent(), 30, 0, 0, MyTimerCallback, &context);
    if (timer != NULL) {
        CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopCommonModes);
    }

    /* Start the run loop to receive timer callbacks. You don't need to
    call this if you already have a Carbon or Cocoa EventLoop running. */
    CFRunLoopRun();

    CFRunLoopTimerInvalidate(timer);
    CFRelease(timer);

    return (0);
}

我认为当 MacBook 盖子关闭时这个方法不起作用...那么你会如何阻止睡眠? - David Karlsson
@DavidKarlsson 有两种睡眠模式:__空闲__和__强制__。 __空闲__可以由您的应用程序控制,而__强制__则不能。关闭MacBook盖子会强制进入睡眠状态。 - Andreas detests censorship
如果显示器当前处于亮屏状态,此答案可用于“防止”睡眠。有关如何唤醒已经休眠的显示器的问题,请参见https://stackoverflow.com/questions/10598809/how-do-i-wake-from-display-sleep-in-osx-10-7-4/47838364#47838364。有关“盖子关闭”的问题,请参见https://dev59.com/qnA75IYBdhLWcg3wZ4Jr。 - rogerdpack

11

苹果的Q&A1340替代了Q&A1160。最新的Q&A回答了以下问题:“Q:我的应用程序如何在计算机即将进入睡眠或从睡眠中唤醒时得到通知?如何阻止睡眠?”

Q&A1340的第2个清单:

#import <IOKit/pwr_mgt/IOPMLib.h>

// kIOPMAssertionTypeNoDisplaySleep prevents display sleep,
// kIOPMAssertionTypeNoIdleSleep prevents idle sleep

//reasonForActivity is a descriptive string used by the system whenever it needs 
//  to tell the user why the system is not sleeping. For example, 
//  "Mail Compacting Mailboxes" would be a useful string.

//  NOTE: IOPMAssertionCreateWithName limits the string to 128 characters. 
CFStringRef* reasonForActivity= CFSTR("Describe Activity Type");

IOPMAssertionID assertionID;
IOReturn success = IOPMAssertionCreateWithName(kIOPMAssertionTypeNoDisplaySleep, 
                                    kIOPMAssertionLevelOn, reasonForActivity, &assertionID); 
if (success == kIOReturnSuccess)
{

    //Add the work you need to do without 
    //  the system sleeping here.

    success = IOPMAssertionRelease(assertionID);
    //The system will be able to sleep again. 
}
注意,您只能停止闲置时间的睡眠,而不能停止用户触发的睡眠。
对于支持Mac OS X 10.6及更高版本的应用程序,请使用新的IOPMAssertion函数系列。这些函数允许其他应用程序和实用程序查看您的应用程序不希望休眠的愿望;这对于与第三方电源管理软件无缝协作至关重要。

CFStringRef*赋值时,XCode给了我“不兼容的指针类型”的错误。我必须在CFSTR()调用之前添加(CFStringRef *)来修复它。另外,你可能需要提到需要将IOKit.framework添加到项目中。这两个都是正确的吗? - Volomike
另外,在 IOPMAssertionCreateWithName() 调用中,我不得不添加一个星号 *reasonForActivity 以便让它编译通过。 - Volomike
@Volomike,也许您所做的更正能够使代码被编译,但IOReturn将不会是0。相反,请尝试在变量定义中删除星号:CFStringRef * reasonForActivity -> CFStringRef reasonForActivity。 - Jim75
1
@Volomike:是的,这是苹果示例中的一个错误。那个星号不应该在那里。正确的写法应该是CFStringRef reasonForActivity= CFSTR("Describe Activity Type");。我很惊讶这么长时间以来没有人注意到并进行更正。 - c00000fd

2
只需创建一个NSTimer定时器,该定时器会触发一个带有此函数的操作。
UpdateSystemActivity(OverallAct);

我非常确定那正是Caffeine所做的。


3
请避免使用这种技巧。相反,请使用文档化在Q&A1340中记录的经过苹果认可的技术。 - Graham Miln
4
我认为他有道理。苹果所描述的“惊人”技术是一个非常糟糕和低劣的解决方案,因为你必须嵌入代码到那个东西上,使它变得复杂。现在想象一下,如果代码是异步的呢?而且,苹果甚至无法打写没有错误的代码。苹果得零星评价。 - Duck
在OSX 10.8中,此功能已被弃用。 - Volomike
1
@SpaceDog 其实并不是很复杂。你不必像示例代码注释中所述那样将代码放入 if 子句中。只需将 assertionID 保存在一个变量中,稍后再运行 IOPMAssertionRelease(assertionID) 函数,无论何时你想要重新激活空闲功能,或者根本不需要激活。 - Jim75

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