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