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