Merge from trunk
[bpt/emacs.git] / lisp / notifications.el
CommitLineData
41a86354
MA
1;;; notifications.el --- Client interface to desktop notifications.
2
73b0cd50 3;; Copyright (C) 2010-2011 Free Software Foundation, Inc.
41a86354
MA
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
42343747
JD
98(when (fboundp 'dbus-register-signal)
99 (dbus-register-signal
100 :session
101 notifications-service
102 notifications-path
103 notifications-interface
104 notifications-action-signal
105 'notifications-on-action-signal))
41a86354
MA
106
107(defun notifications-on-closed-signal (id reason)
fa4003da 108 "Dispatch signals to callback functions from `notifications-on-closed-map'."
41a86354
MA
109 (let ((entry (assoc id notifications-on-close-map)))
110 (when entry
fa4003da
MA
111 (funcall (cadr entry)
112 id (cadr (assoc reason notifications-closed-reason)))
41a86354
MA
113 (remove entry 'notifications-on-close-map))))
114
42343747
JD
115(when (fboundp 'dbus-register-signal)
116 (dbus-register-signal
117 :session
118 notifications-service
119 notifications-path
120 notifications-interface
121 notifications-closed-signal
122 'notifications-on-closed-signal))
41a86354
MA
123
124(defun notifications-notify (&rest params)
125 "Send notification via D-Bus using the Freedesktop notification protocol.
126Various PARAMS can be set:
127
128 :title The notification title.
129 :body The notification body text.
130 :app-name The name of the application sending the notification.
131 Default to `notifications-application-name'.
132 :replaces-id The notification ID that this notification replaces.
133 :app-icon The notification icon.
134 Default is `notifications-application-icon'.
135 Set to nil if you do not want any icon displayed.
136 :actions A list of actions in the form:
137 (KEY TITLE KEY TITLE ...)
138 where KEY and TITLE are both strings.
b978141d
JB
139 The default action (usually invoked by clicking the
140 notification) should have a key named \"default\".
890a18d6 141 The title can be anything, though implementations are free
b978141d 142 not to display it.
41a86354
MA
143 :timeout The timeout time in milliseconds since the display
144 of the notification at which the notification should
145 automatically close.
146 If -1, the notification's expiration time is dependent
147 on the notification server's settings, and may vary for
148 the type of notification.
149 If 0, the notification never expires.
150 Default value is -1.
151 :urgency The urgency level.
152 Either `low', `normal' or `critical'.
153 :category The type of notification this is.
154 :desktop-entry This specifies the name of the desktop filename representing
155 the calling program.
156 :image-data This is a raw data image format which describes the width,
157 height, rowstride, has alpha, bits per sample, channels and
158 image data respectively.
7ea2d383
MA
159 :image-path This is represented either as a URI (file:// is the
160 only URI schema supported right now) or a name
161 in a freedesktop.org-compliant icon theme.
41a86354 162 :sound-file The path to a sound file to play when the notification pops up.
7ea2d383
MA
163 :sound-name A themeable named sound from the freedesktop.org sound naming
164 specification to play when the notification pops up.
165 Similar to icon-name,only for sounds. An example would
166 be \"message-new-instant\".
41a86354
MA
167 :suppress-sound Causes the server to suppress playing any sounds, if it has
168 that ability.
169 :x Specifies the X location on the screen that the notification
b978141d 170 should point to. The \"y\" hint must also be specified.
41a86354 171 :y Specifies the Y location on the screen that the notification
b978141d 172 should point to. The \"x\" hint must also be specified.
fa4003da
MA
173 :on-action Function to call when an action is invoked.
174 The notification id and the key of the action are passed
175 as arguments to the function.
41a86354
MA
176 :on-close Function to call when the notification has been closed
177 by timeout or by the user.
fa4003da
MA
178 The function receive the notification id and the closing
179 reason as arguments:
41a86354
MA
180 - `expired' if the notification has expired
181 - `dismissed' if the notification was dismissed by the user
182 - `close-notification' if the notification was closed
183 by a call to CloseNotification
184
185This function returns a notification id, an integer, which can be
186used to manipulate the notification item with
187`notifications-close'."
188 (let ((title (plist-get params :title))
189 (body (plist-get params :body))
190 (app-name (plist-get params :app-name))
191 (replaces-id (plist-get params :replaces-id))
192 (app-icon (plist-get params :app-icon))
193 (actions (plist-get params :actions))
194 (timeout (plist-get params :timeout))
195 ;; Hints
196 (hints '())
197 (urgency (plist-get params :urgency))
198 (category (plist-get params :category))
199 (desktop-entry (plist-get params :desktop-entry))
200 (image-data (plist-get params :image-data))
7ea2d383 201 (image-path (plist-get params :image-path))
41a86354 202 (sound-file (plist-get params :sound-file))
7ea2d383 203 (sound-name (plist-get params :sound-name))
41a86354
MA
204 (suppress-sound (plist-get params :suppress-sound))
205 (x (plist-get params :x))
206 (y (plist-get params :y))
207 id)
208 ;; Build hints array
209 (when urgency
210 (add-to-list 'hints `(:dict-entry
211 "urgency"
212 (:variant :byte ,(case urgency
0adf5618
SM
213 (low 0)
214 (critical 2)
41a86354
MA
215 (t 1)))) t))
216 (when category
217 (add-to-list 'hints `(:dict-entry
218 "category"
219 (:variant :string ,category)) t))
220 (when desktop-entry
221 (add-to-list 'hints `(:dict-entry
222 "desktop-entry"
223 (:variant :string ,desktop-entry)) t))
224 (when image-data
225 (add-to-list 'hints `(:dict-entry
226 "image_data"
227 (:variant :struct ,image-data)) t))
7ea2d383
MA
228 (when image-path
229 (add-to-list 'hints `(:dict-entry
230 "image_path"
231 (:variant :string ,image-path)) t))
41a86354
MA
232 (when sound-file
233 (add-to-list 'hints `(:dict-entry
234 "sound-file"
235 (:variant :string ,sound-file)) t))
7ea2d383
MA
236 (when sound-name
237 (add-to-list 'hints `(:dict-entry
238 "sound-name"
239 (:variant :string ,sound-name)) t))
41a86354
MA
240 (when suppress-sound
241 (add-to-list 'hints `(:dict-entry
242 "suppress-sound"
243 (:variant :boolean ,suppress-sound)) t))
244 (when x
245 (add-to-list 'hints `(:dict-entry "x" (:variant :int32 ,x)) t))
246 (when y
247 (add-to-list 'hints `(:dict-entry "y" (:variant :int32 ,y)) t))
248
249 ;; Call Notify method
250 (setq id
251 (dbus-call-method :session
252 notifications-service
253 notifications-path
254 notifications-interface
255 notifications-notify-method
256 :string (or app-name
257 notifications-application-name)
258 :uint32 (or replaces-id 0)
259 :string (if app-icon
260 (expand-file-name app-icon)
261 ;; If app-icon is nil because user
262 ;; requested it to be so, send the
263 ;; empty string
264 (if (plist-member params :app-icon)
265 ""
266 ;; Otherwise send the default icon path
267 notifications-application-icon))
268 :string (or title "")
269 :string (or body "")
270 `(:array ,@actions)
271 (or hints '(:array :signature "{sv}"))
272 :int32 (or timeout -1)))
273
274 ;; Register close/action callback function
275 (let ((on-action (plist-get params :on-action))
276 (on-close (plist-get params :on-close)))
277 (when on-action
278 (add-to-list 'notifications-on-action-map (list id on-action)))
279 (when on-close
280 (add-to-list 'notifications-on-close-map (list id on-close))))
281
282 ;; Return notification id
283 id))
284
285(defun notifications-close-notification (id)
286 "Close a notification with identifier ID."
287 (dbus-call-method :session
288 notifications-service
289 notifications-path
290 notifications-interface
291 notifications-close-notification-method
292 :int32 id))
293
294(provide 'notifications)