是否可以通过编程方式打开/关闭OS X的"请勿打扰"

xmk*_*hen 23 macos cocoa

是否可以通过编程方式为mac os x打开/关闭"请勿打扰",这意味着通过代码.我已经通过谷歌做了一些研究,例如:

  1. 通过Automator脚本applescripting通知中心调度请勿打扰.顺便说一句,我没有让它工作,当我开始使用NotificationCenter时,请勿打扰开关仍然关闭

  2. 通过代码编写默认值,编程等效于默认值写命令,例如如何使用NSUserDefaults,但是如何使用args -currentHost(在上面的链接文章中提到)

小智 18

不幸的是(但并不令人惊讶),没有用于处理用户通知偏好的公共API,其中之一是请勿打扰模式(DND).

然而,如果你想在你的应用程序中提供打开和关闭DND的功能,你实际上并不是运气不好:有三种方式供你选择.

#1.运行这个小AppleScript并查看结果

这是一个由philastokes提供的AppleScript,它考虑到选项 - 点击菜单栏中的Notification Center图标完全符合我们的要求:它切换DND模式!

(* Copyright © philastokes from applehelpwriter.com *)
(* Link: http://applehelpwriter.com/2014/12/10/applescript-toggle-notification-centre-yosemite *)

tell application "System Events"
    tell application process "SystemUIServer"
        try
            (* Replace "Notification Center" with "NotificationCenter"
            here if you're targeting OS X 10.10 *)
            if exists menu bar item "Notification Center, Do Not Disturb enabled" of menu bar 2 then
                key down option
                (* Replace "Notification Center" with "NotificationCenter"
                here if you're targeting OS X 10.10 *)
                click menu bar item "Notification Center, Do Not Disturb enabled" of menu bar 2
                key up option
            else
                key down option
                click menu bar item "Notification Center" of menu bar 2
                key up option
            end if
        on error
            key up option
        end try
    end tell
end tell
Run Code Online (Sandbox Code Playgroud)

请注意,您需要更换"Notification Center"与"NotificationCenter"无处不在,如果你的目标OS X 10.10

此外,执行此代码需要您的应用程序为其启用辅助功能.

最后一步是将其包装到Objctive-C/Swift代码中:

NSString *source  = ... // the AppleScript code
NSAppleScript *script = [[NSAppleScript alloc] initWithSource: source];
NSDictionary *errorInfo = nil;
[script executeAndReturnError: &errorInfo];
Run Code Online (Sandbox Code Playgroud)

#2.直接使用Accessibility API

我们可以使用系统中提供的Accessibility API,而不是让AppleScript引擎处理用户交互,而不是让它们成为我们自己的工具:

执行此代码需要您的应用程序为其启用辅助功能.

pid_t SystemUIServerPID = [[NSRunningApplication runningApplicationsWithBundleIdentifier:
                              @"com.apple.systemuiserver"].firstObject processIdentifier];
assert(SystemUIServerPID != 0);

AXUIElementRef target = AXUIElementCreateApplication(SystemUIServerPID);
assert(target != nil);

CFArrayRef attributes = nil;
AXUIElementCopyAttributeNames(target, &attributes);
assert([(__bridge NSArray *)attributes containsObject: @"AXExtrasMenuBar"]);

CFTypeRef menubar;
AXUIElementCopyAttributeValue(target, CFSTR("AXExtrasMenuBar"), &menubar);

CFTypeRef children;
AXUIElementCopyAttributeValue(menubar, CFSTR("AXChildren"), &children);

// XXX: I hate mixing CF and Objective-C like this but it's just a PoC code.
// Anyway, I'm sorry
NSArray *items = (__bridge NSArray *)children;
for (id x in items) {
    AXUIElementRef child = (__bridge AXUIElementRef)x;
    CFTypeRef title;
    AXUIElementCopyAttributeValue(child, CFSTR("AXTitle"), &title);
    assert(CFGetTypeID(title) == CFStringGetTypeID());
    // XXX: the proper check would be to match the whole "Notification Center" string,
    // but on OS X 10.10 it's "NotificationCenter" (without the space in-between) and
    // I don't feel like having two conditionals here
    if (CFStringHasPrefix(title, CFSTR("Notification"))) {
        optionKeyDown();
        AXUIElementPerformAction(child, kAXPressAction);
        optionKeyUp();
        break;
    }
}
Run Code Online (Sandbox Code Playgroud)

其中,optionKeyDown()和optionKeyUp()是

#define kOptionKeyCode (58)

static void optionKeyDown(void)
{
    CGEventRef e = CGEventCreateKeyboardEvent(NULL, kOptionKeyCode, true);
    CGEventPost(kCGSessionEventTap, e);
    CFRelease(e);
}

static void optionKeyUp(void)
{
    CGEventRef e = CGEventCreateKeyboardEvent(NULL, kOptionKeyCode, false);
    CGEventPost(kCGSessionEventTap, e);
    CFRelease(e);
}
Run Code Online (Sandbox Code Playgroud)

#3.让我们断言我们是Notifications.prefPane

您可能已经注意到,通过将模式的范围设置为00:00到23:59 ,可以通过" 通知"首选项窗格启用DND模式.禁用DND只是取消选中复选框.

这是Notifications.prefPane里面的内容:

void turnDoNotDisturbOn(void)
{
    // The trick is to set DND time range from 00:00 (0 minutes) to 23:59 (1439 minutes),
    // so it will always be on
    CFPreferencesSetValue(CFSTR("dndStart"), (__bridge CFPropertyListRef)(@(0.0f)),
                          CFSTR("com.apple.notificationcenterui"),
                          kCFPreferencesCurrentUser, kCFPreferencesCurrentHost);

    CFPreferencesSetValue(CFSTR("dndEnd"), (__bridge CFPropertyListRef)(@(1440.f)),
                          CFSTR("com.apple.notificationcenterui"),
                          kCFPreferencesCurrentUser, kCFPreferencesCurrentHost);

    CFPreferencesSetValue(CFSTR("doNotDisturb"), (__bridge CFPropertyListRef)(@(YES)),
                          CFSTR("com.apple.notificationcenterui"),
                          kCFPreferencesCurrentUser, kCFPreferencesCurrentHost);

    // Notify all the related daemons that we have changed Do Not Disturb preferences
    commitDoNotDisturbChanges();
}


void turnDoNotDisturbOff()
{
    CFPreferencesSetValue(CFSTR("dndStart"), NULL,
                        CFSTR("com.apple.notificationcenterui"),
                        kCFPreferencesCurrentUser, kCFPreferencesCurrentHost);

    CFPreferencesSetValue(CFSTR("dndEnd"), NULL,
                          CFSTR("com.apple.notificationcenterui"),
                          kCFPreferencesCurrentUser, kCFPreferencesCurrentHost);

    CFPreferencesSetValue(CFSTR("doNotDisturb"), (__bridge CFPropertyListRef)(@(NO)),
                          CFSTR("com.apple.notificationcenterui"),
                          kCFPreferencesCurrentUser, kCFPreferencesCurrentHost);

    commitDoNotDisturbChanges();
}

void commitDoNotDisturbChanges(void)
{
    /// XXX: I'm using kCFPreferencesCurrentUser placeholder here which means that this code must
    /// be run under regular user's account (not root/admin). If you're going to run this code
    /// from a privileged helper, use kCFPreferencesAnyUser in order to toggle DND for all users
    /// or drop privileges and use kCFPreferencesCurrentUser.
    CFPreferencesSynchronize(CFSTR("com.apple.notificationcenterui"), kCFPreferencesCurrentUser, kCFPreferencesCurrentHost);
    [[NSDistributedNotificationCenter defaultCenter] postNotificationName: @"com.apple.notificationcenterui.dndprefs_changed"
                                                               object: nil userInfo: nil
                                                   deliverImmediately: YES];
}
Run Code Online (Sandbox Code Playgroud)


arn*_*app 11

@Dmitry的第2号解决方案是唯一能够正常使用的解决方案.

这是它的Swift 4版本:

    func enableDND(){

        CFPreferencesSetValue("dndStart" as CFString, CGFloat(0) as CFPropertyList, "com.apple.notificationcenterui" as CFString, kCFPreferencesCurrentUser, kCFPreferencesCurrentHost)

        CFPreferencesSetValue("dndEnd" as CFString, CGFloat(1440) as CFPropertyList, "com.apple.notificationcenterui" as CFString, kCFPreferencesCurrentUser, kCFPreferencesCurrentHost)

        CFPreferencesSetValue("doNotDisturb" as CFString, true as CFPropertyList, "com.apple.notificationcenterui" as CFString, kCFPreferencesCurrentUser, kCFPreferencesCurrentHost)


        commitDNDChanges()
    }

    func disableDND(){
        CFPreferencesSetValue("dndStart" as CFString, nil, "com.apple.notificationcenterui" as CFString, kCFPreferencesCurrentUser, kCFPreferencesCurrentHost)

        CFPreferencesSetValue("dndEnd" as CFString, nil, "com.apple.notificationcenterui" as CFString, kCFPreferencesCurrentUser, kCFPreferencesCurrentHost)

        CFPreferencesSetValue("doNotDisturb" as CFString, false as CFPropertyList, "com.apple.notificationcenterui" as CFString, kCFPreferencesCurrentUser, kCFPreferencesCurrentHost)
        commitDNDChanges()
    }

    func commitDNDChanges(){
        CFPreferencesSynchronize("com.apple.notificationcenterui" as CFString, kCFPreferencesCurrentUser, kCFPreferencesCurrentHost)
        DistributedNotificationCenter.default().postNotificationName(NSNotification.Name(rawValue: "com.apple.notificationcenterui.dndprefs_changed"), object: nil, userInfo: nil, deliverImmediately: true)
    }
Run Code Online (Sandbox Code Playgroud)

  • 请记住,这在沙盒环境中不起作用。 (3认同)