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