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