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