├── LICENSE ├── README.md └── alert.el /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2024, John Wiegley 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of John Wiegley nor the names of other 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Alert is a Growl-workalike for Emacs which uses a common notification 2 | interface and multiple, selectable "styles", whose use is fully customizable 3 | by the user. 4 | 5 | For desktop notifications, the [notifications package](https://www.gnu.org/software/emacs/manual/html_node/elisp/Desktop-Notifications.html) that is installed with emacs, provides a probably better alternative for most users. 6 | 7 | # For module writers 8 | 9 | Just use `alert` instead of `message` as follows: 10 | 11 | ``` lisp 12 | (require 'alert) 13 | 14 | ;; This is the most basic form usage 15 | (alert "This is an alert") 16 | 17 | ;; You can adjust the severity for more important messages 18 | (alert "This is an alert" :severity 'high) 19 | 20 | ;; Or decrease it for purely informative ones 21 | (alert "This is an alert" :severity 'trivial) 22 | 23 | ;; Alerts can have optional titles. Otherwise, the title is the 24 | ;; buffer-name of the (current-buffer) where the alert originated. 25 | (alert "This is an alert" :title "My Alert") 26 | 27 | ;; Further, alerts can have categories. This allows users to 28 | ;; selectively filter on them. 29 | (alert "This is an alert" :title "My Alert" :category 'debug) 30 | 31 | ;; If a backend allows replacing alerts, you may pass an id 32 | ;; to your alert; then the next one with the same id will replace the 33 | ;; first one: 34 | (alert "You have 30 unread mails" :title "Mail!" :id 'new-mail-alert) 35 | (alert "You have 49 unread mails" :title "Mail!" :id 'new-mail-alert) 36 | ;; This avoids piling up lots of alerts, when only the last one is 37 | ;; relevant. 38 | ``` 39 | 40 | # For users 41 | 42 | For the user, there are several variables to control when and how alerts 43 | are presented. By default, they appear in the minibuffer much the same 44 | as a normal Emacs message. But there are many more possibilities: 45 | 46 | - `alert-fade-time` 47 | Normally alerts disappear after this many seconds, if the style 48 | supports it. The default is 5 seconds. 49 | 50 | - `alert-default-style` 51 | Pick the style to use if no other config rule matches. The 52 | default is `message`, but `growl` works well too. 53 | 54 | - `alert-reveal-idle-time` 55 | If a config rule choose to match on `idle`, this is how many 56 | seconds idle the user has to be. Defaults to 5 so that users 57 | don't miss any alerts, but 120 is also good. 58 | 59 | - `alert-persist-idle-time` 60 | After this many idle seconds, alerts will become sticky, and not 61 | fade away more. The default is 15 minutes. 62 | 63 | - `alert-log-messages` 64 | By default, all alerts are logged to \*Alerts\* (and to \*Messages\*, 65 | if the `message` style is being used). Set to nil to disable. 66 | 67 | - `alert-hide-all-notifications` 68 | Want alerts off entirely? They still get logged, however, unless 69 | you've turned that off too. 70 | 71 | - `alert-user-configuration` 72 | This variable lets you control exactly how and when a particular 73 | alert, a class of alerts, or all alerts, get reported -- or if at 74 | all. Use this to make some alerts use Growl, while others are 75 | completely silent. 76 | 77 | # Programmatically adding rules 78 | 79 | Users can also programmatically add configuration rules, in addition to 80 | customizing `alert-user-configuration`. Here is one that the author 81 | currently uses with ERC, so that the fringe gets colored whenever people 82 | chat on BitlBee: 83 | 84 | ``` lisp 85 | (alert-add-rule :status '(buried visible idle) 86 | :severity '(moderate high urgent) 87 | :mode 'erc-mode 88 | :predicate 89 | #'(lambda (info) 90 | (string-match (concat "\\`[^&].*@BitlBee\\'") 91 | (erc-format-target-and/or-network))) 92 | :persistent 93 | #'(lambda (info) 94 | ;; If the buffer is buried, or the user has been 95 | ;; idle for `alert-reveal-idle-time' seconds, 96 | ;; make this alert persistent. Normally, alerts 97 | ;; become persistent after 98 | ;; `alert-persist-idle-time' seconds. 99 | (memq (plist-get info :status) '(buried idle))) 100 | :style 'fringe 101 | :continue t) 102 | ``` 103 | 104 | # Builtin alert styles 105 | 106 | There are several builtin styles, and it is trivial to create new ones. 107 | The builtins are: 108 | 109 | | Name | Summary | 110 | | ------------- | ------------------------------------------------------------------ | 111 | | fringe | Changes the current frame's fringe background color | 112 | | mode-line | Changes the current frame's mode-line background color | 113 | | gntp | Uses gntp, it requires [gntp.el](https://github.com/tekai/gntp.el) | 114 | | growl | Uses Growl on OS X, if growlnotify is on the PATH | 115 | | ignore | Ignores the alert entirely | 116 | | libnotify | Uses libnotify if notify-send is on the PATH | 117 | | log | Logs the alert text to *Alerts*, with a timestamp | 118 | | message | Uses the Emacs `message` facility | 119 | | notifications | Uses notifications library via D-Bus | 120 | | notifier | Uses terminal-notifier on OS X, if it is on the PATH | 121 | | osx-notifier | Native OSX notification using AppleScript | 122 | | toaster | Use the toast notification system | 123 | | x11 | Changes the urgency property of the window in the X Window System 124 | | termux | Use `termux-notification` from the Termux API | 125 | 126 | # Defining new styles 127 | 128 | To create a new style, you need to at least write a `notifier`, which is 129 | a function that receives the details of the alert. These details are 130 | given in a plist which uses various keyword to identify the parts of the 131 | alert. Here is a prototypical style definition: 132 | 133 | ``` lisp 134 | (alert-define-style 'style-name :title "My Style's title" 135 | :notifier 136 | (lambda (info) 137 | ;; The message text is :message 138 | (plist-get info :message) 139 | ;; The :title of the alert 140 | (plist-get info :title) 141 | ;; The :category of the alert 142 | (plist-get info :category) 143 | ;; The major-mode this alert relates to 144 | (plist-get info :mode) 145 | ;; The buffer the alert relates to 146 | (plist-get info :buffer) 147 | ;; Severity of the alert. It is one of: 148 | ;; `urgent' 149 | ;; `high' 150 | ;; `moderate' 151 | ;; `normal' 152 | ;; `low' 153 | ;; `trivial' 154 | (plist-get info :severity) 155 | ;; Whether this alert should persist, or fade away 156 | (plist-get info :persistent) 157 | ;; Data which was passed to `alert'. Can be 158 | ;; anything. 159 | (plist-get info :data)) 160 | 161 | ;; Removers are optional. Their job is to remove 162 | ;; the visual or auditory effect of the alert. 163 | :remover 164 | (lambda (info) 165 | ;; It is the same property list that was passed to 166 | ;; the notifier function. 167 | )) 168 | ``` 169 | -------------------------------------------------------------------------------- /alert.el: -------------------------------------------------------------------------------- 1 | ;;; alert.el --- Growl-style notification system for Emacs -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2011-2013 John Wiegley 4 | 5 | ;; Author: John Wiegley 6 | ;; Created: 24 Aug 2011 7 | ;; Updated: 16 Mar 2015 8 | ;; Version: 1.2 9 | ;; Package-Requires: ((gntp "0.1") (log4e "0.3.0") (cl-lib "0.5")) 10 | ;; Keywords: notification emacs message 11 | ;; X-URL: https://github.com/jwiegley/alert 12 | 13 | ;; This program is free software; you can redistribute it and/or 14 | ;; modify it under the terms of the GNU General Public License as 15 | ;; published by the Free Software Foundation; either version 2, or (at 16 | ;; your option) any later version. 17 | 18 | ;; This program is distributed in the hope that it will be useful, but 19 | ;; WITHOUT ANY WARRANTY; without even the implied warranty of 20 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 21 | ;; General Public License for more details. 22 | 23 | ;; You should have received a copy of the GNU General Public License 24 | ;; along with GNU Emacs; see the file COPYING. If not, write to the 25 | ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, 26 | ;; Boston, MA 02111-1307, USA. 27 | 28 | ;;; Commentary: 29 | 30 | ;; Alert is a Growl-workalike for Emacs which uses a common notification 31 | ;; interface and multiple, selectable "styles", whose use is fully 32 | ;; customizable by the user. 33 | ;; 34 | ;; * For module writers 35 | ;; 36 | ;; Just use `alert' instead of `message' as follows: 37 | ;; 38 | ;; (require 'alert) 39 | ;; 40 | ;; ;; This is the most basic form usage 41 | ;; (alert "This is an alert") 42 | ;; 43 | ;; ;; You can adjust the severity for more important messages 44 | ;; (alert "This is an alert" :severity 'high) 45 | ;; 46 | ;; ;; Or decrease it for purely informative ones 47 | ;; (alert "This is an alert" :severity 'trivial) 48 | ;; 49 | ;; ;; Alerts can have optional titles. Otherwise, the title is the 50 | ;; ;; buffer-name of the (current-buffer) where the alert originated. 51 | ;; (alert "This is an alert" :title "My Alert") 52 | ;; 53 | ;; ;; Further, alerts can have categories. This allows users to 54 | ;; ;; selectively filter on them. 55 | ;; (alert "This is an alert" :title "My Alert" :category 'debug) 56 | ;; 57 | ;; * For users 58 | ;; 59 | ;; For the user, there are several variables to control when and how alerts 60 | ;; are presented. By default, they appear in the minibuffer much the same 61 | ;; as a normal Emacs message. But there are many more possibilities: 62 | ;; 63 | ;; `alert-fade-time' 64 | ;; Normally alerts disappear after this many seconds, if the style 65 | ;; supports it. The default is 5 seconds. 66 | ;; 67 | ;; `alert-default-style' 68 | ;; Pick the style to use if no other config rule matches. The 69 | ;; default is `message', but `growl' works well too. 70 | ;; 71 | ;; `alert-reveal-idle-time' 72 | ;; If a config rule choose to match on `idle', this is how many 73 | ;; seconds idle the user has to be. Defaults to 5 so that users 74 | ;; don't miss any alerts, but 120 is also good. 75 | ;; 76 | ;; `alert-persist-idle-time' 77 | ;; After this many idle seconds, alerts will become sticky, and not 78 | ;; fade away more. The default is 15 minutes. 79 | ;; 80 | ;; `alert-log-messages' 81 | ;; By default, all alerts are logged to *Alerts* (and to *Messages*, 82 | ;; if the `message' style is being used). Set to nil to disable. 83 | ;; 84 | ;; `alert-hide-all-notifications' 85 | ;; Want alerts off entirely? They still get logged, however, unless 86 | ;; you've turned that off too. 87 | ;; 88 | ;; `alert-user-configuration' 89 | ;; This variable lets you control exactly how and when a particular 90 | ;; alert, a class of alerts, or all alerts, get reported -- or if at 91 | ;; all. Use this to make some alerts use Growl, while others are 92 | ;; completely silent. 93 | ;; 94 | ;; * Programmatically adding rules 95 | ;; 96 | ;; Users can also programmatically add configuration rules, in addition to 97 | ;; customizing `alert-user-configuration'. Here is one that the author 98 | ;; currently uses with ERC, so that the fringe gets colored whenever people 99 | ;; chat on BitlBee: 100 | ;; 101 | ;; (alert-add-rule :status '(buried visible idle) 102 | ;; :severity '(moderate high urgent) 103 | ;; :mode 'erc-mode 104 | ;; :predicate 105 | ;; #'(lambda (info) 106 | ;; (string-match (concat "\\`[^&].*@BitlBee\\'") 107 | ;; (erc-format-target-and/or-network))) 108 | ;; :persistent 109 | ;; #'(lambda (info) 110 | ;; ;; If the buffer is buried, or the user has been 111 | ;; ;; idle for `alert-reveal-idle-time' seconds, 112 | ;; ;; make this alert persistent. Normally, alerts 113 | ;; ;; become persistent after 114 | ;; ;; `alert-persist-idle-time' seconds. 115 | ;; (memq (plist-get info :status) '(buried idle))) 116 | ;; :style 'fringe 117 | ;; :continue t) 118 | ;; 119 | ;; * Builtin alert styles 120 | ;; 121 | ;; There are several builtin styles, and it is trivial to create new ones. 122 | ;; The builtins are: 123 | ;; 124 | ;; fringe - Changes the current frame's fringe background color 125 | ;; mode-line - Changes the current frame's mode-line background color 126 | ;; gntp - Uses gntp, it requires gntp.el (see https://github.com/tekai/gntp.el) 127 | ;; growl - Uses Growl on OS X, if growlnotify is on the PATH 128 | ;; ignore - Ignores the alert entirely 129 | ;; libnotify - Uses libnotify if notify-send is on the PATH 130 | ;; log - Logs the alert text to *Alerts*, with a timestamp 131 | ;; message - Uses the Emacs `message' facility 132 | ;; momentary - Uses the Emacs `momentary-string-display' facility 133 | ;; notifications - Uses notifications library via D-Bus 134 | ;; notifier - Uses terminal-notifier on OS X, if it is on the PATH 135 | ;; osx-notifier - Native OSX notifier using AppleScript 136 | ;; toaster - Use the toast notification system 137 | ;; x11 - Changes the urgency property of the window in the X Window System 138 | ;; termux - Use termux-notification from the Termux API 139 | ;; 140 | ;; * Defining new styles 141 | ;; 142 | ;; To create a new style, you need to at least write a "notifier", which is 143 | ;; a function that receives the details of the alert. These details are 144 | ;; given in a plist which uses various keyword to identify the parts of the 145 | ;; alert. Here is a prototypical style definition: 146 | ;; 147 | ;; (alert-define-style 'style-name :title "My Style's title" 148 | ;; :notifier 149 | ;; (lambda (info) 150 | ;; ;; The message text is :message 151 | ;; (plist-get info :message) 152 | ;; ;; The :title of the alert 153 | ;; (plist-get info :title) 154 | ;; ;; The :category of the alert 155 | ;; (plist-get info :category) 156 | ;; ;; The major-mode this alert relates to 157 | ;; (plist-get info :mode) 158 | ;; ;; The buffer the alert relates to 159 | ;; (plist-get info :buffer) 160 | ;; ;; Severity of the alert. It is one of: 161 | ;; ;; `urgent' 162 | ;; ;; `high' 163 | ;; ;; `moderate' 164 | ;; ;; `normal' 165 | ;; ;; `low' 166 | ;; ;; `trivial' 167 | ;; (plist-get info :severity) 168 | ;; ;; Whether this alert should persist, or fade away 169 | ;; (plist-get info :persistent) 170 | ;; ;; Data which was passed to `alert'. Can be 171 | ;; ;; anything. 172 | ;; (plist-get info :data)) 173 | ;; 174 | ;; ;; Removers are optional. Their job is to remove 175 | ;; ;; the visual or auditory effect of the alert. 176 | ;; :remover 177 | ;; (lambda (info) 178 | ;; ;; It is the same property list that was passed to 179 | ;; ;; the notifier function. 180 | ;; )) 181 | ;; 182 | ;; You can test a specific style with something like this: 183 | ;; 184 | ;; (let ((alert-user-configuration '((((:severity high)) momentary nil)))) 185 | ;; (alert "Same buffer momentary alert" :title "My Alert" :severity 'high) 186 | ;; (alert "This is a momentary alert in another visible buffer" :title "My Alert" 187 | ;; :severity 'high :buffer (other-buffer (current-buffer) t))) 188 | 189 | ;;; Code: 190 | 191 | (require 'cl-lib) 192 | (require 'gntp nil t) 193 | (eval-when-compile 194 | ;; if not available, silence the byte compiler 195 | (defvar gntp-server)) 196 | (declare-function gntp-notify "gntp") 197 | (require 'notifications nil t) 198 | (require 'log4e nil t) 199 | 200 | ;; shut up the byte compiler 201 | (declare-function alert-gntp-notify "alert") 202 | (declare-function alert-notifications-notify "alert") 203 | 204 | (defgroup alert nil 205 | "Notification system for Emacs similar to Growl" 206 | :group 'emacs) 207 | 208 | (defcustom alert-severity-faces 209 | '((urgent . alert-urgent-face) 210 | (high . alert-high-face) 211 | (moderate . alert-moderate-face) 212 | (normal . alert-normal-face) 213 | (low . alert-low-face) 214 | (trivial . alert-trivial-face)) 215 | "Faces associated by default with alert severities." 216 | :type '(alist :key-type symbol :value-type color) 217 | :group 'alert) 218 | 219 | (defcustom alert-severity-colors 220 | '((urgent . "red") 221 | (high . "orange") 222 | (moderate . "yellow") 223 | (normal . "green") 224 | (low . "blue") 225 | (trivial . "purple")) 226 | "Colors associated by default with alert severities. 227 | This is used by styles external to Emacs that don't understand faces." 228 | :type '(alist :key-type symbol :value-type color) 229 | :group 'alert) 230 | 231 | (defcustom alert-log-severity-functions 232 | '((urgent . alert--log-fatal) 233 | (high . alert--log-error) 234 | (moderate . alert--log-warn) 235 | (normal . alert--log-info) 236 | (low . alert--log-debug) 237 | (trivial . alert--log-trace)) 238 | "Log4e logging functions." 239 | :type '(alist :key-type symbol :value-type color) 240 | :group 'alert) 241 | 242 | (defcustom alert-log-level 243 | 'normal 244 | "Minimum level of messages to log." 245 | :type 'symbol 246 | :group 'alert) 247 | 248 | (defcustom alert-reveal-idle-time 15 249 | "If idle this many seconds, rules will match the `idle' property." 250 | :type 'integer 251 | :group 'alert) 252 | 253 | (defcustom alert-persist-idle-time 900 254 | "If idle this many seconds, all alerts become persistent. 255 | This can be overridden with the Never Persist option (:never-persist)." 256 | :type 'integer 257 | :group 'alert) 258 | 259 | (defcustom alert-fade-time 5 260 | "If not idle, alerts disappear after this many seconds. 261 | The amount of idle time is governed by `alert-persist-idle-time'." 262 | :type 'integer 263 | :group 'alert) 264 | 265 | (defcustom alert-hide-all-notifications nil 266 | "If non-nil, no alerts are ever shown to the user." 267 | :type 'boolean 268 | :group 'alert) 269 | 270 | (defcustom alert-log-messages t 271 | "If non-nil, all alerts are logged to the *Alerts* buffer." 272 | :type 'boolean 273 | :group 'alert) 274 | 275 | (defcustom alert-default-icon 276 | (concat data-directory 277 | "images/icons/hicolor/scalable/apps/emacs.svg") 278 | "Filename of default icon to show for libnotify-alerts." 279 | :type 'string 280 | :group 'alert) 281 | 282 | (defvar alert-styles nil) 283 | 284 | (defun alert-styles-radio-type (widget-name) 285 | (append 286 | (list widget-name :tag "Style") 287 | (mapcar #'(lambda (style) 288 | (list 'const 289 | :tag (or (plist-get (cdr style) :title) 290 | (symbol-name (car style))) 291 | (car style))) 292 | (setq alert-styles 293 | (sort alert-styles 294 | #'(lambda (l r) 295 | (string< (symbol-name (car l)) 296 | (symbol-name (car r))))))))) 297 | 298 | (defcustom alert-default-style 'message 299 | "The style to use if no rules match in the current configuration. 300 | If a configured rule does match an alert, this style is not used; 301 | it is strictly a fallback." 302 | :type (alert-styles-radio-type 'radio) 303 | :group 'alert) 304 | 305 | (defun alert-configuration-type () 306 | (list 'repeat 307 | (list 308 | 'list :tag "Select style if alert matches selector" 309 | '(repeat 310 | :tag "Selector" 311 | (choice 312 | (cons :tag "Severity" 313 | (const :format "" :severity) 314 | (set (const :tag "Urgent" urgent) 315 | (const :tag "High" high) 316 | (const :tag "Moderate" moderate) 317 | (const :tag "Normal" normal) 318 | (const :tag "Low" low) 319 | (const :tag "Trivial" trivial))) 320 | (cons :tag "User Status" 321 | (const :format "" :status) 322 | (set (const :tag "Buffer not visible" buried) 323 | (const :tag "Buffer visible" visible) 324 | (const :tag "Buffer selected" selected) 325 | (const :tag "Buffer selected, user idle" idle))) 326 | (cons :tag "Major Mode" 327 | (const :format "" :mode) 328 | regexp) 329 | (cons :tag "Category" 330 | (const :format "" :category) 331 | regexp) 332 | (cons :tag "Title" 333 | (const :format "" :title) 334 | regexp) 335 | (cons :tag "Message" 336 | (const :format "" :message) 337 | regexp) 338 | (cons :tag "Predicate" 339 | (const :format "" :predicate) 340 | function) 341 | (cons :tag "Icon" 342 | (const :format "" :icon) 343 | regexp))) 344 | (alert-styles-radio-type 'choice) 345 | '(set :tag "Options" 346 | (cons :tag "Make alert persistent" 347 | (const :format "" :persistent) 348 | (choice :value t (const :tag "Yes" t) 349 | (function :tag "Predicate"))) 350 | (cons :tag "Never persist" 351 | (const :format "" :never-persist) 352 | (choice :value t (const :tag "Yes" t) 353 | (function :tag "Predicate"))) 354 | (cons :tag "Continue to next rule" 355 | (const :format "" :continue) 356 | (choice :value t (const :tag "Yes" t) 357 | (function :tag "Predicate"))) 358 | ;;(list :tag "Change Severity" 359 | ;; (radio :tag "From" 360 | ;; (const :tag "Urgent" urgent) 361 | ;; (const :tag "High" high) 362 | ;; (const :tag "Moderate" moderate) 363 | ;; (const :tag "Normal" normal) 364 | ;; (const :tag "Low" low) 365 | ;; (const :tag "Trivial" trivial)) 366 | ;; (radio :tag "To" 367 | ;; (const :tag "Urgent" urgent) 368 | ;; (const :tag "High" high) 369 | ;; (const :tag "Moderate" moderate) 370 | ;; (const :tag "Normal" normal) 371 | ;; (const :tag "Low" low) 372 | ;; (const :tag "Trivial" trivial))) 373 | )))) 374 | 375 | (defcustom alert-user-configuration nil 376 | "Rules that determine how and when alerts get displayed." 377 | :type (alert-configuration-type) 378 | :group 'alert) 379 | 380 | (defvar alert-internal-configuration nil 381 | "Rules added by `alert-add-rule'. 382 | For user customization, see `alert-user-configuration'.") 383 | 384 | (defface alert-urgent-face 385 | '((t (:foreground "Red" :bold t))) 386 | "Urgent alert face." 387 | :group 'alert) 388 | 389 | (defface alert-high-face 390 | '((t (:foreground "Dark Orange" :bold t))) 391 | "High alert face." 392 | :group 'alert) 393 | 394 | (defface alert-moderate-face 395 | '((t (:foreground "Gold" :bold t))) 396 | "Moderate alert face." 397 | :group 'alert) 398 | 399 | (defface alert-normal-face 400 | '((t)) 401 | "Normal alert face." 402 | :group 'alert) 403 | 404 | (defface alert-low-face 405 | '((t (:foreground "Dark Blue"))) 406 | "Low alert face." 407 | :group 'alert) 408 | 409 | (defface alert-trivial-face 410 | '((t (:foreground "Dark Violet"))) 411 | "Trivial alert face." 412 | :group 'alert) 413 | 414 | (defun alert-define-style (name &rest plist) 415 | "Define a new style for notifying the user of alert messages. 416 | To create a new style, you need to at least write a \"notifier\", 417 | which is a function that receives the details of the alert. 418 | These details are given in a plist which uses various keyword to 419 | identify the parts of the alert. Here is a prototypical style 420 | definition: 421 | 422 | \(alert-define-style 'style-name :title \"My Style's title\" 423 | :notifier 424 | (lambda (info) 425 | ;; The message text is :message 426 | (plist-get info :message) 427 | ;; The :title of the alert 428 | (plist-get info :title) 429 | ;; The :category of the alert 430 | (plist-get info :category) 431 | ;; The major-mode this alert relates to 432 | (plist-get info :mode) 433 | ;; The buffer the alert relates to 434 | (plist-get info :buffer) 435 | ;; Severity of the alert. It is one of: 436 | ;; `urgent' 437 | ;; `high' 438 | ;; `moderate' 439 | ;; `normal' 440 | ;; `low' 441 | ;; `trivial' 442 | (plist-get info :severity) 443 | ;; Whether this alert should persist, or fade away 444 | (plist-get info :persistent) 445 | ;; Data which was passed to `alert'. Can be 446 | ;; anything. 447 | (plist-get info :data)) 448 | 449 | ;; Removers are optional. Their job is to remove 450 | ;; the visual or auditory effect of the alert. 451 | :remover 452 | (lambda (info) 453 | ;; It is the same property list that was passed to 454 | ;; the notifier function. 455 | ))" 456 | (add-to-list 'alert-styles (cons name plist)) 457 | (put 'alert-user-configuration 'custom-type (alert-configuration-type)) 458 | (put 'alert-define-style 'custom-type (alert-styles-radio-type 'radio))) 459 | 460 | (alert-define-style 'ignore :title "Ignore Alert" 461 | :notifier #'ignore 462 | :remover #'ignore) 463 | 464 | ;;;###autoload 465 | (cl-defun alert-add-rule (&key severity status mode category title 466 | message predicate icon (style alert-default-style) 467 | persistent continue never-persist append) 468 | "Programmatically add an alert configuration rule. 469 | 470 | Normally, users should custoimze `alert-user-configuration'. 471 | This facility is for module writers and users that need to do 472 | things the Lisp way. 473 | 474 | Here is a rule the author currently uses with ERC, so that the 475 | fringe gets colored whenever people chat on BitlBee: 476 | 477 | \(alert-add-rule :status \\='(buried visible idle) 478 | :severity \\='(moderate high urgent) 479 | :mode \\='erc-mode 480 | :predicate 481 | #\\='(lambda (info) 482 | (string-match (concat \"\\\\`[^&].*@BitlBee\\\\\\='\") 483 | (erc-format-target-and/or-network))) 484 | :persistent 485 | #\\='(lambda (info) 486 | ;; If the buffer is buried, or the user has been 487 | ;; idle for `alert-reveal-idle-time' seconds, 488 | ;; make this alert persistent. Normally, alerts 489 | ;; become persistent after 490 | ;; `alert-persist-idle-time' seconds. 491 | (memq (plist-get info :status) \\='(buried idle))) 492 | :style \\='fringe 493 | :continue t)" 494 | (let ((rule (list (list t) style (list t)))) 495 | (if severity 496 | (nconc (nth 0 rule) 497 | (list (cons :severity 498 | (if (listp severity) 499 | severity 500 | (list severity)))))) 501 | (if status 502 | (nconc (nth 0 rule) 503 | (list (cons :status 504 | (if (listp status) 505 | status 506 | (list status)))))) 507 | (if mode 508 | (nconc (nth 0 rule) 509 | (list (cons :mode 510 | (if (stringp mode) 511 | mode 512 | (concat "\\`" (symbol-name mode) 513 | "\\'")))))) 514 | (if category 515 | (nconc (nth 0 rule) (list (cons :category category)))) 516 | (if title 517 | (nconc (nth 0 rule) (list (cons :title title)))) 518 | (if message 519 | (nconc (nth 0 rule) (list (cons :message message)))) 520 | (if predicate 521 | (nconc (nth 0 rule) (list (cons :predicate predicate)))) 522 | (if icon 523 | (nconc (nth 0 rule) (list (cons :icon icon)))) 524 | (setcar rule (cdr (nth 0 rule))) 525 | 526 | (if persistent 527 | (nconc (nth 2 rule) (list (cons :persistent persistent)))) 528 | (if never-persist 529 | (nconc (nth 2 rule) (list (cons :never-persist never-persist)))) 530 | (if continue 531 | (nconc (nth 2 rule) (list (cons :continue continue)))) 532 | (setcdr (cdr rule) (list (cdr (nth 2 rule)))) 533 | 534 | (if (null alert-internal-configuration) 535 | (setq alert-internal-configuration (list rule)) 536 | (if append 537 | (nconc alert-internal-configuration (list rule)) 538 | (setq alert-internal-configuration 539 | (cons rule alert-internal-configuration)))) 540 | 541 | rule)) 542 | 543 | (alert-define-style 'ignore :title "Don't display alerts") 544 | 545 | (defun alert-log-notify (info) 546 | (let* ((mes (plist-get info :message)) 547 | (sev (plist-get info :severity)) 548 | (len (length mes)) 549 | (func (cdr (assoc sev alert-log-severity-functions)))) 550 | (if (not (featurep 'log4e)) 551 | (alert-legacy-log-notify mes sev len) 552 | ;; when we get here you better be using log4e or have your logging 553 | ;; functions defined 554 | (unless (fboundp func) 555 | (when (fboundp 'log4e:deflogger) 556 | (log4e:deflogger "alert" "%t [%l] %m" "%H:%M:%S") 557 | (when (functionp 'alert--log-set-level) 558 | (alert--log-set-level alert-log-level))) 559 | (alert--log-enable-logging)) 560 | (when (fboundp func) 561 | (apply func (list mes)))))) 562 | 563 | (defun alert-legacy-log-notify (mes sev len) 564 | (with-current-buffer 565 | (get-buffer-create "*Alerts*") 566 | (goto-char (point-max)) 567 | (insert (format-time-string "%H:%M %p - ")) 568 | (insert mes) 569 | (set-text-properties (- (point) len) (point) 570 | (list 'face (cdr (assq sev 571 | alert-severity-faces)))) 572 | (insert ?\n))) 573 | 574 | (defun alert-log-clear (info) 575 | (if (functionp 'alert--log-clear-log) 576 | (alert--log-clear-log) 577 | (if (bufferp "*Alerts*") 578 | (with-current-buffer 579 | (get-buffer-create "*Alerts*") 580 | (goto-char (point-max)) 581 | (insert (format-time-string "%H:%M %p - ") 582 | "Clear: " (plist-get info :message) 583 | ?\n))))) 584 | 585 | (alert-define-style 'log :title "Log to *Alerts* buffer" 586 | :notifier #'alert-log-notify 587 | ;;:remover #'alert-log-clear 588 | ) 589 | 590 | (defun alert-message-notify (info) 591 | ;; the message text might contain `%' and we don't want them to be 592 | ;; interpreted as format specifiers: 593 | (message "%s" (plist-get info :message)) 594 | ;;(if (memq (plist-get info :severity) '(high urgency)) 595 | ;; (ding)) 596 | ) 597 | 598 | (defun alert-message-remove (_info) 599 | (message "")) 600 | 601 | (alert-define-style 'message :title "Display message in minibuffer" 602 | :notifier #'alert-message-notify 603 | :remover #'alert-message-remove) 604 | 605 | (defun alert-momentary-notify (info) 606 | (save-excursion 607 | (with-current-buffer (or (plist-get info :buffer) (current-buffer)) 608 | (momentary-string-display 609 | (format "%s: %s (%s/%s/%s)" 610 | (or (plist-get info :title) "untitled") 611 | (or (plist-get info :message) "no message") 612 | (or (plist-get info :severity) "no priority") 613 | (or (plist-get info :category) "no category") 614 | (or (plist-get info :mode) "no mode")) 615 | (progn 616 | (beginning-of-line) 617 | (point)))))) 618 | 619 | (alert-define-style 'momentary :title "Display message momentarily in buffer" 620 | :notifier #'alert-momentary-notify 621 | ;; explicitly, we don't need a remover 622 | :remover #'ignore) 623 | 624 | (copy-face 'fringe 'alert-saved-fringe-face) 625 | 626 | (defun alert-fringe-notify (info) 627 | (set-face-background 'fringe (cdr (assq (plist-get info :severity) 628 | alert-severity-colors)))) 629 | 630 | (defun alert-fringe-restore (_info) 631 | (copy-face 'alert-saved-fringe-face 'fringe)) 632 | 633 | (alert-define-style 'fringe :title "Change the fringe color" 634 | :notifier #'alert-fringe-notify 635 | :remover #'alert-fringe-restore) 636 | 637 | (copy-face 'mode-line 'alert-saved-mode-line-face) 638 | (defun alert-mode-line-notify (info) 639 | (set-face-background 'mode-line (cdr (assq (plist-get info :severity) 640 | alert-severity-colors))) 641 | (set-face-foreground 'mode-line "white")) 642 | 643 | (defun alert-mode-line-restore (_info) 644 | (copy-face 'alert-saved-mode-line-face 'mode-line)) 645 | 646 | (alert-define-style 'mode-line :title "Change the mode-line color" 647 | :notifier #'alert-mode-line-notify 648 | :remover #'alert-mode-line-restore) 649 | 650 | 651 | 652 | (defcustom alert-growl-command (executable-find "growlnotify") 653 | "Path to the growlnotify command. 654 | This is found in the Growl Extras: http://growl.info/extras.php." 655 | :type 'file 656 | :group 'alert) 657 | 658 | (defcustom alert-growl-priorities 659 | '((urgent . 2) 660 | (high . 2) 661 | (moderate . 1) 662 | (normal . 0) 663 | (low . -1) 664 | (trivial . -2)) 665 | "A mapping of alert severities onto Growl priority values." 666 | :type '(alist :key-type symbol :value-type integer) 667 | :group 'alert) 668 | 669 | (defsubst alert-encode-string (str) 670 | (encode-coding-string str (keyboard-coding-system))) 671 | 672 | (defun alert-growl-notify (info) 673 | (if alert-growl-command 674 | (let* ((title (alert-encode-string (plist-get info :title))) 675 | (priority (number-to-string 676 | (cdr (assq (plist-get info :severity) 677 | alert-growl-priorities)))) 678 | (args 679 | (cl-case system-type 680 | ('windows-nt (mapcar 681 | (lambda (lst) (apply #'concat lst)) 682 | `( 683 | ;; http://www.growlforwindows.com/gfw/help/growlnotify.aspx 684 | ("/i:" ,(file-truename (concat invocation-directory "../share/icons/hicolor/48x48/apps/emacs.png"))) 685 | ("/t:" ,title) 686 | ("/p:" ,priority)))) 687 | (t (list 688 | "--appIcon" "Emacs" 689 | "--name" "Emacs" 690 | "--title" title 691 | "--priority" priority))))) 692 | (if (and (plist-get info :persistent) 693 | (not (plist-get info :never-persist))) 694 | (cl-case system-type 695 | ('windows-nt (nconc args (list "/s:true"))) 696 | (t (nconc args (list "--sticky"))))) 697 | (let ((message (alert-encode-string (plist-get info :message)))) 698 | (cl-case system-type 699 | ('windows-nt (nconc args (list message))) 700 | (t (nconc args (list "--message" message))))) 701 | (apply #'call-process alert-growl-command nil nil nil args)) 702 | (alert-message-notify info))) 703 | 704 | (alert-define-style 'growl :title "Notify using Growl" 705 | :notifier #'alert-growl-notify) 706 | 707 | 708 | (defcustom alert-libnotify-command (executable-find "notify-send") 709 | "Path to the notify-send command. 710 | This is found in the libnotify-bin package in Debian based 711 | systems." 712 | :type 'file 713 | :group 'alert) 714 | 715 | (defcustom alert-libnotify-additional-args 716 | nil 717 | "Additional args to pass to notify-send. 718 | Must be a list of strings." 719 | :type '(repeat string) 720 | :group 'alert) 721 | 722 | (defcustom alert-libnotify-priorities 723 | '((urgent . critical) 724 | (high . critical) 725 | (moderate . normal) 726 | (normal . normal) 727 | (low . low) 728 | (trivial . low)) 729 | "A mapping of alert severities onto libnotify priority values." 730 | :type '(alist :key-type symbol :value-type symbol) 731 | :group 'alert) 732 | 733 | (defun alert-libnotify-notify (info) 734 | "Send INFO using notify-send. 735 | Handles :ICON, :CATEGORY, :SEVERITY, :PERSISTENT, :NEVER-PERSIST, :TITLE 736 | and :MESSAGE keywords from the INFO plist. :CATEGORY can be 737 | passed as a single symbol, a string or a list of symbols or 738 | strings." 739 | (if alert-libnotify-command 740 | (let* ((args 741 | (append 742 | (list "--icon" (or (plist-get info :icon) 743 | alert-default-icon) 744 | "--app-name" "Emacs" 745 | "--urgency" (let ((urgency (cdr (assq 746 | (plist-get info :severity) 747 | alert-libnotify-priorities)))) 748 | (if urgency 749 | (symbol-name urgency) 750 | "normal"))) 751 | (copy-tree alert-libnotify-additional-args))) 752 | (category (plist-get info :category))) 753 | (nconc args 754 | (list "--expire-time" 755 | (number-to-string 756 | (* 1000 ; notify-send takes msecs 757 | (if (and (plist-get info :persistent) 758 | (not (plist-get info :never-persist))) 759 | 0 ; 0 indicates persistence 760 | alert-fade-time))))) 761 | (when category 762 | (nconc args 763 | (list "--category" 764 | (cond ((symbolp category) 765 | (symbol-name category)) 766 | ((stringp category) category) 767 | ((listp category) 768 | (mapconcat (if (symbolp (car category)) 769 | #'symbol-name 770 | #'identity) 771 | category ",")))))) 772 | (nconc args (list 773 | (alert-encode-string (plist-get info :title)) 774 | (alert-encode-string (plist-get info :message)))) 775 | (apply #'call-process alert-libnotify-command nil 776 | (list (get-buffer-create " *libnotify output*") t) nil args)) 777 | (alert-message-notify info))) 778 | 779 | (alert-define-style 'libnotify :title "Notify using libnotify" 780 | :notifier #'alert-libnotify-notify) 781 | 782 | 783 | (defcustom alert-gntp-icon 784 | "http://cvs.savannah.gnu.org/viewvc/*checkout*/emacs/emacs/etc/images/icons/hicolor/48x48/apps/emacs.png" 785 | "Icon file using gntp." 786 | :type 'string 787 | :group 'alert) 788 | 789 | (when (featurep 'gntp) 790 | (defun alert-gntp-notify (info) 791 | (gntp-notify 'alert 792 | (alert-encode-string (plist-get info :title)) 793 | (alert-encode-string (plist-get info :message)) 794 | gntp-server nil 795 | (number-to-string 796 | (cdr (assq (plist-get info :severity) 797 | alert-growl-priorities))) 798 | (if (eq (plist-get info :icon) nil) 799 | alert-gntp-icon 800 | (plist-get info :icon))) 801 | (alert-message-notify info)) 802 | 803 | (alert-define-style 'gntp :title "Notify using gntp" 804 | :notifier #'alert-gntp-notify)) 805 | 806 | 807 | (defcustom alert-notifications-priorities 808 | '((urgent . critical) 809 | (high . critical) 810 | (moderate . normal) 811 | (normal . normal) 812 | (low . low) 813 | (trivial . low)) 814 | "A mapping of alert severities onto Growl priority values." 815 | :type '(alist :key-type symbol :value-type symbol) 816 | :group 'alert) 817 | 818 | (defvar alert-notifications-ids (make-hash-table :test #'equal) 819 | "Internal store of notification ids returned by the `notifications' backend. 820 | Used for replacing notifications with the same id. The key is 821 | the value of the :id keyword to `alert'. An id is only stored 822 | here if there `alert' was called with an :id keyword and handled 823 | by the `notifications' style.") 824 | 825 | (when (featurep 'notifications) 826 | (defun alert-notifications-notify (info) 827 | "Show the alert defined by INFO with `notifications-notify'." 828 | (let ((id (notifications-notify :title (plist-get info :title) 829 | :body (plist-get info :message) 830 | :app-icon (plist-get info :icon) 831 | :timeout (if (plist-get info :persistent) 0 -1) 832 | :replaces-id (gethash (plist-get info :id) alert-notifications-ids) 833 | :urgency (cdr (assq (plist-get info :severity) 834 | alert-notifications-priorities)) 835 | :actions '("default" "Open corresponding buffer") 836 | :on-action (lambda (id action) 837 | (when (string= action "default") 838 | (switch-to-buffer (plist-get info :buffer))))))) 839 | (when (plist-get info :id) 840 | (puthash (plist-get info :id) id alert-notifications-ids))) 841 | (alert-message-notify info)) 842 | 843 | (defun alert-notifications-remove (info) 844 | "Remove the `notifications-notify' message based on INFO :id." 845 | (let ((id (and (plist-get info :id) 846 | (gethash (plist-get info :id) alert-notifications-ids)))) 847 | (when id 848 | (notifications-close-notification id) 849 | (remhash (plist-get info :id) alert-notifications-ids)))) 850 | 851 | (alert-define-style 'notifications :title "Notify using notifications" 852 | :notifier #'alert-notifications-notify)) 853 | 854 | 855 | (defcustom alert-notifier-command (executable-find "terminal-notifier") 856 | "Path to the terminal-notifier command. 857 | From https://github.com/julienXX/terminal-notifier." 858 | :type 'file 859 | :group 'alert) 860 | 861 | (defcustom alert-notifier-default-icon 862 | (concat data-directory 863 | "images/icons/hicolor/128x128/apps/emacs.png") 864 | "Filename of default icon to show for terminal-notifier alerts." 865 | :type 'string 866 | :group 'alert) 867 | 868 | (defun alert-notifier-notify (info) 869 | (if alert-notifier-command 870 | (let ((args 871 | (list "-title" (alert-encode-string (plist-get info :title)) 872 | "-appIcon" (or (plist-get info :icon) alert-notifier-default-icon) 873 | "-message" (alert-encode-string (plist-get info :message))))) 874 | (apply #'call-process alert-notifier-command nil nil nil args)) 875 | (alert-message-notify info))) 876 | 877 | (alert-define-style 'notifier :title "Notify using terminal-notifier" 878 | :notifier #'alert-notifier-notify) 879 | 880 | (defun alert-osx-notifier-notify (info) 881 | (apply #'call-process "osascript" nil nil nil "-e" 882 | (list (format "display notification %S with title %S" 883 | (alert-encode-string (plist-get info :message)) 884 | (alert-encode-string (plist-get info :title))))) 885 | (alert-message-notify info)) 886 | 887 | (when (fboundp 'do-applescript) 888 | ;; Use built-in AppleScript support when possible. 889 | (defun alert-osx-notifier-notify (info) 890 | (do-applescript (format "display notification %S with title %S" 891 | (alert-encode-string (plist-get info :message)) 892 | (alert-encode-string (plist-get info :title)))) 893 | (alert-message-notify info))) 894 | 895 | (alert-define-style 'osx-notifier :title "Notify using native OSX notification" :notifier #'alert-osx-notifier-notify) 896 | 897 | (defun alert-frame-notify (info) 898 | (let ((buf (plist-get info :buffer))) 899 | (if (eq (alert-buffer-status buf) 'buried) 900 | (let ((current-frame (selected-frame))) 901 | (with-selected-frame 902 | (make-frame '((width . 80) 903 | (height . 20) 904 | (top . -1) 905 | (left . 0) 906 | (left-fringe . 0) 907 | (right-fringe . 0) 908 | (tool-bar-lines . nil) 909 | (menu-bar-lines . nil) 910 | (vertical-scroll-bars . nil) 911 | (unsplittable . t) 912 | (has-modeline-p . nil) 913 | (minibuffer . nil))) 914 | (switch-to-buffer buf) 915 | ;;(set (make-local-variable 'mode-line-format) nil) 916 | (nconc info (list :frame (selected-frame)))) 917 | (select-frame current-frame))))) 918 | 919 | (defun alert-frame-remove (info) 920 | (unless (eq this-command 'handle-switch-frame) 921 | (delete-frame (plist-get info :frame) t))) 922 | 923 | ;; This code was kindly borrowed from Arne Babenhauserheide: 924 | ;; http://www.draketo.de/proj/babcore/#sec-3-14-2 925 | (defun x-urgency-hint (frame arg &optional source) 926 | "Set the x-urgency hint for FRAME to ARG. 927 | 928 | - If arg is nil, unset the urgency. 929 | - If arg is any other value, set the urgency. 930 | 931 | If you unset the urgency, you still have to visit the frame to make the urgency 932 | setting disappear (at least in KDE)." 933 | (let* ((wm-hints (append (x-window-property 934 | "WM_HINTS" frame "WM_HINTS" 935 | source nil t) nil)) 936 | (flags (car wm-hints))) 937 | (setcar wm-hints 938 | (if arg 939 | (logior flags #x00000100) 940 | (logand flags #x1ffffeff))) 941 | (x-change-window-property "WM_HINTS" wm-hints frame "WM_HINTS" 32 t))) 942 | 943 | (defun x-urgent (&optional arg) 944 | "Mark the current Emacs frame as requiring urgent attention. 945 | 946 | With non-nil ARG, remove the urgency flag (which might or might 947 | not change display, depending on the window manager)." 948 | (interactive "P") 949 | (let ((frame (car (car (cdr (current-frame-configuration)))))) 950 | (x-urgency-hint frame (not arg)))) 951 | 952 | (defun alert-x11-notify (_info) 953 | "Call `x-urgent'." 954 | (x-urgent)) 955 | 956 | (alert-define-style 'x11 :title "Set the X11 window property" 957 | :notifier #'alert-x11-notify) 958 | 959 | 960 | (defcustom alert-toaster-default-icon 961 | (let ((exec-bin (executable-find "emacs.exe"))) 962 | (cond (exec-bin 963 | (concat (file-name-directory exec-bin) "../share/icons/hicolor/128x128/apps/emacs.png")) 964 | (t nil))) 965 | "Icon file using toaster." 966 | :type 'string 967 | :group 'alert 968 | ) 969 | 970 | (defcustom alert-toaster-command (executable-find "toast") 971 | "Path to the toast command. 972 | This is found at https://github.com/nels-o/toaster." 973 | :type 'file 974 | :group 'alert 975 | ) 976 | 977 | (defun alert-toaster-notify (info) 978 | (if alert-toaster-command 979 | (let ((args (list 980 | "-t" (alert-encode-string (plist-get info :title)) 981 | "-m" (alert-encode-string (plist-get info :message)) 982 | "-p" (expand-file-name (or (plist-get info :icon) alert-toaster-default-icon)) 983 | ))) 984 | (apply #'call-process alert-toaster-command nil nil nil args)) 985 | (alert-message-notify info))) 986 | 987 | (alert-define-style 'toaster :title "Notify using Toaster" 988 | :notifier #'alert-toaster-notify) 989 | 990 | (defcustom alert-termux-command (executable-find "termux-notification") 991 | "Path to the termux-notification command. 992 | This is found in the termux-api package, and it requires the Termux 993 | API addon app to be installed." 994 | :type 'file 995 | :group 'alert) 996 | 997 | (defun alert-termux-notify (info) 998 | "Send INFO using termux-notification. 999 | Handles :TITLE and :MESSAGE keywords from the 1000 | INFO plist." 1001 | (if alert-termux-command 1002 | (let ((args (nconc 1003 | (when (plist-get info :title) 1004 | (list "-t" (alert-encode-string (plist-get info :title)))) 1005 | (list "-c" (alert-encode-string (plist-get info :message)))))) 1006 | (apply #'call-process alert-termux-command nil 1007 | (list (get-buffer-create " *termux-notification output*") t) 1008 | nil args)) 1009 | (alert-message-notify info))) 1010 | 1011 | (alert-define-style 'termux :title "Notify using termux" 1012 | :notifier #'alert-termux-notify) 1013 | 1014 | ;; jww (2011-08-25): Not quite working yet 1015 | ;;(alert-define-style 'frame :title "Popup buffer in a frame" 1016 | ;; :notifier #'alert-frame-notify 1017 | ;; :remover #'alert-frame-remove) 1018 | 1019 | (defun alert-buffer-status (&optional buffer) 1020 | (with-current-buffer (or buffer (current-buffer)) 1021 | (let ((wind (get-buffer-window))) 1022 | (if wind 1023 | (if (eq wind (selected-window)) 1024 | (if (and (current-idle-time) 1025 | (> (float-time (current-idle-time)) 1026 | alert-reveal-idle-time)) 1027 | 'idle 1028 | 'selected) 1029 | 'visible) 1030 | 'buried)))) 1031 | 1032 | (defvar alert-active-alerts nil) 1033 | 1034 | (defun alert-remove-when-active (remover info) 1035 | (let ((idle-time (and (current-idle-time) 1036 | (float-time (current-idle-time))))) 1037 | (cond 1038 | ((and idle-time (> idle-time alert-persist-idle-time))) 1039 | ((and idle-time (> idle-time alert-reveal-idle-time)) 1040 | (run-with-timer alert-fade-time nil 1041 | #'alert-remove-when-active remover info)) 1042 | (t 1043 | (funcall remover info))))) 1044 | 1045 | (defun alert-remove-on-command () 1046 | (let (to-delete) 1047 | (dolist (alert alert-active-alerts) 1048 | (when (eq (current-buffer) (nth 0 alert)) 1049 | (push alert to-delete) 1050 | (if (nth 2 alert) 1051 | (funcall (nth 2 alert) (nth 1 alert))))) 1052 | (dolist (alert to-delete) 1053 | (setq alert-active-alerts (delq alert alert-active-alerts))))) 1054 | 1055 | (defun alert-send-notification 1056 | (alert-buffer info style-def &optional persist never-per) 1057 | (let ((notifier (plist-get style-def :notifier))) 1058 | (if notifier 1059 | (funcall notifier info))) 1060 | (let ((remover (plist-get style-def :remover))) 1061 | (add-to-list 'alert-active-alerts (list alert-buffer info remover)) 1062 | (with-current-buffer alert-buffer 1063 | (add-hook 'post-command-hook #'alert-remove-on-command nil t)) 1064 | (if (and remover (or (not persist) never-per)) 1065 | (run-with-timer alert-fade-time nil 1066 | #'alert-remove-when-active 1067 | remover info)))) 1068 | 1069 | ;;;###autoload 1070 | (cl-defun alert (message &key (severity 'normal) title icon category 1071 | buffer mode data style persistent never-persist 1072 | id) 1073 | "Alert the user that something has happened. 1074 | MESSAGE is what the user will see. You may also use keyword 1075 | arguments to specify additional details. Here is a full example: 1076 | 1077 | \(alert \"This is a message\" 1078 | :severity \\='high ;; The default severity is `normal' 1079 | :title \"Title\" ;; An optional title 1080 | :category \\='example ;; A symbol to identify the message 1081 | :mode \\='text-mode ;; Normally determined automatically 1082 | :buffer (current-buffer) ;; This is the default 1083 | :data nil ;; Unused by alert.el itself 1084 | :persistent nil ;; Force the alert to be persistent; 1085 | ;; it is best not to use this 1086 | :never-persist nil ;; Force this alert to never persist 1087 | :id \\='my-id) ;; Used to replace previous message of 1088 | ;; the same id in styles that support it 1089 | :style \\='fringe) ;; Force a given style to be used; 1090 | ;; this is only for debugging! 1091 | :icon \\=\"mail-message-new\" ;; if style supports icon then add icon 1092 | ;; name or path here 1093 | 1094 | If no :title is given, the buffer-name of :buffer is used. If 1095 | :buffer is nil, it is the current buffer at the point of call. 1096 | 1097 | :data is an opaque value which modules can pass through to their 1098 | own styles if they wish. 1099 | 1100 | Here are some more typical examples of usage: 1101 | 1102 | ;; This is the most basic form usage 1103 | (alert \"This is an alert\") 1104 | 1105 | ;; You can adjust the severity for more important messages 1106 | (alert \"This is an alert\" :severity \\='high) 1107 | 1108 | ;; Or decrease it for purely informative ones 1109 | (alert \"This is an alert\" :severity \\='trivial) 1110 | 1111 | ;; Alerts can have optional titles. Otherwise, the title is the 1112 | ;; buffer-name of the (current-buffer) where the alert originated. 1113 | (alert \"This is an alert\" :title \"My Alert\") 1114 | 1115 | ;; Further, alerts can have categories. This allows users to 1116 | ;; selectively filter on them. 1117 | (alert \"This is an alert\" :title \"My Alert\" 1118 | :category \\='some-category-or-other)" 1119 | (cl-destructuring-bind 1120 | (alert-buffer current-major-mode current-buffer-status 1121 | current-buffer-name) 1122 | (with-current-buffer (or buffer (current-buffer)) 1123 | (list (current-buffer) 1124 | (or mode major-mode) 1125 | (alert-buffer-status) 1126 | (buffer-name))) 1127 | 1128 | (let ((base-info (list :message message 1129 | :title (or title current-buffer-name) 1130 | :icon icon 1131 | :severity severity 1132 | :category category 1133 | :buffer alert-buffer 1134 | :persistent persistent 1135 | :mode current-major-mode 1136 | :id id 1137 | :data data 1138 | :persistent persistent)) 1139 | matched) 1140 | 1141 | (if alert-log-messages 1142 | (alert-log-notify base-info)) 1143 | 1144 | (unless alert-hide-all-notifications 1145 | (catch 'finish 1146 | (dolist (config (or (append alert-user-configuration 1147 | alert-internal-configuration) 1148 | (when style '(nil)))) 1149 | (let* ((style-def (cdr (assq (or style (nth 1 config)) 1150 | alert-styles))) 1151 | (options (nth 2 config)) 1152 | (persist-p (or persistent 1153 | (cdr (assq :persistent options)))) 1154 | (persist (if (functionp persist-p) 1155 | (funcall persist-p base-info) 1156 | persist-p)) 1157 | (never-persist-p 1158 | (or never-persist 1159 | (cdr (assq :never-persist options)))) 1160 | (never-per (if (functionp never-persist-p) 1161 | (funcall never-persist-p base-info) 1162 | never-persist-p)) 1163 | (continue (cdr (assq :continue options))) 1164 | info) 1165 | (setq info (if (not (memq :persistent base-info)) 1166 | (append base-info (list :persistent persist)) 1167 | base-info) 1168 | info (if (not (memq :never-persist info)) 1169 | (append info (list :never-persist never-per)) 1170 | info)) 1171 | (when 1172 | (or style ; :style always "matches", for testing 1173 | (not 1174 | (memq 1175 | nil 1176 | (mapcar 1177 | #'(lambda (condition) 1178 | (cl-case (car condition) 1179 | (:severity 1180 | (memq severity (cdr condition))) 1181 | (:status 1182 | (memq current-buffer-status (cdr condition))) 1183 | (:mode 1184 | (string-match 1185 | (cdr condition) 1186 | (symbol-name current-major-mode))) 1187 | (:category 1188 | (and category (string-match 1189 | (cdr condition) 1190 | (if (stringp category) 1191 | category 1192 | (symbol-name category))))) 1193 | (:title 1194 | (and title 1195 | (string-match (cdr condition) title))) 1196 | (:message 1197 | (string-match (cdr condition) message)) 1198 | (:predicate 1199 | (funcall (cdr condition) info)) 1200 | (:icon 1201 | (string-match (cdr condition) icon)))) 1202 | (nth 0 config))))) 1203 | 1204 | (alert-send-notification alert-buffer info style-def 1205 | persist never-per) 1206 | (setq matched t) 1207 | (if (or style (not (if (functionp continue) 1208 | (funcall continue info) 1209 | continue))) 1210 | (throw 'finish t))))))) 1211 | 1212 | (if (and (not matched) alert-default-style) 1213 | (alert-send-notification alert-buffer base-info 1214 | (cdr (assq alert-default-style 1215 | alert-styles))))))) 1216 | 1217 | (provide 'alert) 1218 | 1219 | ;;; alert.el ends here 1220 | --------------------------------------------------------------------------------