1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2  *
3  * Copyright (C) 2005 William Jon McCann <mccann@jhu.edu>
4  * Copyright (C) 2005-2009 Richard Hughes <richard@hughsie.com>
5  * Copyright (C) 2012-2021 MATE Developers
6  *
7  * Licensed under the GNU General Public License Version 2
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22  */
23 
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27 
28 #include <stdlib.h>
29 #include <stdio.h>
30 #include <time.h>
31 #include <errno.h>
32 
33 #include <string.h>
34 #include <sys/time.h>
35 #include <sys/types.h>
36 #ifdef HAVE_UNISTD_H
37 #include <unistd.h>
38 #endif /* HAVE_UNISTD_H */
39 
40 #include <glib/gi18n.h>
41 #include <gtk/gtk.h>
42 #include <libupower-glib/upower.h>
43 
44 #include "gpm-upower.h"
45 #include "gpm-engine.h"
46 #include "gpm-common.h"
47 #include "gpm-icon-names.h"
48 #include "gpm-tray-icon.h"
49 
50 static void     gpm_tray_icon_finalize   (GObject	   *object);
51 
52 struct GpmTrayIconPrivate
53 {
54 	GSettings		*settings;
55 	GpmEngine		*engine;
56 	GtkStatusIcon		*status_icon;
57 	gboolean		 show_actions;
58 };
59 
G_DEFINE_TYPE_WITH_PRIVATE(GpmTrayIcon,gpm_tray_icon,G_TYPE_OBJECT)60 G_DEFINE_TYPE_WITH_PRIVATE (GpmTrayIcon, gpm_tray_icon, G_TYPE_OBJECT)
61 
62 /**
63  * gpm_tray_icon_enable_actions:
64  **/
65 static void
66 gpm_tray_icon_enable_actions (GpmTrayIcon *icon, gboolean enabled)
67 {
68 	g_return_if_fail (GPM_IS_TRAY_ICON (icon));
69 	icon->priv->show_actions = enabled;
70 }
71 
72 /**
73  * gpm_tray_icon_show:
74  * @enabled: If we should show the tray
75  **/
76 static void
gpm_tray_icon_show(GpmTrayIcon * icon,gboolean enabled)77 gpm_tray_icon_show (GpmTrayIcon *icon, gboolean enabled)
78 {
79 	g_return_if_fail (GPM_IS_TRAY_ICON (icon));
80 	gtk_status_icon_set_visible (icon->priv->status_icon, enabled);
81 }
82 
83 /**
84  * gpm_tray_icon_set_tooltip:
85  * @tooltip: The tooltip text, e.g. "Batteries charged"
86  **/
87 gboolean
gpm_tray_icon_set_tooltip(GpmTrayIcon * icon,const gchar * tooltip)88 gpm_tray_icon_set_tooltip (GpmTrayIcon *icon, const gchar *tooltip)
89 {
90 	g_return_val_if_fail (icon != NULL, FALSE);
91 	g_return_val_if_fail (GPM_IS_TRAY_ICON (icon), FALSE);
92 	g_return_val_if_fail (tooltip != NULL, FALSE);
93 
94 	gtk_status_icon_set_tooltip_text (icon->priv->status_icon, tooltip);
95 
96 	return TRUE;
97 }
98 
99 /**
100  * gpm_tray_icon_get_status_icon:
101  **/
102 GtkStatusIcon *
gpm_tray_icon_get_status_icon(GpmTrayIcon * icon)103 gpm_tray_icon_get_status_icon (GpmTrayIcon *icon)
104 {
105 	g_return_val_if_fail (GPM_IS_TRAY_ICON (icon), NULL);
106 	return g_object_ref (icon->priv->status_icon);
107 }
108 
109 /**
110  * gpm_tray_icon_set_icon:
111  * @icon_name: The icon name, e.g. GPM_ICON_APP_ICON, or NULL to remove.
112  *
113  * Loads a pixmap from disk, and sets as the tooltip icon.
114  **/
115 gboolean
gpm_tray_icon_set_icon(GpmTrayIcon * icon,const gchar * icon_name)116 gpm_tray_icon_set_icon (GpmTrayIcon *icon, const gchar *icon_name)
117 {
118 	g_return_val_if_fail (icon != NULL, FALSE);
119 	g_return_val_if_fail (GPM_IS_TRAY_ICON (icon), FALSE);
120 
121 	if (icon_name != NULL) {
122 		g_debug ("Setting icon to %s", icon_name);
123 		gtk_status_icon_set_from_icon_name (icon->priv->status_icon,
124 		                                    icon_name);
125 
126 		/* make sure that we are visible */
127 		gpm_tray_icon_show (icon, TRUE);
128 	} else {
129 		/* remove icon */
130 		g_debug ("no icon will be displayed");
131 
132 		/* make sure that we are hidden */
133 		gpm_tray_icon_show (icon, FALSE);
134 	}
135 	return TRUE;
136 }
137 
138 /**
139  * gpm_tray_icon_show_info_cb:
140  **/
141 static void
gpm_tray_icon_show_info_cb(GtkMenuItem * item,gpointer data)142 gpm_tray_icon_show_info_cb (GtkMenuItem *item, gpointer data)
143 {
144 	gchar *path;
145 	const gchar *object_path;
146 
147 	object_path = g_object_get_data (G_OBJECT (item), "object-path");
148 	path = g_strdup_printf ("%s/mate-power-statistics --device %s", BINDIR, object_path);
149 	if (!g_spawn_command_line_async (path, NULL))
150 		g_warning ("Couldn't execute command: %s", path);
151 	g_free (path);
152 }
153 
154 /**
155  * gpm_tray_icon_show_preferences_cb:
156  * @action: A valid GtkAction
157  **/
158 static void
gpm_tray_icon_show_preferences_cb(GtkMenuItem * item,gpointer data)159 gpm_tray_icon_show_preferences_cb (GtkMenuItem *item, gpointer data)
160 {
161 	const gchar *command = "mate-power-preferences";
162 
163 	if (g_spawn_command_line_async (command, NULL) == FALSE)
164 		g_warning ("Couldn't execute command: %s", command);
165 }
166 
167 #define ABOUT_GROUP "About"
168 #define EMAILIFY(string) (g_strdelimit ((string), "%", '@'))
169 
170 /**
171  * gpm_tray_icon_show_about_cb:
172  * @action: A valid GtkAction
173  **/
174 static void
gpm_tray_icon_show_about_cb(GtkMenuItem * item,gpointer data)175 gpm_tray_icon_show_about_cb (GtkMenuItem *item, gpointer data)
176 {
177 	GKeyFile *key_file;
178 	GBytes *bytes;
179 	const guint8 *data_resource;
180 	gsize data_resource_len;
181 	GError *error = NULL;
182 	char **authors;
183 	gsize n_authors = 0, i;
184 
185 	bytes = g_resources_lookup_data ("/org/mate/powermanager/manager/mate-power-manager.about",
186 	                                 G_RESOURCE_LOOKUP_FLAGS_NONE, &error);
187 	g_assert_no_error (error);
188 
189 	data_resource = g_bytes_get_data (bytes, &data_resource_len);
190 	key_file = g_key_file_new ();
191 	g_key_file_load_from_data (key_file, (const char *) data_resource, data_resource_len, 0, &error);
192 	g_assert_no_error (error);
193 
194 	authors = g_key_file_get_string_list (key_file, ABOUT_GROUP, "Authors", &n_authors, NULL);
195 
196 	g_key_file_free (key_file);
197 	g_bytes_unref (bytes);
198 
199 	for (i = 0; i < n_authors; ++i)
200 		authors[i] = EMAILIFY (authors[i]);
201 
202 	gtk_show_about_dialog (NULL,
203 				"program-name", _("Power Manager"),
204 				"version", VERSION,
205 				"comments", _("Power management daemon"),
206 				"copyright", _("Copyright \xC2\xA9 2011-2021 MATE developers"),
207 				"authors", authors,
208 				/* Translators should localize the following string
209 				* which will be displayed at the bottom of the about
210 				* box to give credit to the translator(s).
211 				*/
212 				"translator-credits", _("translator-credits"),
213 				"icon-name", "mate-power-manager",
214 				"logo-icon-name", "mate-power-manager",
215 				"website", PACKAGE_URL,
216 				NULL);
217 
218 	g_strfreev (authors);
219 }
220 
221 /**
222  * gpm_tray_icon_class_init:
223  **/
224 static void
gpm_tray_icon_class_init(GpmTrayIconClass * klass)225 gpm_tray_icon_class_init (GpmTrayIconClass *klass)
226 {
227 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
228 
229 	object_class->finalize = gpm_tray_icon_finalize;
230 }
231 
232 /**
233  * gpm_tray_icon_add_device:
234  **/
235 static guint
gpm_tray_icon_add_device(GpmTrayIcon * icon,GtkMenu * menu,const GPtrArray * array,UpDeviceKind kind)236 gpm_tray_icon_add_device (GpmTrayIcon *icon, GtkMenu *menu, const GPtrArray *array, UpDeviceKind kind)
237 {
238 	guint i;
239 	guint added = 0;
240 	gchar *icon_name;
241 	gchar *label, *vendor, *model;
242 	GtkWidget *item;
243 	GtkWidget *image;
244 	const gchar *object_path;
245 	UpDevice *device;
246 	UpDeviceKind kind_tmp;
247 	gdouble percentage;
248 
249 	/* find type */
250 	for (i=0;i<array->len;i++) {
251 		device = g_ptr_array_index (array, i);
252 
253 		/* get device properties */
254 		g_object_get (device,
255 			      "kind", &kind_tmp,
256 			      "percentage", &percentage,
257 			      "vendor", &vendor,
258 			      "model", &model,
259 			      NULL);
260 
261 		if (kind != kind_tmp)
262 			continue;
263 
264 		object_path = up_device_get_object_path (device);
265 		g_debug ("adding device %s", object_path);
266 		added++;
267 
268 		/* generate the label */
269 		if ((vendor != NULL && strlen(vendor) != 0) && (model != NULL && strlen(model) != 0)) {
270 			label = g_strdup_printf ("%s %s (%.1f%%)", vendor, model, percentage);
271 		}
272 		else {
273 			label = g_strdup_printf ("%s (%.1f%%)", gpm_device_kind_to_localised_string (kind, 1), percentage);
274 		}
275 		item = gtk_image_menu_item_new_with_label (label);
276 
277 		/* generate the image */
278 		icon_name = gpm_upower_get_device_icon (device);
279 		image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_MENU);
280 		gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
281 		gtk_image_menu_item_set_always_show_image (GTK_IMAGE_MENU_ITEM (item), TRUE);
282 
283 		/* set callback and add the menu */
284 		g_signal_connect (G_OBJECT (item), "activate", G_CALLBACK (gpm_tray_icon_show_info_cb), icon);
285 		g_object_set_data (G_OBJECT (item), "object-path", (gpointer) object_path);
286 		gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
287 
288 		g_free (icon_name);
289 		g_free (label);
290 		g_free (vendor);
291 		g_free (model);
292 	}
293 	return added;
294 }
295 
296 /**
297  * gpm_tray_icon_add_primary_device:
298  **/
299 static void
gpm_tray_icon_add_primary_device(GpmTrayIcon * icon,GtkMenu * menu,UpDevice * device)300 gpm_tray_icon_add_primary_device (GpmTrayIcon *icon, GtkMenu *menu, UpDevice *device)
301 {
302 	GtkWidget *item;
303 	gchar *time_str;
304 	gchar *string;
305 	gint64 time_to_empty = 0;
306 
307 	/* get details */
308 	g_object_get (device,
309 		      "time-to-empty", &time_to_empty,
310 		      NULL);
311 
312 	/* convert time to string */
313 	time_str = gpm_get_timestring (time_to_empty);
314 
315 	/* TRANSLATORS: % is a timestring, e.g. "6 hours 10 minutes" */
316 	string = g_strdup_printf (_("%s remaining"), time_str);
317 	item = gtk_image_menu_item_new_with_label (string);
318 	gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
319 	g_free (time_str);
320 	g_free (string);
321 }
322 
323 /**
324  * gpm_tray_icon_create_menu:
325  *
326  * Create the popup menu.
327  **/
328 static GtkMenu *
gpm_tray_icon_create_menu(GpmTrayIcon * icon)329 gpm_tray_icon_create_menu (GpmTrayIcon *icon)
330 {
331 	GtkMenu *menu = (GtkMenu*) gtk_menu_new ();
332 	GtkWidget *item;
333 	GtkWidget *image;
334 	guint dev_cnt = 0;
335 	GPtrArray *array;
336 	UpDevice *device = NULL;
337 
338 	/* show the primary device time remaining */
339 	device = gpm_engine_get_primary_device (icon->priv->engine);
340 	if (device != NULL) {
341 		gpm_tray_icon_add_primary_device (icon, menu, device);
342 		item = gtk_separator_menu_item_new ();
343 		gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
344 	}
345 
346 	/* add all device types to the drop down menu */
347 	array = gpm_engine_get_devices (icon->priv->engine);
348 	dev_cnt += gpm_tray_icon_add_device (icon, menu, array, UP_DEVICE_KIND_BATTERY);
349 	dev_cnt += gpm_tray_icon_add_device (icon, menu, array, UP_DEVICE_KIND_UPS);
350 	dev_cnt += gpm_tray_icon_add_device (icon, menu, array, UP_DEVICE_KIND_MOUSE);
351 	dev_cnt += gpm_tray_icon_add_device (icon, menu, array, UP_DEVICE_KIND_KEYBOARD);
352 	dev_cnt += gpm_tray_icon_add_device (icon, menu, array, UP_DEVICE_KIND_PDA);
353 	dev_cnt += gpm_tray_icon_add_device (icon, menu, array, UP_DEVICE_KIND_PHONE);
354 	dev_cnt += gpm_tray_icon_add_device (icon, menu, array, UP_DEVICE_KIND_MEDIA_PLAYER);
355 	dev_cnt += gpm_tray_icon_add_device (icon, menu, array, UP_DEVICE_KIND_TABLET);
356 	dev_cnt += gpm_tray_icon_add_device (icon, menu, array, UP_DEVICE_KIND_COMPUTER);
357 	g_ptr_array_unref (array);
358 
359 	/* skip for things like live-cd's and GDM */
360 	if (!icon->priv->show_actions)
361 		goto skip_prefs;
362 
363 	/* only do the separator if we have at least one device */
364 	if (dev_cnt != 0) {
365 		item = gtk_separator_menu_item_new ();
366 		gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
367 	}
368 
369 	/* preferences */
370 	item = gtk_image_menu_item_new_with_mnemonic (_("_Preferences"));
371 	image = gtk_image_new_from_icon_name ("preferences-system", GTK_ICON_SIZE_MENU);
372 	gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
373 	g_signal_connect (G_OBJECT (item), "activate",
374 			  G_CALLBACK (gpm_tray_icon_show_preferences_cb), icon);
375 	gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
376 
377 	/*Set up custom panel menu theme support-gtk3 only */
378 	GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (menu));
379 	/* Fix any failures of compiz/other wm's to communicate with gtk for transparency in menu theme */
380 	GdkScreen *screen = gtk_widget_get_screen(GTK_WIDGET(toplevel));
381 	GdkVisual *visual = gdk_screen_get_rgba_visual(screen);
382 	gtk_widget_set_visual(GTK_WIDGET(toplevel), visual);
383 	/* Set menu and its toplevel window to follow panel theme */
384 	GtkStyleContext *context;
385 	context = gtk_widget_get_style_context (GTK_WIDGET(toplevel));
386 	gtk_style_context_add_class(context,"gnome-panel-menu-bar");
387 	gtk_style_context_add_class(context,"mate-panel-menu-bar");
388 
389 	/* about */
390 	item = gtk_image_menu_item_new_from_stock ("gtk-about", NULL);
391 	g_signal_connect (G_OBJECT (item), "activate",
392 			  G_CALLBACK (gpm_tray_icon_show_about_cb), icon);
393 	gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
394 
395 skip_prefs:
396 	if (device != NULL)
397 		g_object_unref (device);
398 	return menu;
399 }
400 
401 /**
402  * gpm_tray_icon_popup_cleared_cd:
403  * @widget: The popup Gtkwidget
404  *
405  * We have to re-enable the tooltip when the popup is removed
406  **/
407 static void
gpm_tray_icon_popup_cleared_cd(GtkWidget * widget,GpmTrayIcon * icon)408 gpm_tray_icon_popup_cleared_cd (GtkWidget *widget, GpmTrayIcon *icon)
409 {
410 	g_return_if_fail (GPM_IS_TRAY_ICON (icon));
411 	g_debug ("clear tray");
412 	g_object_ref_sink (widget);
413 	g_object_unref (widget);
414 }
415 
416 /**
417  * gpm_tray_icon_popup_menu:
418  *
419  * Display the popup menu.
420  **/
421 static void
gpm_tray_icon_popup_menu(GpmTrayIcon * icon,guint32 timestamp)422 gpm_tray_icon_popup_menu (GpmTrayIcon *icon, guint32 timestamp)
423 {
424 	GtkMenu *menu;
425 
426 	menu = gpm_tray_icon_create_menu (icon);
427 
428 	/* show the menu */
429 	gtk_widget_show_all (GTK_WIDGET (menu));
430 	gtk_menu_popup (GTK_MENU (menu), NULL, NULL,
431 			gtk_status_icon_position_menu, icon->priv->status_icon,
432 			1, timestamp);
433 
434 	g_signal_connect (GTK_WIDGET (menu), "hide",
435 			  G_CALLBACK (gpm_tray_icon_popup_cleared_cd), icon);
436 }
437 
438 /**
439  * gpm_tray_icon_popup_menu_cb:
440  *
441  * Display the popup menu.
442  **/
443 static void
gpm_tray_icon_popup_menu_cb(GtkStatusIcon * status_icon,guint button,guint32 timestamp,GpmTrayIcon * icon)444 gpm_tray_icon_popup_menu_cb (GtkStatusIcon *status_icon, guint button, guint32 timestamp, GpmTrayIcon *icon)
445 {
446 	g_debug ("icon right clicked");
447 	gpm_tray_icon_popup_menu (icon, timestamp);
448 }
449 
450 
451 /**
452  * gpm_tray_icon_activate_cb:
453  * @button: Which buttons are pressed
454  *
455  * Callback when the icon is clicked
456  **/
457 static void
gpm_tray_icon_activate_cb(GtkStatusIcon * status_icon,GpmTrayIcon * icon)458 gpm_tray_icon_activate_cb (GtkStatusIcon *status_icon, GpmTrayIcon *icon)
459 {
460 	g_debug ("icon left clicked");
461 	gpm_tray_icon_popup_menu (icon, gtk_get_current_event_time());
462 }
463 
464 /**
465  * gpm_tray_icon_settings_changed_cb:
466  *
467  * We might have to do things when the settings change; do them here.
468  **/
469 static void
gpm_tray_icon_settings_changed_cb(GSettings * settings,const gchar * key,GpmTrayIcon * icon)470 gpm_tray_icon_settings_changed_cb (GSettings *settings, const gchar *key, GpmTrayIcon *icon)
471 {
472 	gboolean allowed_in_menu;
473 
474 	if (g_strcmp0 (key, GPM_SETTINGS_SHOW_ACTIONS) == 0) {
475 		allowed_in_menu = g_settings_get_boolean (settings, key);
476 		gpm_tray_icon_enable_actions (icon, allowed_in_menu);
477 	}
478 }
479 
480 /**
481  * gpm_tray_icon_init:
482  *
483  * Initialise the tray object
484  **/
485 static void
gpm_tray_icon_init(GpmTrayIcon * icon)486 gpm_tray_icon_init (GpmTrayIcon *icon)
487 {
488 	gboolean allowed_in_menu;
489 
490 	icon->priv = gpm_tray_icon_get_instance_private (icon);
491 
492 	icon->priv->engine = gpm_engine_new ();
493 
494 	icon->priv->settings = g_settings_new (GPM_SETTINGS_SCHEMA);
495 	g_signal_connect (icon->priv->settings, "changed",
496 			  G_CALLBACK (gpm_tray_icon_settings_changed_cb), icon);
497 
498 	icon->priv->status_icon = gtk_status_icon_new ();
499 	gpm_tray_icon_show (icon, FALSE);
500 	g_signal_connect_object (G_OBJECT (icon->priv->status_icon),
501 				 "popup_menu",
502 				 G_CALLBACK (gpm_tray_icon_popup_menu_cb),
503 				 icon, 0);
504 	g_signal_connect_object (G_OBJECT (icon->priv->status_icon),
505 				 "activate",
506 				 G_CALLBACK (gpm_tray_icon_activate_cb),
507 				 icon, 0);
508 
509 	allowed_in_menu = g_settings_get_boolean (icon->priv->settings, GPM_SETTINGS_SHOW_ACTIONS);
510 	gpm_tray_icon_enable_actions (icon, allowed_in_menu);
511 }
512 
513 /**
514  * gpm_tray_icon_finalize:
515  * @object: This TrayIcon class instance
516  **/
517 static void
gpm_tray_icon_finalize(GObject * object)518 gpm_tray_icon_finalize (GObject *object)
519 {
520 	GpmTrayIcon *tray_icon;
521 
522 	g_return_if_fail (object != NULL);
523 	g_return_if_fail (GPM_IS_TRAY_ICON (object));
524 
525 	tray_icon = GPM_TRAY_ICON (object);
526 
527 	g_object_unref (tray_icon->priv->status_icon);
528 	g_object_unref (tray_icon->priv->engine);
529 	g_return_if_fail (tray_icon->priv != NULL);
530 
531 	G_OBJECT_CLASS (gpm_tray_icon_parent_class)->finalize (object);
532 }
533 
534 /**
535  * gpm_tray_icon_new:
536  * Return value: A new TrayIcon object.
537  **/
538 GpmTrayIcon *
gpm_tray_icon_new(void)539 gpm_tray_icon_new (void)
540 {
541 	GpmTrayIcon *tray_icon;
542 	tray_icon = g_object_new (GPM_TYPE_TRAY_ICON, NULL);
543 	return GPM_TRAY_ICON (tray_icon);
544 }
545 
546