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