Monday, October 22, 2018
  • Index
  •  » Code Exchange
  •  » Customizing the display time of banners in the Notification Center

#1 2018-05-03 06:46:13 pm

bmose
Member
From:: Massachusetts
Registered: 2006-01-03
Posts: 281

Customizing the display time of banners in the Notification Center

The System Preferences Notification Center pane allows the user to customize certain notification behaviors, such as notification type (no notification vs banner vs alert) and a global do-not-disturb switch. Whereas notification alerts stay open until the user closes them, notification banners display for a fixed duration of time set by the system (approximately 5 seconds), and no option is offered to customize that value. The current handler, called displayNotification, is an attempt to add that missing feature for Notification Center banners (hereafter referred to simply as "notifications".)

The handler uses ASObjC's NSUserNotificationCenter and NSUserNotification classes to achieve finer control over notifications than that provided by Applescript's display notification Standard Additions command. The handler delivers a new unique instance of the notification to the user notification center every second, then removes the previous instance after allowing sufficient time for the new instance to be displayed. This removal process prevents a large number of identical-appearing notifications from accumulating in Notification Center's display menu. The handler offers the user control over whether the final notification instance is removed from or left in the Notification Center at the end of handler execution; in the former case, it will remain visible in the Notification Center's display menu. The handler's actions are effected through a background osascript so that control is returned to the calling program immediately after the handler is called. Please refer to the comments in the handler code for greater detail concerning the handler's actions.

The handler input argument is used to configure the notification to be displayed. It is an AppleScript record of the following form:

Applescript:


set inputRecord to {theTitle:..., theSubtitle:..., informativeText..., displayTime:..., removeWhenDone:...}

where:

theTitle = text string containing the notification's title
    - largest font size and topmost location in the displayed notification
    - corresponds to the display notification command's with title parameter
    - default value if omitted = "" (the empty string, in which case the system will display
        "osascript" as the title, which is the name of the process delivering the notification)

theSubtitle = text string containing the notification's subtitle
    - middle font size and mid location in the displayed notification
    - corresponds to the display notification command's subtitle parameter
    - default value if omitted = "" (the empty string, in which case no subtitle will be displayed)

informativeText = text string containing the notification's informative text
    - smallest font size and bottommost location in the displayed notification
    - corresponds to the display notification command's direct parameter
    - default value if omitted = "" (the empty string, in which case no informative text will be displayed)

displayTime = integer specifying the time in seconds the notification is to be displayed
    - any value < 5 will be set to 5, the minimum display time of the system
    - default value if omitted = 5

removeWhenDone = true or false
    - true will result in the final notification instance being removed from the Notification Center's display
        menu at the end of handler execution (all other notification instances will have already been removed)
    - default value if omitted = true

A couple of caveats with the current handler:

1) Because the handler uses repetitive firing of notifications to give the illusion of a single notification, it doesn't handle multiple simultaneous notifications well, unless, that is, you like watching a "Dancing Notifications" show. smile

2) If an old unrelated notification is deleted from the Notification Center menu while a notification generated by the current handler is being displayed, the current notification may transiently disappear while the old notification is being deleted then reappear once the deletion is completed.

Example:

Applescript:


-- The following handler call will display a notification with title = "The current date and time is:", no subtitle, informative text = current date/time value, display time = 60 seconds, and removal of the notification from the Notification Center display menu at the end of handler execution:

set currDateTime to (current date) as text

displayNotification({theTitle:"The current date and time is:", theSubtitle:"", informativeText:currDateTime, displayTime:60, removeWhenDone:true})

-- or equivalently and taking advantage of default values for missing input record properties --

displayNotification({theTitle:"The current date and time is:", informativeText:currDateTime, displayTime:60})

Handler:

Applescript:


on displayNotification(inputRecord)
   -- Displays a Notification Center banner with the options to customize the banner's title, subtitle, informative text, display time, and persistence in the Notification Center menu after completion of handler execution
   
   -- Utility handler to convert the input record values to their text representations for incorporation into the background osascript
   script util
       on textValue(theObject)
           set tid to AppleScript's text item delimiters
           try
               || of {theObject}
           on error m
               try
                   set AppleScript's text item delimiters to "{"
                   set m to m's text items 2 thru -1 as text
                   set AppleScript's text item delimiters to "}"
                   set theText to m's text items 1 thru -2 as text
               end try
           end try
           set AppleScript's text item delimiters to tid
           return theText
       end textValue
   end script
   
   -- Process and validate the input record
   try
       tell inputRecord
           if (its class ≠ record) and (it ≠ {}) then error "The input argument is not an Applescript record."
           tell (it & {theTitle:"", theSubtitle:"", informativeText:"", displayTime:5, removeWhenDone:true})
               if length ≠ 5 then error "The input argument record has an invalid property." & return & return & "Valid properties include:" & return & return & tab & "theTitle" & return & tab & "theSubtitle" & return & tab & "informativeText" & return & tab & "displayTime" & return & tab & "removeWhenDone"
               set {theTitle, theSubtitle, informativeText, displayTime, removeWhenDone} to {its theTitle, its theSubtitle, its informativeText, its displayTime, its removeWhenDone}
               if theTitle's class ≠ text then error "The input argument's theTitle property is not a text string."
               if theSubtitle's class ≠ text then error "The input argument's theSubtitle property is not a text string."
               if informativeText's class ≠ text then error "The input argument's informativeText property is not a text string."
               if displayTime's class is not in {integer, real} then error "The input argument's displayTime property is not a number (it should be an integer ≥ 5)."
               set displayTime to displayTime as integer
               if removeWhenDone's class ≠ boolean then error "The input argument's removeWhenDone property is not a boolean true or false value."
           end tell
       end tell
   on error m number n
       error ("Problem with handler displayNotification:" & return & return & m) number n
   end try
   
   -- Execute the notification-displaying script as a background osascript so that control is returned to the calling program immediately after the handler is called
   do shell script "osascript -e " & ("

       use framework \"Foundation\"
       use scripting additions
       property || : current application
       
       on run
           -- Get the osascript's notification configuration parameters from the handler input record values
           set {theTitle, theSubtitle, informativeText, displayTime, removeWhenDone} to "
& util's textValue({theTitle, theSubtitle, informativeText, displayTime, removeWhenDone}) & "
           -- Create a reference to the default notification center, and set the current osascript to be its delegate
           set userNotificationCenter to (||'s NSUserNotificationCenter)'s defaultUserNotificationCenter()
           set userNotificationCenter's delegate to me
           -- Set the number of one-second repeat loop cycles to the input display time minus 5 (minimum value = 1) to adjust for the 5-second system display time of the final delivered notification
           set nReps to displayTime - 5
           if nReps < 1 then set nReps to 1
           -- Deliver a new instance of the user notification to the notification center every second for the predetermined number of times
           set theNotification to null
           repeat nReps times
               -- Save a reference to the most recently delivered user notification instance so that it can be removed after the current instance is displayed
               set oldNotification to theNotification
               -- Create a new user notification instance using the input record's title, subtitle, and informative text values, along with a unique identifying string for its identifier property
               set theNotification to (||'s NSUserNotification)'s alloc()'s init()
               tell theNotification to set {its title, its subtitle, its informativeText, its identifier} to {theTitle, theSubtitle, informativeText, (||'s NSUUID)'s UUID()'s UUIDString()}
               -- Deliver the new user notification instance to the notification center
               (userNotificationCenter's deliverNotification:theNotification)
               -- After a one second delay to allow sufficient time for the new notification to be displayed, remove the previously delivered notification instance
               delay 1
               if oldNotification ≠ null then (userNotificationCenter's removeDeliveredNotification:oldNotification)
           end repeat
           -- After the final user notification instance has been allowed sufficient time to complete its display, remove it if specified by the input record
           if removeWhenDone then
               delay 5
               (userNotificationCenter's removeDeliveredNotification:theNotification)
           end if
       end run
       
       on userNotificationCenter:userNotificationCenter shouldPresentNotification:theNotification
           -- Set the return value of this handler to true so that all notifications delivered to the notification center by the current osascript (the notification center's delegate) are displayed
           return true
       end userNotificationCenter:shouldPresentNotification:
       
   "
)'s quoted form & " >/dev/null 2>&1 &"
   
end displayNotification

Edit notes:
    - Since the post was first submitted, it became evident that the osascript would best be coded as a single multi-line text string rather than lines of text to be concatenated. The current version of the osascript reflects that simplification.
    - Also, minor cosmetic improvements were made to the line indentations and comments in the osascript.

Last edited by bmose (2018-05-04 11:09:42 am)

Offline

 
  • Index
  •  » Code Exchange
  •  » Customizing the display time of banners in the Notification Center

Board footer

Powered by FluxBB

RSS (new topics) RSS (active topics)