sindresorhus / do-not-disturb

Control the macOS `Do Not Disturb` feature
MIT License
218 stars 18 forks source link

Get DND state (as setting DND state) is not working anymore on mac os v.11 (Big Sur) #9

Open RihardKrauz opened 4 years ago

RihardKrauz commented 4 years ago

getDoNotDisturb is not working anymore on mac os v.11 (Big Sur) since they removed ~/Library/Preferences/ByHost/com.apple.notificationcenterui setting

a7madgamal commented 3 years ago

took a look at the issue, here is where I THINK the value is, but I'm stuck trying to update the UI to apply the change (works with logout though) https://gist.github.com/a7madgamal/9d3518a62c21477a92be461fbd652533

PhilipDukhov commented 3 years ago

value for key dnd_prefs at com.apple.ncprefs.plist is data representations of an other plist! The minus is that it's not updated instantly, takes a couple of seconds. This is how I parse it using swift:

import Foundation

let ncprefsUrl = URL(
    fileURLWithPath: NSString(
        "~/Library/Preferences/com.apple.ncprefs.plist"
    ).expandingTildeInPath
)

func plistFromData(_ data: Data) throws -> [String:Any] {
    try PropertyListSerialization.propertyList(
        from: data,
        format: nil
    ) as! [String:Any]
}

let prefsList = try plistFromData(try Data(contentsOf: ncprefsUrl))
let dndPrefsData = prefsList["dnd_prefs"] as! Data
let dndPrefsList = try plistFromData(dndPrefsData)

// exit with 1 if dnd, 0 otherwise
if let userPref = dndPrefsList["userPref"] as? [String:Any] {
    print(userPref)
    exit(userPref["enabled"] as! Int32)
} else {
    exit(0)
}
CharlieHess commented 3 years ago

sharing here as many others will surely stumble upon this thread. here's the Objective-C version of parsing that nested plist, also handles the scheduled prefs: https://github.com/felixrieseberg/macos-notification-state/pull/15

alse commented 3 years ago

@a7madgamal any luck on updating the UI after the change?

I tried something similar to commitChanges with a 'com.apple.ncprefs' appid / notification name but couldn't get it to work

tiiiecherle commented 3 years ago

took a look at the issue, here is where I THINK the value is, but I'm stuck trying to update the UI to apply the change (works with logout though) https://gist.github.com/a7madgamal/9d3518a62c21477a92be461fbd652533

Thanks for sharing, I found out the commands to trigger the new state. Just use your commands to enable or disable followed by:

killall usernoted
killall ControlCenter

Works perfectly on macOS 11.1 ;)

Edit: I added a second kill command as it seems necessary if triggering the dnd for the first time after reboot.

mellson commented 3 years ago

Nice one @tiiiecherle thanks for posting this!

tiiiecherle commented 3 years ago

@mellson You're welcome, I'm glad I could help.

I was trying to find a solution myself when I found this issue and @a7madgamal 's solution.

After quite some time today I also found out how to get the correct values on the command line...

/usr/libexec/PlistBuddy -x -c 'Print "dnd_prefs"' /Users/"$USER"/Library/Preferences/com.apple.ncprefs.plist | xmllint --xpath "string(//data)" - | base64 --decode | xxd -p

or

plutil -extract dnd_prefs xml1 -o - /Users/"$USER"/Library/Preferences/com.apple.ncprefs.plist | xmllint --xpath "string(//data)" - | base64 --decode | xxd -p

vitorgalvao commented 3 years ago

For those looking for a standalone solution, I’ve studied options out there (including this thread; thank you all for the previous research) and made a script which works robustly. It doesn’t require additional software (everything ships with macOS) and is only for Big Sur.

If you use Homebrew, you can install it with brew install vitorgalvao/tiny-scripts/calm-notifications. Do calm-notifications --help to see available options (on, off, toggle, status).

tiiiecherle commented 3 years ago

@vitorgalvao Thanks for the script and the information. I updated the necessary commands for zsh and added them to my macos system preferences configuration script for documentation purposes.

For anyone who needs them in a shell script, this works for me in zsh with the new additions:

check_dnd_status() {
        DND_STATUS=$(plutil -extract dnd_prefs xml1 -o - /Users/"$USER"/Library/Preferences/com.apple.ncprefs.plist | xmllint --xpath "string(//data)" - | base64 --decode | plutil -convert xml1 - -o - | xmllint --xpath 'boolean(//key[text()="userPref"]/following-sibling::dict/key[text()="enabled"])' -)
}
check_dnd_status

enable_dnd() {
    defaults read /Users/"$USER"/Library/Preferences/com.apple.ncprefs.plist >/dev/null
    DND_HEX_DATA=$(plutil -extract dnd_prefs xml1 -o - /Users/"$USER"/Library/Preferences/com.apple.ncprefs.plist | xmllint --xpath "string(//data)" - | base64 --decode | plutil -convert xml1 - -o - | plutil -insert userPref -xml "
    <dict>
        <key>date</key>
        <date>$(date -u +"%Y-%m-%dT%H:%M:%SZ")</date>
        <key>enabled</key>
        <true/>
        <key>reason</key>
        <integer>1</integer>
    </dict> " - -o - | plutil -convert binary1 - -o - | xxd -p | tr -d '\n')
    defaults write com.apple.ncprefs.plist dnd_prefs -data "$DND_HEX_DATA"
    PROCESS_LIST=(
    #cfprefsd
    usernoted
    #NotificationCenter
    )
    while IFS= read -r line || [[ -n "$line" ]] 
    do
    if [[ "$line" == "" ]]; then continue; fi
        i="$line"
        #echo "$i"
        if [[ $(ps aux | grep "$i" | grep -v grep | awk '{print $2;}') != "" ]]
        then
            killall "$i" && sleep 0.1 && while [[ $(ps aux | grep "$i" | grep -v grep | awk '{print $2;}') == "" ]]; do sleep 0.5; done
    else
        :
    fi
    done <<< "$(printf "%s\n" "${PROCESS_LIST[@]}")"
    #sleep 2
}
enable_dnd

disable_dnd() {
    defaults read /Users/"$USER"/Library/Preferences/com.apple.ncprefs.plist >/dev/null
    DND_HEX_DATA=$(plutil -extract dnd_prefs xml1 -o - /Users/"$USER"/Library/Preferences/com.apple.ncprefs.plist | xmllint --xpath "string(//data)" - | base64 --decode | plutil -convert xml1 - -o - | plutil -remove userPref - -o - | plutil -convert binary1 - -o - | xxd -p | tr -d '\n')
    defaults write com.apple.ncprefs.plist dnd_prefs -data "$DND_HEX_DATA"
    PROCESS_LIST=(
    #cfprefsd
    usernoted
    #NotificationCenter
    )
    while IFS= read -r line || [[ -n "$line" ]] 
    do
    if [[ "$line" == "" ]]; then continue; fi
        i="$line"
        #echo "$i"
        if [[ $(ps aux | grep "$i" | grep -v grep | awk '{print $2;}') != "" ]]
        then
            killall "$i" && sleep 0.1 && while [[ $(ps aux | grep "$i" | grep -v grep | awk '{print $2;}') == "" ]]; do sleep 0.5; done
    else
        :
    fi
    done <<< "$(printf "%s\n" "${PROCESS_LIST[@]}")"
    #sleep 2
}
disable_dnd

EDIT: I updated the functions after some testing. They seem to work reliably now.

karlhorky commented 3 years ago

@tiiiecherle thanks for this! I've used this in this script to automatically stop and start Do Not Disturb when Zoom calls are running, seems to be working well!

https://github.com/karlhorky/do-not-disturb-during-zoom-macos

tiiiecherle commented 3 years ago

@karlhorky You are welcome. I'm glad I could help.

Big credits and thanks go to everyone who posted here and gave the needed ideas and input, e.g. @a7madgamal and @vitorgalvao

karlhorky commented 2 years ago

Edit, see comments below:

I just updated to macOS 12 Monterey and can confirm that the solution from @tiiiecherle still works! 🙌

agross commented 2 years ago

@karlhorky @tiiiecherle For me both enabling and disabling do not work on Monterey. Focus mode/DND stays the way it is set via the UI.

karlhorky commented 2 years ago

@agross interesting, wonder what's different between your system and mine.

Here's the script that I am using to trigger that code:

https://github.com/karlhorky/do-not-disturb-during-zoom-macos

agross commented 2 years ago

@karlhorky I don't know either, but I have two Macs that show the same behavior. Getting the DND status does not change regardless of the Focus Mode setting.

nbashkankov commented 2 years ago

@agross - same for me - Monterey has new Focus mode stuff - see #12

karlhorky commented 2 years ago

Yeah weirdly enough, the solution from @tiiiecherle stopped working for me on Monterey... 😫

Maybe it never worked, and I just tricked myself into thinking it was working...

vitorgalvao commented 2 years ago

Yeah weirdly enough, the solution from @tiiiecherle stopped working for me on Monterey...

It’s not weird at all. Programmatically setting Do Not Disturb isn’t simple and Apple keeps breaking methods we come up with. Big Sur required a whole new method, and so will Monterey.

Maybe it never worked, and I just tricked myself into thinking it was working...

Seems like the likeliest explanation.

karlhorky commented 2 years ago

Ah, just came up with an easy and elegant solution using macOS Monterey Shortcuts 🎉

  1. Open Shortcuts.app
  2. Create a shortcut that ONLY turns on the Focus mode "Do Not Disturb" (rename this to be dnd-on)
  3. Create a shortcut that ONLY turns off the Focus mode "Do Not Disturb" (rename this to be dnd-on)
  4. Run shortcut run dnd-on on the command line to turn on Do Not Disturb
  5. Run shortcut run dnd-off on the command line to turn off Do Not Disturb

Screenshots / videos:

2021-11-18_19-21

2021-11-18_19-19

Screen Shot 2021-11-18 at 19 17 39

https://user-images.githubusercontent.com/1935696/142481782-06731f84-4012-482a-9cdb-075afa95df59.mp4

vitorgalvao commented 2 years ago

I’ve been thinking the same, that Shortcuts would be the solution going forward. At least we no longer need to worry about Apple breaking our solutions every time.

Though that likely does not provide a way to check the state of Do Not Disturb.

I’m still on Big Sur so can’t check.

karlhorky commented 2 years ago

Though that likely does not provide a way to check the state of Do Not Disturb.

Right, that makes sense, although apparently SiriKit can query the state - also macOS via Mac Catalyst 15.0+ (the focusStatus API) (maybe macOS gets this capability too, or already has it but is undocumented)

vitorgalvao commented 2 years ago

To get Do not Disturb state: https://github.com/vitorgalvao/tiny-scripts/issues/206#issuecomment-974747114

tiiiecherle commented 2 years ago

Thanks to everyone for all the updates and the hints.

I also decided to go with the Shortcuts.app when using macOS12 and updated all my scripts accordingly.

Checking status and turning on and off seems to work reliably after some testing and a first time configuration of the Shortcuts.app.

vitorgalvao commented 2 years ago

WIth this Shortcut, you can give it as input on, off, or a number (representing minutes to stay active). Example:

shortcuts run 'Calm Notifications' <<< 'on'
tiiiecherle commented 2 years ago

@vitorgalvao Thanks for the hint. I am not an Alfred Powerpack user, but I will keep it in mind. The workflow with shortcuts.app and the command line seems to work fine.

vitorgalvao commented 2 years ago

The workflow with shortcuts.app and the command line seems to work fine.

That’s why I shared the link to the relevant file. While I do provide it as part of an Alfred tool, it can be downloaded and used independently.

tiiiecherle commented 2 years ago

Thanks, but how and where do I install the workflow? Alfred states on it's website that you have to be a powerpack user to use workflows.

arodik commented 2 years ago

Hey folks, I've created a library for shortcut-based focus mode management. https://github.com/arodik/macos-focus-mode

@vitorgalvao I've used your shortcut if you don't mind) Thank you 🎉 🎉 🎉

john-preston commented 1 year ago

I hope I've fixed that for the next version, need to check in the next 4.6.11 beta when it's out.