Cosmetic doc fixes for eshell.
[bpt/emacs.git] / lisp / notifications.el
CommitLineData
41a86354
MA
1;;; notifications.el --- Client interface to desktop notifications.
2
3;; Copyright (C) 2010 Free Software Foundation, Inc.
4
5;; Author: Julien Danjou <julien@danjou.info>
6;; Keywords: comm desktop notifications
7
8;; This file is part of GNU Emacs.
9
10;; GNU Emacs is free software: you can redistribute it and/or modify
11;; it under the terms of the GNU General Public License as published by
12;; the Free Software Foundation, either version 3 of the License, or
13;; (at your option) any later version.
14
15;; GNU Emacs is distributed in the hope that it will be useful,
16;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18;; GNU General Public License for more details.
19
20;; You should have received a copy of the GNU General Public License
21;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
22
23;;; Commentary:
24
25;; This package provides an implementation of the Desktop Notifications
26;; <http://www.galago-project.org/specs/notification/>.
27
28;; In order to activate this package, you must add the following code
29;; into your .emacs:
30;;
31;; (require 'notifications)
32
33;;; Code:
34(eval-when-compile
35 (require 'cl))
36
37;; Pacify byte-compiler. D-Bus support in the Emacs core can be
38;; disabled with configuration option "--without-dbus". Declare used
39;; subroutines and variables of `dbus' therefore.
40(declare-function dbus-call-method "dbusbind.c")
b978141d 41(declare-function dbus-register-signal "dbusbind.c")
41a86354
MA
42
43(require 'dbus)
44
7ea2d383
MA
45(defconst notifications-specification-version "1.1"
46 "The version of the Desktop Notifications Specification implemented.")
47
41a86354
MA
48(defconst notifications-application-name "Emacs"
49 "Default application name.")
50
51(defconst notifications-application-icon
52 (expand-file-name
53 "images/icons/hicolor/scalable/apps/emacs.svg"
54 data-directory)
55 "Default application icon.")
56
57(defconst notifications-service "org.freedesktop.Notifications"
58 "D-Bus notifications service name.")
59
60(defconst notifications-path "/org/freedesktop/Notifications"
61 "D-Bus notifications service path.")
62
63(defconst notifications-interface "org.freedesktop.Notifications"
64 "D-Bus notifications service path.")
65
66(defconst notifications-notify-method "Notify"
67 "D-Bus notifications service path.")
68
69(defconst notifications-close-notification-method "CloseNotification"
70 "D-Bus notifications service path.")
71
72(defconst notifications-action-signal "ActionInvoked"
73 "D-Bus notifications action signal.")
74
75(defconst notifications-closed-signal "NotificationClosed"
76 "D-Bus notifications closed signal.")
77
78(defconst notifications-closed-reason
79 '((1 expired)
80 (2 dismissed)
81 (3 close-notification)
82 (4 undefined))
83 "List of reasons why a notification has been closed.")
84
85(defvar notifications-on-action-map nil
86 "Mapping between notification and action callback functions.")
87
88(defvar notifications-on-close-map nil
89 "Mapping between notification and close callback functions.")
90
91(defun notifications-on-action-signal (id action)
fa4003da 92 "Dispatch signals to callback functions from `notifications-on-action-map'."
41a86354
MA
93 (let ((entry (assoc id notifications-on-action-map)))
94 (when entry
fa4003da 95 (funcall (cadr entry) id action)
41a86354
MA
96 (remove entry 'notifications-on-action-map))))
97
98(dbus-register-signal
99 :session
100 notifications-service
101 notifications-path
102 notifications-interface
103 notifications-action-signal
104 'notifications-on-action-signal)
105
106(defun notifications-on-closed-signal (id reason)
fa4003da 107 "Dispatch signals to callback functions from `notifications-on-closed-map'."
41a86354
MA
108 (let ((entry (assoc id notifications-on-close-map)))
109 (when entry
fa4003da
MA
110 (funcall (cadr entry)
111 id (cadr (assoc reason notifications-closed-reason)))
41a86354
MA
112 (remove entry 'notifications-on-close-map))))
113
114(dbus-register-signal
115 :session
116 notifications-service
117 notifications-path
118 notifications-interface
119 notifications-closed-signal
120 'notifications-on-closed-signal)
121
122(defun notifications-notify (&rest params)
123 "Send notification via D-Bus using the Freedesktop notification protocol.
124Various PARAMS can be set:
125
126 :title The notification title.
127 :body The notification body text.
128 :app-name The name of the application sending the notification.
129 Default to `notifications-application-name'.
130 :replaces-id The notification ID that this notification replaces.
131 :app-icon The notification icon.
132 Default is `notifications-application-icon'.
133 Set to nil if you do not want any icon displayed.
134 :actions A list of actions in the form:
135 (KEY TITLE KEY TITLE ...)
136 where KEY and TITLE are both strings.
b978141d
JB
137 The default action (usually invoked by clicking the
138 notification) should have a key named \"default\".
890a18d6 139 The title can be anything, though implementations are free
b978141d 140 not to display it.
41a86354
MA
141 :timeout The timeout time in milliseconds since the display
142 of the notification at which the notification should
143 automatically close.
144 If -1, the notification's expiration time is dependent
145 on the notification server's settings, and may vary for
146 the type of notification.
147 If 0, the notification never expires.
148 Default value is -1.
149 :urgency The urgency level.
150 Either `low', `normal' or `critical'.
151 :category The type of notification this is.
152 :desktop-entry This specifies the name of the desktop filename representing
153 the calling program.
154 :image-data This is a raw data image format which describes the width,
155 height, rowstride, has alpha, bits per sample, channels and
156 image data respectively.
7ea2d383
MA
157 :image-path This is represented either as a URI (file:// is the
158 only URI schema supported right now) or a name
159 in a freedesktop.org-compliant icon theme.
41a86354 160 :sound-file The path to a sound file to play when the notification pops up.
7ea2d383
MA
161 :sound-name A themeable named sound from the freedesktop.org sound naming
162 specification to play when the notification pops up.
163 Similar to icon-name,only for sounds. An example would
164 be \"message-new-instant\".
41a86354
MA
165 :suppress-sound Causes the server to suppress playing any sounds, if it has
166 that ability.
167 :x Specifies the X location on the screen that the notification
b978141d 168 should point to. The \"y\" hint must also be specified.
41a86354 169 :y Specifies the Y location on the screen that the notification
b978141d 170 should point to. The \"x\" hint must also be specified.
fa4003da
MA
171 :on-action Function to call when an action is invoked.
172 The notification id and the key of the action are passed
173 as arguments to the function.
41a86354
MA
174 :on-close Function to call when the notification has been closed
175 by timeout or by the user.
fa4003da
MA
176 The function receive the notification id and the closing
177 reason as arguments:
41a86354
MA
178 - `expired' if the notification has expired
179 - `dismissed' if the notification was dismissed by the user
180 - `close-notification' if the notification was closed
181 by a call to CloseNotification
182
183This function returns a notification id, an integer, which can be
184used to manipulate the notification item with
185`notifications-close'."
186 (let ((title (plist-get params :title))
187 (body (plist-get params :body))
188 (app-name (plist-get params :app-name))
189 (replaces-id (plist-get params :replaces-id))
190 (app-icon (plist-get params :app-icon))
191 (actions (plist-get params :actions))
192 (timeout (plist-get params :timeout))
193 ;; Hints
194 (hints '())
195 (urgency (plist-get params :urgency))
196 (category (plist-get params :category))
197 (desktop-entry (plist-get params :desktop-entry))
198 (image-data (plist-get params :image-data))
7ea2d383 199 (image-path (plist-get params :image-path))
41a86354 200 (sound-file (plist-get params :sound-file))
7ea2d383 201 (sound-name (plist-get params :sound-name))
41a86354
MA
202 (suppress-sound (plist-get params :suppress-sound))
203 (x (plist-get params :x))
204 (y (plist-get params :y))
205 id)
206 ;; Build hints array
207 (when urgency
208 (add-to-list 'hints `(:dict-entry
209 "urgency"
210 (:variant :byte ,(case urgency
211 ('low 0)
212 ('critical 2)
213 (t 1)))) t))
214 (when category
215 (add-to-list 'hints `(:dict-entry
216 "category"
217 (:variant :string ,category)) t))
218 (when desktop-entry
219 (add-to-list 'hints `(:dict-entry
220 "desktop-entry"
221 (:variant :string ,desktop-entry)) t))
222 (when image-data
223 (add-to-list 'hints `(:dict-entry
224 "image_data"
225 (:variant :struct ,image-data)) t))
7ea2d383
MA
226 (when image-path
227 (add-to-list 'hints `(:dict-entry
228 "image_path"
229 (:variant :string ,image-path)) t))
41a86354
MA
230 (when sound-file
231 (add-to-list 'hints `(:dict-entry
232 "sound-file"
233 (:variant :string ,sound-file)) t))
7ea2d383
MA
234 (when sound-name
235 (add-to-list 'hints `(:dict-entry
236 "sound-name"
237 (:variant :string ,sound-name)) t))
41a86354
MA
238 (when suppress-sound
239 (add-to-list 'hints `(:dict-entry
240 "suppress-sound"
241 (:variant :boolean ,suppress-sound)) t))
242 (when x
243 (add-to-list 'hints `(:dict-entry "x" (:variant :int32 ,x)) t))
244 (when y
245 (add-to-list 'hints `(:dict-entry "y" (:variant :int32 ,y)) t))
246
247 ;; Call Notify method
248 (setq id
249 (dbus-call-method :session
250 notifications-service
251 notifications-path
252 notifications-interface
253 notifications-notify-method
254 :string (or app-name
255 notifications-application-name)
256 :uint32 (or replaces-id 0)
257 :string (if app-icon
258 (expand-file-name app-icon)
259 ;; If app-icon is nil because user
260 ;; requested it to be so, send the
261 ;; empty string
262 (if (plist-member params :app-icon)
263 ""
264 ;; Otherwise send the default icon path
265 notifications-application-icon))
266 :string (or title "")
267 :string (or body "")
268 `(:array ,@actions)
269 (or hints '(:array :signature "{sv}"))
270 :int32 (or timeout -1)))
271
272 ;; Register close/action callback function
273 (let ((on-action (plist-get params :on-action))
274 (on-close (plist-get params :on-close)))
275 (when on-action
276 (add-to-list 'notifications-on-action-map (list id on-action)))
277 (when on-close
278 (add-to-list 'notifications-on-close-map (list id on-close))))
279
280 ;; Return notification id
281 id))
282
283(defun notifications-close-notification (id)
284 "Close a notification with identifier ID."
285 (dbus-call-method :session
286 notifications-service
287 notifications-path
288 notifications-interface
289 notifications-close-notification-method
290 :int32 id))
291
292(provide 'notifications)