Update to Unicode 6.0.0 beta.
[bpt/emacs.git] / lisp / net / notifications.el
CommitLineData
41a86354
MA
1;;; notifications.el --- Client interface to desktop notifications.
2
3;; Copyright (C) 2010 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;;; 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
45(defconst notifications-application-name "Emacs"
46 "Default application name.")
47
48(defconst notifications-application-icon
49 (expand-file-name
50 "images/icons/hicolor/scalable/apps/emacs.svg"
51 data-directory)
52 "Default application icon.")
53
54(defconst notifications-service "org.freedesktop.Notifications"
55 "D-Bus notifications service name.")
56
57(defconst notifications-path "/org/freedesktop/Notifications"
58 "D-Bus notifications service path.")
59
60(defconst notifications-interface "org.freedesktop.Notifications"
61 "D-Bus notifications service path.")
62
63(defconst notifications-notify-method "Notify"
64 "D-Bus notifications service path.")
65
66(defconst notifications-close-notification-method "CloseNotification"
67 "D-Bus notifications service path.")
68
69(defconst notifications-action-signal "ActionInvoked"
70 "D-Bus notifications action signal.")
71
72(defconst notifications-closed-signal "NotificationClosed"
73 "D-Bus notifications closed signal.")
74
75(defconst notifications-closed-reason
76 '((1 expired)
77 (2 dismissed)
78 (3 close-notification)
79 (4 undefined))
80 "List of reasons why a notification has been closed.")
81
82(defvar notifications-on-action-map nil
83 "Mapping between notification and action callback functions.")
84
85(defvar notifications-on-close-map nil
86 "Mapping between notification and close callback functions.")
87
88(defun notifications-on-action-signal (id action)
89 (let ((entry (assoc id notifications-on-action-map)))
90 (when entry
91 (funcall (cadr entry) action)
92 (remove entry 'notifications-on-action-map))))
93
94(dbus-register-signal
95 :session
96 notifications-service
97 notifications-path
98 notifications-interface
99 notifications-action-signal
100 'notifications-on-action-signal)
101
102(defun notifications-on-closed-signal (id reason)
103 (let ((entry (assoc id notifications-on-close-map)))
104 (when entry
105 (funcall (cadr entry) (cadr (assoc reason notifications-closed-reason)))
106 (remove entry 'notifications-on-close-map))))
107
108(dbus-register-signal
109 :session
110 notifications-service
111 notifications-path
112 notifications-interface
113 notifications-closed-signal
114 'notifications-on-closed-signal)
115
116(defun notifications-notify (&rest params)
117 "Send notification via D-Bus using the Freedesktop notification protocol.
118Various PARAMS can be set:
119
120 :title The notification title.
121 :body The notification body text.
122 :app-name The name of the application sending the notification.
123 Default to `notifications-application-name'.
124 :replaces-id The notification ID that this notification replaces.
125 :app-icon The notification icon.
126 Default is `notifications-application-icon'.
127 Set to nil if you do not want any icon displayed.
128 :actions A list of actions in the form:
129 (KEY TITLE KEY TITLE ...)
130 where KEY and TITLE are both strings.
b978141d
JB
131 The default action (usually invoked by clicking the
132 notification) should have a key named \"default\".
133 The name can be anything, though implementations are free
134 not to display it.
41a86354
MA
135 :timeout The timeout time in milliseconds since the display
136 of the notification at which the notification should
137 automatically close.
138 If -1, the notification's expiration time is dependent
139 on the notification server's settings, and may vary for
140 the type of notification.
141 If 0, the notification never expires.
142 Default value is -1.
143 :urgency The urgency level.
144 Either `low', `normal' or `critical'.
145 :category The type of notification this is.
146 :desktop-entry This specifies the name of the desktop filename representing
147 the calling program.
148 :image-data This is a raw data image format which describes the width,
149 height, rowstride, has alpha, bits per sample, channels and
150 image data respectively.
151 :sound-file The path to a sound file to play when the notification pops up.
152 :suppress-sound Causes the server to suppress playing any sounds, if it has
153 that ability.
154 :x Specifies the X location on the screen that the notification
b978141d 155 should point to. The \"y\" hint must also be specified.
41a86354 156 :y Specifies the Y location on the screen that the notification
b978141d
JB
157 should point to. The \"x\" hint must also be specified.
158 :on-action Function to call when an action is invoked. The key of the
41a86354
MA
159 action is passed as argument to the function.
160 :on-close Function to call when the notification has been closed
161 by timeout or by the user.
b978141d 162 The function receives the closing reason as argument:
41a86354
MA
163 - `expired' if the notification has expired
164 - `dismissed' if the notification was dismissed by the user
165 - `close-notification' if the notification was closed
166 by a call to CloseNotification
167
168This function returns a notification id, an integer, which can be
169used to manipulate the notification item with
170`notifications-close'."
171 (let ((title (plist-get params :title))
172 (body (plist-get params :body))
173 (app-name (plist-get params :app-name))
174 (replaces-id (plist-get params :replaces-id))
175 (app-icon (plist-get params :app-icon))
176 (actions (plist-get params :actions))
177 (timeout (plist-get params :timeout))
178 ;; Hints
179 (hints '())
180 (urgency (plist-get params :urgency))
181 (category (plist-get params :category))
182 (desktop-entry (plist-get params :desktop-entry))
183 (image-data (plist-get params :image-data))
184 (sound-file (plist-get params :sound-file))
185 (suppress-sound (plist-get params :suppress-sound))
186 (x (plist-get params :x))
187 (y (plist-get params :y))
188 id)
189 ;; Build hints array
190 (when urgency
191 (add-to-list 'hints `(:dict-entry
192 "urgency"
193 (:variant :byte ,(case urgency
194 ('low 0)
195 ('critical 2)
196 (t 1)))) t))
197 (when category
198 (add-to-list 'hints `(:dict-entry
199 "category"
200 (:variant :string ,category)) t))
201 (when desktop-entry
202 (add-to-list 'hints `(:dict-entry
203 "desktop-entry"
204 (:variant :string ,desktop-entry)) t))
205 (when image-data
206 (add-to-list 'hints `(:dict-entry
207 "image_data"
208 (:variant :struct ,image-data)) t))
209 (when sound-file
210 (add-to-list 'hints `(:dict-entry
211 "sound-file"
212 (:variant :string ,sound-file)) t))
213 (when suppress-sound
214 (add-to-list 'hints `(:dict-entry
215 "suppress-sound"
216 (:variant :boolean ,suppress-sound)) t))
217 (when x
218 (add-to-list 'hints `(:dict-entry "x" (:variant :int32 ,x)) t))
219 (when y
220 (add-to-list 'hints `(:dict-entry "y" (:variant :int32 ,y)) t))
221
222 ;; Call Notify method
223 (setq id
224 (dbus-call-method :session
225 notifications-service
226 notifications-path
227 notifications-interface
228 notifications-notify-method
229 :string (or app-name
230 notifications-application-name)
231 :uint32 (or replaces-id 0)
232 :string (if app-icon
233 (expand-file-name app-icon)
234 ;; If app-icon is nil because user
235 ;; requested it to be so, send the
236 ;; empty string
237 (if (plist-member params :app-icon)
238 ""
239 ;; Otherwise send the default icon path
240 notifications-application-icon))
241 :string (or title "")
242 :string (or body "")
243 `(:array ,@actions)
244 (or hints '(:array :signature "{sv}"))
245 :int32 (or timeout -1)))
246
247 ;; Register close/action callback function
248 (let ((on-action (plist-get params :on-action))
249 (on-close (plist-get params :on-close)))
250 (when on-action
251 (add-to-list 'notifications-on-action-map (list id on-action)))
252 (when on-close
253 (add-to-list 'notifications-on-close-map (list id on-close))))
254
255 ;; Return notification id
256 id))
257
258(defun notifications-close-notification (id)
259 "Close a notification with identifier ID."
260 (dbus-call-method :session
261 notifications-service
262 notifications-path
263 notifications-interface
264 notifications-close-notification-method
265 :int32 id))
266
267(provide 'notifications)