1 /* System tray main() */
2 
3 /*
4  * Copyright (C) 2002 Red Hat, Inc.
5  * Copyright (C) 2003-2006 Vincent Untz
6  * Copyright (C) 2011 Perberos
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public License as
10  * published by the Free Software Foundation; either version 2 of the
11  * License, or (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful, but
14  * WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
21  * 02110-1301, USA.
22  */
23 
24 #include <config.h>
25 
26 #ifndef HAVE_X11
27 #error file should only be built when HAVE_X11 is enabled
28 #endif
29 
30 #include <string.h>
31 
32 #include <mate-panel-applet.h>
33 #include <mate-panel-applet-gsettings.h>
34 
35 #include <glib/gi18n.h>
36 #include <gtk/gtk.h>
37 #include <gio/gio.h>
38 
39 #include "main.h"
40 #include "na-grid.h"
41 
42 #ifdef PROVIDE_WATCHER_SERVICE
43 # include "libstatus-notifier-watcher/gf-status-notifier-watcher.h"
44 #endif
45 
46 #define NOTIFICATION_AREA_ICON "mate-panel-notification-area"
47 
48 typedef struct
49 {
50   GtkWidget *preferences_dialog;
51   GtkWidget *min_icon_size_spin;
52 } NAPreferencesDialog;
53 
54 struct _NaTrayAppletPrivate
55 {
56   GtkWidget               *grid;
57 
58   NAPreferencesDialog     *dialog;
59   GtkBuilder              *builder;
60 
61   GSettings               *settings;
62   gint                     min_icon_size;
63 
64 #ifdef PROVIDE_WATCHER_SERVICE
65   GfStatusNotifierWatcher *sn_watcher;
66 #endif
67 };
68 
69 G_DEFINE_TYPE_WITH_PRIVATE (NaTrayApplet, na_tray_applet, PANEL_TYPE_APPLET)
70 
71 static void (*parent_class_realize) (GtkWidget *widget);
72 static void (*parent_class_style_updated) (GtkWidget *widget);
73 static void (*parent_class_change_background)(MatePanelApplet* panel_applet, MatePanelAppletBackgroundType type, GdkRGBA* color, cairo_pattern_t* pattern);
74 static void (*parent_class_change_orient)(MatePanelApplet       *panel_applet, MatePanelAppletOrient  orient);
75 
76 
77 #ifdef PROVIDE_WATCHER_SERVICE
78 /* Quite dirty way of providing the org.kde.StatusNotifierWatcher service
79  * ourselves, in case the session doesn't already */
80 
81 static GfStatusNotifierWatcher *sn_watcher_service = NULL;
82 
83 static GfStatusNotifierWatcher *
sn_watcher_service_ref(void)84 sn_watcher_service_ref (void)
85 {
86   GSettings *settings;
87   settings = g_settings_new ("org.mate.panel");
88 
89   if (g_settings_get_boolean (settings, "enable-sni-support") == TRUE)
90     {
91       if (sn_watcher_service != NULL)
92         g_object_ref (sn_watcher_service);
93       else
94         {
95           sn_watcher_service = gf_status_notifier_watcher_new ();
96           g_object_add_weak_pointer ((GObject *) sn_watcher_service,
97                                      (gpointer *) &sn_watcher_service);
98         }
99     }
100 
101   g_object_unref (settings);
102   return sn_watcher_service;
103 }
104 #endif
105 
106 
107 static GtkOrientation
get_gtk_orientation_from_applet_orient(MatePanelAppletOrient orient)108 get_gtk_orientation_from_applet_orient (MatePanelAppletOrient orient)
109 {
110   switch (orient)
111     {
112     case MATE_PANEL_APPLET_ORIENT_LEFT:
113     case MATE_PANEL_APPLET_ORIENT_RIGHT:
114       return GTK_ORIENTATION_VERTICAL;
115     case MATE_PANEL_APPLET_ORIENT_UP:
116     case MATE_PANEL_APPLET_ORIENT_DOWN:
117     default:
118       return GTK_ORIENTATION_HORIZONTAL;
119     }
120 
121   g_assert_not_reached ();
122 
123   return GTK_ORIENTATION_HORIZONTAL;
124 }
125 
126 static void
gsettings_changed_min_icon_size(GSettings * settings,gchar * key,NaTrayApplet * applet)127 gsettings_changed_min_icon_size (GSettings    *settings,
128                                  gchar        *key,
129                                  NaTrayApplet *applet)
130 {
131   applet->priv->min_icon_size = g_settings_get_int (settings, key);
132 
133   if (applet->priv->dialog)
134     gtk_spin_button_set_value (GTK_SPIN_BUTTON (applet->priv->dialog->min_icon_size_spin),
135                                applet->priv->min_icon_size);
136 
137   na_grid_set_min_icon_size (NA_GRID (applet->priv->grid), applet->priv->min_icon_size);
138 }
139 
140 static void
setup_gsettings(NaTrayApplet * applet)141 setup_gsettings (NaTrayApplet *applet)
142 {
143   applet->priv->settings = mate_panel_applet_settings_new (MATE_PANEL_APPLET (applet), NA_TRAY_SCHEMA);
144   g_signal_connect (applet->priv->settings, "changed::" KEY_MIN_ICON_SIZE, G_CALLBACK (gsettings_changed_min_icon_size), applet);
145 }
146 
147 static void
na_preferences_dialog_min_icon_size_changed(NaTrayApplet * applet,GtkSpinButton * spin_button)148 na_preferences_dialog_min_icon_size_changed (NaTrayApplet  *applet,
149                                              GtkSpinButton *spin_button)
150 {
151   applet->priv->min_icon_size = gtk_spin_button_get_value_as_int (spin_button);
152   g_settings_set_int (applet->priv->settings, KEY_MIN_ICON_SIZE, applet->priv->min_icon_size);
153 }
154 
155 static gboolean
na_preferences_dialog_hide_event(GtkWidget * widget,GdkEvent * event,NaTrayApplet * applet)156 na_preferences_dialog_hide_event (GtkWidget    *widget,
157                                   GdkEvent     *event,
158                                   NaTrayApplet *applet)
159 {
160   gtk_widget_hide (applet->priv->dialog->preferences_dialog);
161   return TRUE;
162 }
163 
164 static void
na_preferences_dialog_response(NaTrayApplet * applet,int response,GtkWidget * preferences_dialog)165 na_preferences_dialog_response (NaTrayApplet *applet,
166                                 int           response,
167                                 GtkWidget    *preferences_dialog)
168 {
169   switch (response)
170     {
171     case GTK_RESPONSE_CLOSE:
172       gtk_widget_hide (preferences_dialog);
173       break;
174     default:
175       break;
176     }
177 }
178 
179 static void
ensure_prefs_window_is_created(NaTrayApplet * applet)180 ensure_prefs_window_is_created (NaTrayApplet *applet)
181 {
182   if (applet->priv->dialog)
183     return;
184 
185   applet->priv->dialog = g_new0 (NAPreferencesDialog, 1);
186 
187   applet->priv->dialog->preferences_dialog = GTK_WIDGET (gtk_builder_get_object (applet->priv->builder, "notification_area_preferences_dialog"));
188 
189   gtk_window_set_icon_name (GTK_WINDOW (applet->priv->dialog->preferences_dialog), NOTIFICATION_AREA_ICON);
190 
191   applet->priv->dialog->min_icon_size_spin = GTK_WIDGET (gtk_builder_get_object (applet->priv->builder, "min_icon_size_spin"));
192   g_return_if_fail (applet->priv->dialog->min_icon_size_spin != NULL);
193 
194   gtk_spin_button_set_range (GTK_SPIN_BUTTON (applet->priv->dialog->min_icon_size_spin), 7, 130);
195   gtk_spin_button_set_value (GTK_SPIN_BUTTON (applet->priv->dialog->min_icon_size_spin), applet->priv->min_icon_size);
196 
197   g_signal_connect_swapped (applet->priv->dialog->min_icon_size_spin, "value-changed",
198                             G_CALLBACK (na_preferences_dialog_min_icon_size_changed),
199                             applet);
200 
201   g_signal_connect_swapped (applet->priv->dialog->preferences_dialog, "response",
202                             G_CALLBACK (na_preferences_dialog_response), applet);
203 
204   g_signal_connect (G_OBJECT (applet->priv->dialog->preferences_dialog), "delete_event",
205                     G_CALLBACK (na_preferences_dialog_hide_event), applet);
206 }
207 
208 static void
properties_dialog(GtkAction * action,NaTrayApplet * applet)209 properties_dialog (GtkAction    *action,
210                    NaTrayApplet *applet)
211 {
212   ensure_prefs_window_is_created (applet);
213 
214   gtk_window_set_screen (GTK_WINDOW (applet->priv->dialog->preferences_dialog),
215                          gtk_widget_get_screen (GTK_WIDGET (applet)));
216   gtk_window_present (GTK_WINDOW (applet->priv->dialog->preferences_dialog));
217 }
218 
help_cb(GtkAction * action,NaTrayApplet * applet)219 static void help_cb(GtkAction* action, NaTrayApplet* applet)
220 {
221 	GError* error = NULL;
222 	char* uri;
223 	#define NA_HELP_DOC "mate-user-guide"
224 
225 	uri = g_strdup_printf("help:%s/%s", NA_HELP_DOC, "panels-notification-area");
226 	gtk_show_uri_on_window (NULL, uri, gtk_get_current_event_time (), &error);
227 	g_free(uri);
228 
229 	if (error && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
230 	{
231 		g_error_free(error);
232 	}
233 	else if(error)
234 	{
235 		GtkWidget* dialog;
236 		char* primary;
237 
238 		primary = g_markup_printf_escaped (_("Could not display help document '%s'"), NA_HELP_DOC);
239 		dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "%s", primary);
240 
241 		gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), "%s", error->message);
242 
243 		g_error_free(error);
244 		g_free(primary);
245 
246 		g_signal_connect(dialog, "response", G_CALLBACK (gtk_widget_destroy), NULL);
247 
248 		gtk_window_set_icon_name (GTK_WINDOW (dialog), NOTIFICATION_AREA_ICON);
249 		gtk_window_set_screen (GTK_WINDOW (dialog), gtk_widget_get_screen (GTK_WIDGET (applet)));
250 		/* we have no parent window */
251 		gtk_window_set_skip_taskbar_hint (GTK_WINDOW (dialog), FALSE);
252 		gtk_window_set_title (GTK_WINDOW (dialog), _("Error displaying help document"));
253 
254 		gtk_widget_show (dialog);
255 	}
256 }
257 
about_cb(GtkAction * action,NaTrayApplet * applet)258 static void about_cb(GtkAction* action, NaTrayApplet* applet)
259 {
260 	const gchar* authors[] = {
261 		"Havoc Pennington <hp@redhat.com>",
262 		"Anders Carlsson <andersca@gnu.org>",
263 		"Vincent Untz <vuntz@gnome.org>",
264 		"Alberts Muktupāvels",
265 		"Colomban Wendling <cwendling@hypra.fr>",
266 		"Fabien Broquard <braikar@gmail.com>",
267 		NULL
268 	};
269 
270 	const char* documenters[] = {
271 		"Sun GNOME Documentation Team <gdocteam@sun.com>",
272 		NULL
273 	};
274 
275 	gtk_show_about_dialog(NULL,
276 		"program-name", _("Notification Area"),
277 		"title", _("About Notification Area"),
278 		"authors", authors,
279 		/* "comments", _(comments), */
280 		"copyright", _("Copyright \xc2\xa9 2002 Red Hat, Inc.\n"
281 		               "Copyright \xc2\xa9 2003-2006 Vincent Untz\n"
282 		               "Copyright \xc2\xa9 2011 Perberos\n"
283 		               "Copyright \xc2\xa9 2012-2021 MATE developers"),
284 		"documenters", documenters,
285 		"logo-icon-name", NOTIFICATION_AREA_ICON,
286 		"translator-credits", _("translator-credits"),
287 		"version", VERSION,
288 		NULL);
289 }
290 
291 static const GtkActionEntry menu_actions [] = {
292 	{ "SystemTrayPreferences", "document-properties", N_("_Preferences"),
293 	  NULL, NULL,
294 	  G_CALLBACK (properties_dialog) },
295 	{ "SystemTrayHelp", "help-browser", N_("_Help"),
296 	  NULL, NULL,
297 	  G_CALLBACK (help_cb) },
298 	{ "SystemTrayAbout", "help-about", N_("_About"),
299 	  NULL, NULL,
300 	  G_CALLBACK (about_cb) }
301 };
302 
303 
304 static void
na_tray_applet_realize(GtkWidget * widget)305 na_tray_applet_realize (GtkWidget *widget)
306 {
307   NaTrayApplet      *applet = NA_TRAY_APPLET (widget);
308 
309   if (parent_class_realize)
310     parent_class_realize (widget);
311 
312   GtkActionGroup* action_group;
313   action_group = gtk_action_group_new("NA Applet Menu Actions");
314   gtk_action_group_set_translation_domain(action_group, GETTEXT_PACKAGE);
315   gtk_action_group_add_actions(action_group, menu_actions, G_N_ELEMENTS(menu_actions), applet);
316   mate_panel_applet_setup_menu_from_resource (MATE_PANEL_APPLET (applet),
317                                               NA_RESOURCE_PATH "notification-area-menu.xml",
318                                               action_group);
319   g_object_unref(action_group);
320 
321   setup_gsettings (applet);
322 
323   /* load min icon size */
324   gsettings_changed_min_icon_size (applet->priv->settings, KEY_MIN_ICON_SIZE, applet);
325 
326   applet->priv->builder = gtk_builder_new ();
327   gtk_builder_set_translation_domain (applet->priv->builder, GETTEXT_PACKAGE);
328   gtk_builder_add_from_resource (applet->priv->builder, NA_RESOURCE_PATH "notification-area-preferences-dialog.ui", NULL);
329 }
330 
331 static void
na_tray_applet_dispose(GObject * object)332 na_tray_applet_dispose (GObject *object)
333 {
334   g_clear_object (&NA_TRAY_APPLET (object)->priv->settings);
335 #ifdef PROVIDE_WATCHER_SERVICE
336   g_clear_object (&NA_TRAY_APPLET (object)->priv->sn_watcher);
337 #endif
338 
339   g_clear_object (&NA_TRAY_APPLET (object)->priv->builder);
340 
341   G_OBJECT_CLASS (na_tray_applet_parent_class)->dispose (object);
342 }
343 
344 static void
na_tray_applet_style_updated(GtkWidget * widget)345 na_tray_applet_style_updated (GtkWidget *widget)
346 {
347   NaTrayApplet    *applet = NA_TRAY_APPLET (widget);
348   gint             padding;
349   gint             icon_size;
350 
351   if (parent_class_style_updated)
352     parent_class_style_updated (widget);
353 
354   if (!applet->priv->grid)
355     return;
356 
357   gtk_widget_style_get (widget,
358                         "icon-padding", &padding,
359                         "icon-size", &icon_size,
360                         NULL);
361   g_object_set (applet->priv->grid,
362                 "icon-padding", padding,
363                 "icon-size", icon_size,
364                 NULL);
365 }
366 
367 static void
na_tray_applet_change_background(MatePanelApplet * panel_applet,MatePanelAppletBackgroundType type,GdkRGBA * color,cairo_pattern_t * pattern)368 na_tray_applet_change_background(MatePanelApplet* panel_applet, MatePanelAppletBackgroundType type, GdkRGBA* color, cairo_pattern_t* pattern)
369 {
370   NaTrayApplet *applet = NA_TRAY_APPLET (panel_applet);
371 
372   if (parent_class_change_background) {
373     parent_class_change_background (panel_applet, type, color, pattern);
374   }
375 
376   if (applet->priv->grid)
377     na_grid_force_redraw (NA_GRID (applet->priv->grid));
378 }
379 
380 static void
na_tray_applet_change_orient(MatePanelApplet * panel_applet,MatePanelAppletOrient orient)381 na_tray_applet_change_orient (MatePanelApplet       *panel_applet,
382                               MatePanelAppletOrient  orient)
383 {
384   NaTrayApplet *applet = NA_TRAY_APPLET (panel_applet);
385 
386   if (parent_class_change_orient)
387     parent_class_change_orient (panel_applet, orient);
388 
389   if (!applet->priv->grid)
390     return;
391 
392   gtk_orientable_set_orientation (GTK_ORIENTABLE (applet->priv->grid),
393                                   get_gtk_orientation_from_applet_orient (orient));
394 }
395 
396 static gboolean
na_tray_applet_button_press_event(GtkWidget * widget,GdkEventButton * event)397 na_tray_applet_button_press_event (GtkWidget      *widget,
398                                    GdkEventButton *event)
399 {
400   /* Prevent the panel from poping up the applet's popup on the the items,
401    * which may also popup a menu which then conflicts.
402    * This doesn't prevent the menu from poping up on the applet handle. */
403   if (event->button == 3)
404     return TRUE;
405 
406   return GTK_WIDGET_CLASS (na_tray_applet_parent_class)->button_press_event (widget, event);
407 }
408 
409 static gboolean
na_tray_applet_focus(GtkWidget * widget,GtkDirectionType direction)410 na_tray_applet_focus (GtkWidget        *widget,
411                       GtkDirectionType  direction)
412 {
413   NaTrayApplet *applet = NA_TRAY_APPLET (widget);
414 
415   /* We let the grid handle the focus movement because we behave more like a
416    * container than a single applet.  But if focus didn't move, we let the
417    * applet do its thing. */
418   if (gtk_widget_child_focus (applet->priv->grid, direction))
419     return TRUE;
420 
421   return GTK_WIDGET_CLASS (na_tray_applet_parent_class)->focus (widget, direction);
422 }
423 
424 static void
na_tray_applet_class_init(NaTrayAppletClass * class)425 na_tray_applet_class_init (NaTrayAppletClass *class)
426 {
427   GObjectClass     *object_class = G_OBJECT_CLASS (class);
428   GtkWidgetClass   *widget_class = GTK_WIDGET_CLASS (class);
429   MatePanelAppletClass *applet_class = MATE_PANEL_APPLET_CLASS (class);
430 
431   object_class->dispose = na_tray_applet_dispose;
432 
433   parent_class_realize = widget_class->realize;
434   widget_class->realize = na_tray_applet_realize;
435 
436   parent_class_style_updated = widget_class->style_updated;
437   widget_class->style_updated = na_tray_applet_style_updated;
438   parent_class_change_background = applet_class->change_background;
439   applet_class->change_background = na_tray_applet_change_background;
440 
441   widget_class->button_press_event = na_tray_applet_button_press_event;
442   widget_class->focus = na_tray_applet_focus;
443 
444   parent_class_change_orient = applet_class->change_orient;
445   applet_class->change_orient = na_tray_applet_change_orient;
446 
447   gtk_widget_class_install_style_property (
448           widget_class,
449           g_param_spec_int ("icon-padding",
450                             "Padding around icons",
451                             "Padding that should be put around icons, in pixels",
452                             0, G_MAXINT, 0,
453                             G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
454 
455   gtk_widget_class_install_style_property (
456           widget_class,
457           g_param_spec_int ("icon-size",
458                             "Icon size",
459                             "If non-zero, hardcodes the size of the icons in pixels",
460                             0, G_MAXINT, 0,
461                             G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
462 
463   gtk_widget_class_set_css_name (widget_class, "na-tray-applet");
464 }
465 
466 static void
na_tray_applet_init(NaTrayApplet * applet)467 na_tray_applet_init (NaTrayApplet *applet)
468 {
469   MatePanelAppletOrient orient;
470   AtkObject *atko;
471 
472   applet->priv = na_tray_applet_get_instance_private (applet);
473 
474 #ifdef PROVIDE_WATCHER_SERVICE
475   applet->priv->sn_watcher = sn_watcher_service_ref ();
476 #endif
477 
478   orient = mate_panel_applet_get_orient (MATE_PANEL_APPLET (applet));
479   applet->priv->grid = na_grid_new (get_gtk_orientation_from_applet_orient (orient));
480 
481   gtk_container_add (GTK_CONTAINER (applet), GTK_WIDGET (applet->priv->grid));
482   gtk_widget_show (GTK_WIDGET (applet->priv->grid));
483 
484   atko = gtk_widget_get_accessible (GTK_WIDGET (applet));
485   atk_object_set_name (atko, _("Panel Notification Area"));
486 
487   mate_panel_applet_set_flags (MATE_PANEL_APPLET (applet),
488                           MATE_PANEL_APPLET_HAS_HANDLE|MATE_PANEL_APPLET_EXPAND_MINOR);
489 }
490 
491 static gboolean
applet_factory(MatePanelApplet * applet,const gchar * iid,gpointer user_data)492 applet_factory (MatePanelApplet *applet,
493                 const gchar *iid,
494                 gpointer     user_data)
495 {
496   if (!(strcmp (iid, "NotificationArea") == 0 ||
497         strcmp (iid, "SystemTrayApplet") == 0))
498     return FALSE;
499 
500   if (!GDK_IS_X11_DISPLAY (gtk_widget_get_display (GTK_WIDGET (applet)))) {
501     g_warning ("Notification area only works on X");
502     return FALSE;
503   }
504 
505 #ifndef NOTIFICATION_AREA_INPROCESS
506   gtk_window_set_default_icon_name (NOTIFICATION_AREA_ICON);
507 #endif
508 
509   gtk_widget_show_all (GTK_WIDGET (applet));
510 
511   return TRUE;
512 }
513 
514 #ifdef NOTIFICATION_AREA_INPROCESS
515 	MATE_PANEL_APPLET_IN_PROCESS_FACTORY ("NotificationAreaAppletFactory",
516 				 NA_TYPE_TRAY_APPLET,
517 				 "NotificationArea",
518 				 applet_factory,
519 				 NULL)
520 #else
521 	MATE_PANEL_APPLET_OUT_PROCESS_FACTORY ("NotificationAreaAppletFactory",
522 				  NA_TYPE_TRAY_APPLET,
523 				  "NotificationArea",
524 				  applet_factory,
525 				  NULL)
526 #endif
527