25

I have long used an Applescript triggered from a keyboard shortcut to dismiss notifications on MacOS. It worked by simulating a click of the "close" button of every open Notification Center window.

Since upgrading to Big Sur (currently 11.0.1), notifications no longer have a close button. Instead, when you hover over them with the mouse, an "X" button will appear. For a person who has difficulty clicking precisely (I have a slight hand tremor), using the mouse/trackpad is difficult.

Anyone have suggestions on how to restore that functionality so I can just keep my hands on the keyboard?

My Settings

I am using Automator to test this.

In System Preferences > Notifications, I selected Automator:

  • Allow Notifications is true.
  • Automator alert style: is Alerts.
  • Notification grouping is automatic.

Testing

  • I opened Automator and created a new workflow.
  • I added three instances of the Display Notification action.
  • I gave them Title: values of test 1, test 2, and test 3.
  • And Subtitle: values of subtitle 1, subtitle 2, and subtitle 3.
  • And Message: values of message 1, message 2, and message 3.
  • I ran the workflow using ⌘+R.
  • In the upper-right corner of my screen, I see a notification from Automator stacked over two others. Only notification test 3 is fully visible and it has the notation "2 more notifications" at the bottom.

@AndrewJanian 's Script

  • Again, thank you, Andrew for providing the script!
  • I added the line #!/usr/bin/osascript to the start of the script and saved it as andrew.applescript.
  • I made the file executable. (chmod 755 andrew.applescript).
  • I ran it in a terminal window (./andrew.applescript)
  • The result of running is:
    • the grouped notifications become ungrouped (so I see test 3, test 2, and test 1 stacked top to bottom in the notification area
    • the terminal displays the following error
      ./andrew.applescript:370:377: execution error: System Events got an error: Can’t get action "Name:Clear All
      Target:0x60000116ff20
      Selector:_internal_handleCustomActionWithUiAction:" of group 1 of UI element 1 of scroll area 1 of window "Notification Center" of application process "NotificationCenter". (-1728)
      

I'll try messing around with it to see if I can make it work with modifications.

Note: If I run the script again, starting from the ungrouped state:

  • it dismisses the top-most notification (test 3)
  • and it displays the following in the terminal:
    action Name:Close
    Target:0x60000116ff20
    Selector:_internal_handleCustomActionWithUiAction: of group 1 of UI element 1 of scroll area 1 of window Notification Center of application process NotificationCenter
    
  • 1
    If notification is set to Banner type then it will be dismissed automatically after a while, no interaction is needed. – Aivar Paalberg Dec 04 '20 at 14:34
  • 3
    Thanks, @AivarPaalberg. I really want the notifications to stick around until I dismiss them. I just want to be able to dismiss them without using the mouse. [Instead of the old 'Close' button, now there is a tiny X to click on—it's almost like they wanted to make it as small as the "close" button for mobile ads. :-) – Colin Fraizer Dec 09 '20 at 07:10
  • You can dismiss notifications without aiming at tiny X if you happen to have trackpad. While hovering anywhere over notification two finger swipe from left to right should dismiss it. From productivity perspective I personally don't believe that on desktop notifications and immediate interaction with them is good workflow. – Aivar Paalberg Dec 09 '20 at 10:22
  • 1
    Thank you, @AivarPaalberg. That certainly helps, but I agree it's less than ideal to have to move my mouse cursor up to that corner just to dismiss the notifications. I prefer my old workflow of running a command (okay, pressing a key combo) to make them go away while keeping my focus on whatever was interrupted. – Colin Fraizer Dec 09 '20 at 19:57
  • And the cross is really very tiny. So tiny that I just noticed it... There is a UI issue here, Apple. – Snicolas Mar 18 '21 at 19:57

11 Answers11

13

Update

I'm posting a most robust version that evolved from my initial post. This seems to do a reasonable job of closing all windows, but it's sometimes slow to execute. As I've stated in the comments, I don't know much about AppleScript, so perhaps someone who knows what they're doing can tell us how to improve the performance.

activate application "NotificationCenter"
tell application "System Events"
    tell process "Notification Center"
        repeat
        try
            set theWindow to group 1 of UI element 1 of scroll area 1 of window "Notification Center"
        on error
            exit repeat
        end try

        try
            set theActions to actions of theWindow

            # Try to close the whole group first. If that fails, close individual windows.
            repeat with theAction in theActions
                if description of theAction is "Clear All" then
                    set closed to true
                    tell theWindow
                        perform theAction
                    end tell
                    exit repeat
                end if
            end repeat

            repeat with theAction in theActions
                if description of theAction is "Close" then
                    set closed to true
                    tell theWindow
                        perform theAction
                    end tell
                    exit repeat
                end if
            end repeat

        end try
    end repeat
end tell

end tell

Original

Yes, thank you for posting the script, @AndrewJanian.

I received an error similar to @ColinFraizer's when using it as is. I think the issue is the inner repeat over the actions: it's trying to access actions in some cases after the window is closed. A minor tweak fixes this issue for me:

activate application "NotificationCenter"
tell application "System Events"
    tell process "Notification Center"
        set theWindow to group 1 of UI element 1 of scroll area 1 of window "Notification Center"
        # click theWindow
        set theActions to actions of theWindow
        repeat with theAction in theActions
            if description of theAction is "Close" then
                tell theWindow
                    perform theAction
                end tell
                exit repeat
            end if
        end repeat
    end tell
end tell
lunzwell
  • 131
  • Does it work on big sur ? I'm getting System Events got an error: Can’t get process "Notification Center" here. – Amadeu Cavalcante Filho Mar 06 '21 at 17:26
  • When I run this script in the Script Editor on macOS Big Sur 11.2 it dismisses the topmost notification only, but not all notifications. – Employee Mar 10 '21 at 00:00
  • @AmadeuCavalcanteFilho Yes, I run it on Big Sur. I'm a total AppleScript newbie, so I'm afraid I don't know what's causing that particular error. – lunzwell Mar 12 '21 at 04:13
  • @Employee Yes! I will update the post to add a more recent version that I have wrangled into a more robust form. The updated version does a pretty reasonable job of clearing all notifications for me, although it seems slow. I don't know the first thing about writing good AppleScript, so perhaps someone who knows what they're doing can improve on this. – lunzwell Mar 12 '21 at 04:15
  • @AmadeuCavalcanteFilho the process name varies depending on where your laptop is registered at. For me it is "Notification Centre" – yaswanth May 18 '21 at 05:49
  • This no longer works for me since I installed MacOS Ventura. Any suggestions? – incandescentman Oct 29 '22 at 23:19
  • 2
    @incandescentman I have the same problem after upgrading. The way to access the Window has changed, it seems, but try replacing the existing set theWindow line with set theWindow to group 1 of UI element 1 of scroll area 1 of UI element 1 of window "Notification Center" – lunzwell Nov 02 '22 at 13:18
  • @lunzwell Wow, that worked, thank you! – incandescentman Nov 08 '22 at 03:24
  • I still get the invalid index error with @lunz 's edit. Monterey OS with language set to US english. – majorgear Nov 14 '22 at 17:48
6

I have an AppleScript that works. I found the elements using UIBrowser. Caveat is that the notification must have a "Close" action. All of the notifications I have encountered have that action.

activate application "NotificationCenter"
tell application "System Events"
    tell process "Notification Center"
        set theWindow to group 1 of UI element 1 of scroll area 1 of window "Notification Center"
        click theWindow
        set theActions to actions of theWindow
        repeat with theAction in theActions
            if description of theAction is "Close" then
                tell theWindow
                    perform theAction
                end tell
            end if
        end repeat
    end tell
end tell
  • 3
    Thanks, @Andrew. Sadly, this doesn't work for me on Big Sur. I'd love to explain more, but the comment is character limited and won't let me add line breaks, making it very annoying. I'm going to edit my question to give the context. – Colin Fraizer Dec 09 '20 at 07:07
4

I found an Automator JavaScript action which works:

function run(input, parameters) {

const notNull = (val) => { return val !== null && val !== undefined; }

const appName = ""; const verbose = true;

const CLEAR_ALL_ACTION = "Clear All"; const CLOSE_ACTION = "Close";

const hasAppName = notNull(appName) && appName !== ""; const appNameForLog = hasAppName ? (" " + appName) : "";

const log = (message, ...optionalParams) => { console.log("[close_notifications] " + message, optionalParams); }

const logVerbose = (message) => { if (verbose) { log(message); } }

const findCloseAction = (group, closedCount) => { try { let clearAllAction; let closeAction; for (let action of group.actions()) { if (action.description() === CLEAR_ALL_ACTION) { clearAllAction = action; break; } else if (action.description() === CLOSE_ACTION) { closeAction = action; } } if (notNull(clearAllAction)) { return clearAllAction; } else if (notNull(closeAction)) { return closeAction; } } catch (err) { logVerbose(${closedCount}: Caught error while searching for close action, window is probably closed.); logVerbose(err); return null; } log("No close action found for notification"); return null; }

const notificationGroupMatches = (group) => { if (!hasAppName) { return true; }

logVerbose(`Checking UI elements of group...`);
try {
  for (let elem of group.uiElements()) {
    if (hasAppName && elem.role() === "AXStaticText" && elem.value().toLowerCase() === appName.toLowerCase()) {
      return true;
    }
  }
} catch (err) {
  logVerbose(`Caught error while checking window, window is probably closed.`);
  logVerbose(err);
}
return false;

}

const closeNextGroup = (groups, closedCount) => { for (let group of groups) { if (notificationGroupMatches(group)) { logVerbose(${closedCount}: FIND_CLOSE_ACTION); let closeAction = findCloseAction(group, closedCount);

    if (notNull(closeAction)) {
      logVerbose(`${closedCount}: CLOSING`);
      try {
        closeAction.perform();
        logVerbose(`${closedCount}: CLOSE_PERFORMED`);
        return [true, 1];
      } catch (err) {
        logVerbose(`${closedCount}: Caught error while performing close action, window is probably closed.`);
        logVerbose(err);
      }
    }
    return [true, 0];
  }
}
return false;

}

const getNotificationCenter = () => { let systemEvents = Application("System Events"); return systemEvents.processes.byName("NotificationCenter"); }

const getNotificationCenterGroups = () => { return getNotificationCenter().windows[0].uiElements[0].uiElements[0].uiElements(); }

let notificationCenter = getNotificationCenter(); if (notificationCenter.windows.length <= 0) { return input; }

let groupsCount = getNotificationCenterGroups().filter(group => notificationGroupMatches(group)).length;

if (groupsCount > 0) { logVerbose(Closing ${groupsCount}${appNameForLog} notification group${(groupsCount &gt; 1 ? &quot;s&quot; : &quot;&quot;)});

let closedCount = 0;
let maybeMore = true;
while (maybeMore) {
  let closeResult = closeNextGroup(getNotificationCenterGroups(), closedCount);
  maybeMore = closeResult[0];
  if (maybeMore) {
    closedCount = closedCount + closeResult[1];
  }
}

} else { throw Error(No${appNameForLog} notifications found...); }

return input; }

3

This has been annoying me too, but I managed to get something close working today.

First I installed cliclick, a command line tool that lets you simulate mouse movement and clicks. It's available via Homebrew with brew install cliclick.

Then I figured out the coordinates of the time in the top-right, which when clicked, opens the notification centre. This took a bit of experimentation.

Then I figured out the coordinates of the close button that appears on hover. Again this took a bit of experimentation.

Then I wrote a script that uses cliclick and these coordinates to open the Notification Center, move the mouse over where the "clear" button appears, sleeps for 500 milliseconds to give it time to appear, and then click it. It worked!

export PATH=/usr/local/bin:$PATH # make sure cliclick binary is on the path
cliclick c:1900,10               # click on the time to open Notification Center
cliclick -w 500 m:1570,45 c:.    # move mouse over the close button, wait for it to appear, then click it

I then tried to get this running as an Automator service. I got it running from within Automator but though the script worked there, it refused to work when I assigned it to a keyboard shortcut. I bashed my head against a brick wall trying out different permissions etc until giving up.

I tried the HotKey app, but had the same result.

Eventually I thought about trying an Alfred workflow, so I could open Alfred with Cmd+Space and then type "Clear notifications" to clear them. Much more verbose than a keyboard shortcut but... it worked! And no mouse needed. My best guess as to why is that when Alfred runs the script, it always runs from within Alfred, and Alfred already has the required permissions to move the mouse and click on stuff, but when I try to attach an Automator service to a keyboard shortcut, that service runs with whatever permissions the currently in focus app has.

This feels like a horribly brittle solution (what happens when I'm not using my external monitor??) and it seems rubbish that there's no built-in way to do this, but at least it works.

TL;DR a combination of a script using cliclick and an Alfred workflow to run it.

  • This is cool but is hard coded for your monitor dimensions. It's better if you move the mouse to the top right extreme and then use negative amounts to get to the required positions. – Matt Sephton Dec 09 '21 at 15:25
3

I've tried most of the solutions offered here, but none has quite worked as well as I want. I've since tried using Keysmith, and been pretty happy with the results.

Keysmith offers an existing shortcut for this. It looks like this:

Screenshot of a Keysmith shortcut to close notification

This gives you the ability to press ⌘ esc to close notifications (from any application).

The only issue I had with it was that once the notifications had been closed, the active window of the current application lost focus. Fortunately, you can add an additional step in the Keysmith shortcut to regain the focus of the active window: ⌃F4. That looks like this in Keysmith:

Screenshot of a Keysmith shortcut to close notifications. The shortcut is: in Notification center, "Click Close", and then "Press ⌃F4"

zgreen
  • 131
2

Thank you @lunzwell. I added a error routine.

on run
    try
        activate application "NotificationCenter"
        tell application "System Events"
            tell process "Mitteilungszentrale"
                set theWindow to group 1 of UI element 1 of scroll area 1 of window "Notification Center"
                # click theWindow
                set theActions to actions of theWindow
                repeat with theAction in theActions
                    if description of theAction is "Schließen" then
                        tell theWindow
                            perform theAction
                        end tell
                        exit repeat
                    end if
                end repeat
            end tell
        end tell
    on error e
        display dialog e
        activate
    end try
end run

I wonder though if there is a way to avoid an error message, when accidentally running the script with no notifications on the screen.

Ptujec
  • 31
2

I made some improvements to this script:

  1. I added error handling (a narrower trap, in English) /ht @Ptujec
  2. I recursively call this function after a delay (because I found that for some notifications, I would close, they would all disappear, but then all the others that were not the top in the stack would re-appear a few seconds later)
  3. I added support for the "Clear All" button (I found sometimes I had to use this instead of "Close")

My full script is in my dotfiles on GitHub here and I have it exposed via an Alfred Workflow here.

#!/usr/bin/osascript
# via:
# https://apple.stackexchange.com/questions/408019/dismiss-macos-big-sur-notifications-with-keyboard

define a function we can call recursively

on dismiss_notification_center(n) log "dismiss_notification_center: " & n set performedAction to false activate application "NotificationCenter" tell application "System Events" tell process "Notification Center" try # when there are no notifications, this may result in: # 'System Events got an error: Can’t get window "Notification Center" of process "Notification Center".' # This is our recursion base case. set theWindow to group 1 of UI element 1 of scroll area 1 of window "Notification Center" on error e # log the error to the console: log quoted form of e return end try # log theWindow

        set theActions to actions of theWindow
        repeat with theAction in theActions
            # log theAction
            # log description of theAction
            if description of theAction is in {&quot;Close&quot;, &quot;Clear All&quot;} then
                tell theWindow
                    perform theAction
                    set performedAction to true
                end tell
                exit repeat
            end if
        end repeat
    end tell
end tell
# log &quot;performedAction: &quot; &amp; performedAction
if performedAction
    # for some reason, the loop doesn't close them all when grouped, so
    # we need to recurse. But, first we have to sleep to allow notif to re-appear:
    do shell script &quot;/bin/sleep 3&quot; # min sleep time that worked for me, and sometimes reminders take even longer
    dismiss_notification_center(n + 1)
end if

end dismiss_notification_center

first call to recursive function:

dismiss_notification_center(0)

kortina
  • 199
2

I got a working script via cliclick also. Here's my version of the script I use. I've stitched together from a bunch of user libraries I used. I have it mapped to voice control created via Automator instead of a keyboard shortcut.

It still fails when the notification refuses to show the close button no matter how much you move the pointer on top of it.

activate application "NotificationCenter"
delay 0.1
tell application "System Events" to tell process "Notification Center"
    if (count of windows) is 0 then return
set theGroup to first group of UI element 1 of scroll area 1 of window &quot;Notification Center&quot;        
tell theGroup
    if my isStacked(theGroup) then
        click theGroup
        delay 1
    end if

    my dismissNotification(theGroup)
end tell

end tell

on isStacked(nextGroup) tell application "System Events" to tell process "Notification Center"
get help of nextGroup is "Activate to expand" end tell end isStacked

to dismissNotification(theNotification) script CloseButtonClicker -- pointer's saveCurrentPosition() movePointer at theNotification delay 0.1 tell application "System Events" try click button "Close" of theNotification return true end try delay 0.1 end tell -- pointer's restorePosition() end script
exec on CloseButtonClicker for 3 by 0.4 -- Retry on failure up to 3x. Optional. end dismissNotification

to movePointer at theUi set coord to getCoord at theUi set formattedCoord to formatCoordinates(item 1 of coord, item 2 of coord)

set clickCommand to &quot;/usr/local/bin/cliclick -e 1 m:&quot; &amp; formattedCoord
do shell script clickCommand

end movePointer

to formatCoordinates(x, y) if x is less than 0 then set x to "=" & x if y is less than 0 then set y to "=" & y

return x &amp; &quot;,&quot; &amp; y as text

end formatCoordinates

to exec on scriptObj by sleep : 1 for ntimes : 1000 repeat ntimes times try set handlerResult to run of scriptObj if handlerResult is not missing value then return handlerResult end try delay sleep end repeat return missing value end exec

0

Here's what works for me on Big Sur. I found a script somewhere in stack exchange (possibly in this thread), but I ran into some errors and fixed them. I've been using it for a few weeks with no issues -- it's also much faster than the script I was using before Big Sur.

1. Create a clear_notifications script

You can do that by running this script from your terminal

write_target=~/bin/clear_notifications
mkdir ~/bin
cat << EOF > $write_target
#!/usr/bin/osascript

Usage: clear_notifications [partial_title]

Clears notifications from the notification center if they contain the string in [partial_title].

If no arg is passed in, all notifications are cleared.

on run argv tell application "System Events" try set _groups to groups of UI element 1 of scroll area 1 of group 1 of window "Notification Center" of application process "NotificationCenter" repeat with _group in _groups set temp to value of static text 1 of _group set _actions to actions of _group # Get all the actions within this group set isInScope to true

      if (count of argv) &gt; 0 then
        set searchTerm to item 1 of argv
        if temp does not contain searchTerm then
          log &quot;Didn't find any notifications matching &quot; &amp; searchTerm
          set isInScope to false
        end if
      end if

      if isInScope then
        if exists (first action of _group where description is &quot;Clear All&quot;) then
          log &quot;Found 'clear all' for &quot; &amp; temp
          perform (first action of _group where description is &quot;Clear All&quot;)
        else if exists (first action of _group where description is &quot;Close&quot;) then
          log &quot;Found close for &quot; &amp; temp
          perform (first action of _group where description is &quot;Close&quot;)
        else
          log &quot;Didn't find close action for &quot; &amp; temp
        end if
      end if

  end repeat
on error errMsg
    log &quot;Error: &quot; &amp; errMsg
end try

end tell end run EOF chmod u+x $write_target

2. Connect the script to a keyboard shortcut.

You can do this using the keyboard shortcut instructions here, but I find automator clunky and cumbersome for such a simple task. I much prefer using Better Touch Tool, which I already use for many other shortcuts.

  • Go to Better Touch Tool configuration
  • In the left panel, select "All Apps"
  • Add a new keyboard shortcut of your choice and choose Execute Terminal Command (Async, non-blocking) as the trigger.
  • Enter ~/bin/clear_notifications into the textbox where it says Enter Terminal Command

better touch tool add shortcut

2. (Optional) Create shortcuts to clear specific notifications

If you just want to clear, say, Calendar notifications, where the title of the notification has the word "Calendar" in it, you can enter the following in the terminal command text box instead. I have terminal notifications from iTerm that let me know when builds are complete, and I mostly use it for that.

  • Enter ~/bin/clear_notifications "Calendar" into the textbox where it says Enter Terminal Command
-1

Had anyone considered just using:

pkill NotificationCenter

The process respawns after doing this. I use it to clear out all the notifications I get with an external drive.

Greenonline
  • 2,004
-1

This answer suggests that on Big Sur in its current release there is no satisfactory way to dismiss notifications with a keyboard. Instead, this answer explains alternative ways to dismiss them that are not noted elsewhere in this thread.

Two-finger swipe right on the notification group works on Big Sur with the Macbook's trackpad (not sure about Yosemite). It also works with left-click and drag to the right. I assume this would work with an Apple Magic Mouse or Magic Trackpad as well. I personally find both the two-finger swipe and left-click and drag to be easier to do than clicking the X icon then clicking clear all. I find coordinating the twice-click in such a small area is very difficult.

This doesn't "clear" all notifications permanently. They will still show if you open the notification sidebar. They will also show if another message comes through; instead of seeing 1 notification it will show N + 1 notifications available.

This idea was noted in a comment in another answer: Dismiss MacOS Big Sur notifications with keyboard.

The reason I am suggesting that there is no satisfactory way to dismiss notifications with a keyboard is: an automated script is noticeably slow for large numbers of notifications, the Keysmith solution didn't work on Big Sur (it errored), pkill/killall NotificationCenter didn't work dismiss the notifications (which means a keyboard shortcut for it wouldn't help). I did not try the clickclick solution, I was concerned I would waste too much time trying to get it to work and that it would experience the same delays as an automated script.

This thread is similar to How do I clear All OS X notifications with 1 click?.

  • The question asks how to do with the keyboard. Your first paragraph is all bout mouse and trackpad. – mmmmmm Dec 16 '22 at 00:33
  • Yes, thank you for noting that. I modified it so that the disclaimer to the answer comes as the first paragraph instead of later in the answer. – josephdpurcell Dec 17 '22 at 04:37