lispref/markers.texi small change
[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'."
a41a6cf4
MA
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
fa4003da 99 (funcall (cadr entry) id action)
a41a6cf4 100 (remove entry notifications-on-action-map))))
41a86354 101
42343747
JD
102(when (fboundp 'dbus-register-signal)
103 (dbus-register-signal
104 :session
ebeabff4 105 nil
42343747
JD
106 notifications-path
107 notifications-interface
108 notifications-action-signal
109 'notifications-on-action-signal))
41a86354 110
5b77774d 111(defun notifications-on-closed-signal (id &optional reason)
fa4003da 112 "Dispatch signals to callback functions from `notifications-on-closed-map'."
5b77774d
MA
113 ;; notification-daemon prior 0.4.0 does not send a reason. So we
114 ;; make it optional, and assume `undefined' as default.
a41a6cf4
MA
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
fa4003da
MA
119 (funcall (cadr entry)
120 id (cadr (assoc reason notifications-closed-reason)))
a41a6cf4 121 (remove entry notifications-on-close-map))))
41a86354 122
42343747
JD
123(when (fboundp 'dbus-register-signal)
124 (dbus-register-signal
125 :session
ebeabff4 126 nil
42343747
JD
127 notifications-path
128 notifications-interface
129 notifications-closed-signal
130 'notifications-on-closed-signal))
41a86354
MA
131
132(defun notifications-notify (&rest params)
133 "Send notification via D-Bus using the Freedesktop notification protocol.
134Various 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.
b978141d
JB
147 The default action (usually invoked by clicking the
148 notification) should have a key named \"default\".
890a18d6 149 The title can be anything, though implementations are free
b978141d 150 not to display it.
41a86354
MA
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.
7ea2d383
MA
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.
41a86354 170 :sound-file The path to a sound file to play when the notification pops up.
40ba43b4 171 :sound-name A themable named sound from the freedesktop.org sound naming
7ea2d383
MA
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\".
41a86354
MA
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
b978141d 178 should point to. The \"y\" hint must also be specified.
41a86354 179 :y Specifies the Y location on the screen that the notification
b978141d 180 should point to. The \"x\" hint must also be specified.
fa4003da
MA
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.
41a86354
MA
184 :on-close Function to call when the notification has been closed
185 by timeout or by the user.
fa4003da
MA
186 The function receive the notification id and the closing
187 reason as arguments:
41a86354
MA
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
193This function returns a notification id, an integer, which can be
194used to manipulate the notification item with
79adf8c8 195`notifications-close-notification'."
41a86354
MA
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))
7ea2d383 209 (image-path (plist-get params :image-path))
41a86354 210 (sound-file (plist-get params :sound-file))
7ea2d383 211 (sound-name (plist-get params :sound-name))
41a86354
MA
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
0adf5618
SM
221 (low 0)
222 (critical 2)
41a86354
MA
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))
7ea2d383
MA
236 (when image-path
237 (add-to-list 'hints `(:dict-entry
238 "image_path"
239 (:variant :string ,image-path)) t))
41a86354
MA
240 (when sound-file
241 (add-to-list 'hints `(:dict-entry
242 "sound-file"
243 (:variant :string ,sound-file)) t))
7ea2d383
MA
244 (when sound-name
245 (add-to-list 'hints `(:dict-entry
246 "sound-name"
247 (:variant :string ,sound-name)) t))
41a86354
MA
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
db976e3c
MA
282 ;; Register close/action callback function. We must also remember
283 ;; the daemon's unique name, because the daemon could have
284 ;; restarted.
41a86354 285 (let ((on-action (plist-get params :on-action))
a41a6cf4
MA
286 (on-close (plist-get params :on-close))
287 (unique-name (dbus-get-name-owner :session notifications-service)))
41a86354 288 (when on-action
a41a6cf4
MA
289 (add-to-list 'notifications-on-action-map
290 (list (cons unique-name id) on-action)))
41a86354 291 (when on-close
a41a6cf4
MA
292 (add-to-list 'notifications-on-close-map
293 (list (cons unique-name id) on-close))))
41a86354
MA
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)