1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/gtk/notifmsg.cpp
3 // Purpose: wxNotificationMessage for wxGTK using libnotify.
4 // Author: Vadim Zeitlin
5 // Created: 2012-07-25
6 // Copyright: (c) 2012 Vadim Zeitlin <vadim@wxwidgets.org>
7 // Licence: wxWindows licence
8 ///////////////////////////////////////////////////////////////////////////////
9
10 // ============================================================================
11 // declarations
12 // ============================================================================
13
14 // ----------------------------------------------------------------------------
15 // headers
16 // ----------------------------------------------------------------------------
17
18 // for compilers that support precompilation, includes "wx.h".
19 #include "wx/wxprec.h"
20
21
22 #if wxUSE_NOTIFICATION_MESSAGE && wxUSE_LIBNOTIFY
23
24 #include "wx/notifmsg.h"
25
26 #ifndef WX_PRECOMP
27 #include "wx/app.h"
28 #include "wx/icon.h"
29 #endif // WX_PRECOMP
30
31 #include <libnotify/notify.h>
32
33 #include "wx/module.h"
34 #include "wx/private/notifmsg.h"
35 #include <wx/stockitem.h>
36
37 // General note about error handling: as notifications are meant to be
38 // non-intrusive, we use wxLogDebug() and not wxLogError() if anything goes
39 // wrong here to avoid spamming the user with message boxes. As all methods
40 // return boolean indicating success or failure, the caller could show the
41 // notification in some other way or notify about the error itself if needed.
42 #include "wx/gtk/private/error.h"
43
44 // ----------------------------------------------------------------------------
45 // A module for cleaning up libnotify on exit.
46 // ----------------------------------------------------------------------------
47
48 class wxLibnotifyModule : public wxModule
49 {
50 public:
OnInit()51 virtual bool OnInit() wxOVERRIDE
52 {
53 // We're initialized on demand.
54 return true;
55 }
56
OnExit()57 virtual void OnExit() wxOVERRIDE
58 {
59 if ( notify_is_initted() )
60 notify_uninit();
61 }
62
63 // Do initialize the library.
Initialize()64 static bool Initialize()
65 {
66 if ( !notify_is_initted() )
67 {
68 if ( !notify_init(wxTheApp->GetAppName().utf8_str()) )
69 return false;
70 }
71
72 return true;
73 }
74
75 private:
76 wxDECLARE_DYNAMIC_CLASS(wxLibnotifyModule);
77 };
78
79 wxIMPLEMENT_DYNAMIC_CLASS(wxLibnotifyModule, wxModule);
80
81 // ============================================================================
82 // wxNotificationMessage implementation
83 // ============================================================================
84
85 class wxLibNotifyMsgImpl;
86
87 extern "C" {
88 static
89 void wxLibNotifyMsgImplActionCallback(NotifyNotification *notification,
90 char *action,
91 gpointer user_data);
92
93 static void closed_notification(NotifyNotification* notification, void* user_data);
94 }
95
96 class wxLibNotifyMsgImpl : public wxNotificationMessageImpl
97 {
98 public:
wxLibNotifyMsgImpl(wxNotificationMessageBase * notification)99 wxLibNotifyMsgImpl(wxNotificationMessageBase* notification) :
100 wxNotificationMessageImpl(notification),
101 m_notification(NULL),
102 m_flags(wxICON_INFORMATION)
103 {
104 if ( !wxLibnotifyModule::Initialize() )
105 wxLogDebug("Could not initialize libnotify");
106
107 }
108
~wxLibNotifyMsgImpl()109 virtual ~wxLibNotifyMsgImpl()
110 {
111 if ( m_notification )
112 g_object_unref(m_notification);
113 }
114
CreateOrUpdateNotification()115 bool CreateOrUpdateNotification()
116 {
117 if ( !wxLibnotifyModule::Initialize() )
118 return false;
119
120 // Determine the GTK+ icon to use from flags and also set the urgency
121 // appropriately.
122 const char* icon;
123 switch ( m_flags )
124 {
125 case wxICON_INFORMATION:
126 icon = "dialog-information";
127 break;
128
129 case wxICON_WARNING:
130 icon = "dialog-warning";
131 break;
132
133 case wxICON_ERROR:
134 icon = "dialog-error";
135 break;
136
137 default:
138 wxFAIL_MSG( "Unknown notification message flags." );
139 return false;
140 }
141
142 // Create the notification or update an existing one if we had already been
143 // shown before.
144 if ( !m_notification )
145 {
146 m_notification = notify_notification_new
147 (
148 m_title.utf8_str(),
149 m_message.utf8_str(),
150 icon
151 #if !wxUSE_LIBNOTIFY_0_7
152 // There used to be an "associated window"
153 // parameter in this function but it has
154 // disappeared by 0.7, so use it for previous
155 // versions only.
156 , 0
157 #endif // libnotify < 0.7
158 );
159 if ( !m_notification )
160 {
161 wxLogDebug("Failed to creation notification.");
162
163 return false;
164 }
165
166
167 g_signal_connect(m_notification, "closed", G_CALLBACK(closed_notification), this);
168
169 }
170 else
171 {
172 if ( !notify_notification_update
173 (
174 m_notification,
175 m_title.utf8_str(),
176 m_message.utf8_str(),
177 icon
178 ) )
179 {
180 wxLogDebug(wxS("notify_notification_update() unexpectedly failed."));
181 }
182 }
183
184 #ifdef __WXGTK3__
185 // Explicitly specified icon name overrides the implicit one determined by
186 // the flags.
187 if ( m_icon.IsOk() )
188 {
189 notify_notification_set_image_from_pixbuf(
190 m_notification,
191 m_icon.GetPixbufNoMask()
192 );
193 }
194 #endif
195
196 return true;
197 }
198
Show(int timeout)199 virtual bool Show(int timeout) wxOVERRIDE
200 {
201 if ( !CreateOrUpdateNotification() )
202 return false;
203
204 // Set the notification parameters not specified during creation.
205 notify_notification_set_timeout
206 (
207 m_notification,
208 timeout == wxNotificationMessage::Timeout_Auto ? NOTIFY_EXPIRES_DEFAULT
209 : timeout == wxNotificationMessage::Timeout_Never ? NOTIFY_EXPIRES_NEVER
210 : 1000*timeout
211 );
212
213 NotifyUrgency urgency;
214 switch ( m_flags )
215 {
216 case wxICON_INFORMATION:
217 urgency = NOTIFY_URGENCY_LOW;
218 break;
219
220 case wxICON_WARNING:
221 urgency = NOTIFY_URGENCY_NORMAL;
222 break;
223
224 case wxICON_ERROR:
225 urgency = NOTIFY_URGENCY_CRITICAL;
226 break;
227
228 default:
229 wxFAIL_MSG( "Unknown notification message flags." );
230 return false;
231 }
232 notify_notification_set_urgency(m_notification, urgency);
233
234
235 // Finally do show the notification.
236 wxGtkError error;
237 if ( !notify_notification_show(m_notification, error.Out()) )
238 {
239 wxLogDebug("Failed to shown notification: %s", error.GetMessage());
240
241 return false;
242 }
243
244 return true;
245 }
246
Close()247 virtual bool Close() wxOVERRIDE
248 {
249 wxCHECK_MSG( m_notification, false,
250 wxS("Can't close not shown notification.") );
251
252 wxGtkError error;
253 if ( !notify_notification_close(m_notification, error.Out()) )
254 {
255 wxLogDebug("Failed to hide notification: %s", error.GetMessage());
256
257 return false;
258 }
259
260 return true;
261 }
262
SetTitle(const wxString & title)263 virtual void SetTitle(const wxString& title) wxOVERRIDE
264 {
265 m_title = title;
266 }
267
SetMessage(const wxString & message)268 virtual void SetMessage(const wxString& message) wxOVERRIDE
269 {
270 m_message = message;
271 }
272
SetParent(wxWindow * WXUNUSED (parent))273 virtual void SetParent(wxWindow *WXUNUSED(parent)) wxOVERRIDE
274 {
275 }
276
SetFlags(int flags)277 virtual void SetFlags(int flags) wxOVERRIDE
278 {
279 m_flags = flags;
280 }
281
SetIcon(const wxIcon & icon)282 virtual void SetIcon(const wxIcon& icon) wxOVERRIDE
283 {
284 m_icon = icon;
285 CreateOrUpdateNotification();
286 }
287
AddAction(wxWindowID actionid,const wxString & label)288 virtual bool AddAction(wxWindowID actionid, const wxString &label) wxOVERRIDE
289 {
290 if ( !CreateOrUpdateNotification() )
291 return false;
292
293 wxString labelStr = label;
294 if ( labelStr.empty() )
295 labelStr = wxGetStockLabel(actionid, wxSTOCK_NOFLAGS);
296
297 notify_notification_add_action
298 (
299 m_notification,
300 wxString::Format("%d", actionid).utf8_str(),
301 labelStr.utf8_str(),
302 &wxLibNotifyMsgImplActionCallback,
303 this,
304 NULL
305 );
306
307 return true;
308 }
309
NotifyClose(int closeReason)310 void NotifyClose(int closeReason)
311 {
312 // Values according to the OpenDesktop specification at:
313 // https://developer.gnome.org/notification-spec/
314
315 switch (closeReason)
316 {
317 case 1: // Expired
318 case 2: // The notification was dismissed by the user.
319 {
320 wxCommandEvent evt(wxEVT_NOTIFICATION_MESSAGE_DISMISSED);
321 ProcessNotificationEvent(evt);
322 break;
323 }
324 }
325
326 }
327
NotifyAction(wxWindowID actionid)328 void NotifyAction(wxWindowID actionid)
329 {
330 wxCommandEvent evt(wxEVT_NOTIFICATION_MESSAGE_ACTION, actionid);
331 ProcessNotificationEvent(evt);
332 }
333
334 private:
335 NotifyNotification* m_notification;
336 wxString m_title;
337 wxString m_message;
338 wxIcon m_icon;
339 int m_flags;
340 };
341
342 extern "C" {
343 static
wxLibNotifyMsgImplActionCallback(NotifyNotification * WXUNUSED (notification),char * action,gpointer user_data)344 void wxLibNotifyMsgImplActionCallback(NotifyNotification *WXUNUSED(notification),
345 char *action,
346 gpointer user_data)
347 {
348 wxLibNotifyMsgImpl* impl = (wxLibNotifyMsgImpl*) user_data;
349
350 impl->NotifyAction(wxAtoi(action));
351 }
352
closed_notification(NotifyNotification * notification,void * user_data)353 static void closed_notification(NotifyNotification* notification, void* user_data)
354 {
355 wxLibNotifyMsgImpl* impl = (wxLibNotifyMsgImpl*) user_data;
356 gint closeReason = notify_notification_get_closed_reason(notification);
357 impl->NotifyClose(closeReason);
358 }
359 }
360
361 // ----------------------------------------------------------------------------
362 // wxNotificationMessage
363 // ----------------------------------------------------------------------------
364
Init()365 void wxNotificationMessage::Init()
366 {
367 m_impl = new wxLibNotifyMsgImpl(this);
368 }
369
370 #endif // wxUSE_NOTIFICATION_MESSAGE && wxUSE_LIBNOTIFY
371