view pkui-icon.c @ 56:63347002d746

Simplify PkuiBackend Make the "check-interval" property construct-only since it is never changed at runtime and schedule the first check for updates in the "constructed" method. Remove unused property get methods. Destroy PkClient and remove periodic check in the "dispose" method.
author Guido Berhoerster <guido+pk-update-icon@berhoerster.name>
date Sun, 01 Jul 2018 22:38:11 +0200
parents b2f6b6b25703
children
line wrap: on
line source

/*
 * (C) 2011 Guido Berhoerster <guido+pk-update-icon@berhoerster.name>
 *
 * Licensed under the GNU General Public License Version 2
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include <glib/gi18n.h>
#include <libnotify/notify.h>
#include "pkui-backend.h"
#include "pkui-icon.h"

G_DEFINE_TYPE(PkuiIcon, pkui_icon, G_TYPE_OBJECT)

#ifndef	UPDATE_VIEWER_COMMAND
#define	UPDATE_VIEWER_COMMAND	""
#endif /* UPDATE_VIEWER_COMMAND */

#define	PKUI_ICON_GET_PRIVATE(obj)	(G_TYPE_INSTANCE_GET_PRIVATE((obj), \
					    PKUI_TYPE_ICON, PkuiIconPrivate))

struct _PkuiIconPrivate
{
	PkuiBackend	*backend;

	GtkStatusIcon	*status_icon;
	GtkWidget	*status_icon_popup_menu;
	NotifyNotification *notification;
	gchar		**update_viewer_argv;
};

static GtkWidget*	icon_popup_menu_create(PkuiIcon *self);
static void	icon_popup_menu_popup(GtkStatusIcon *status_icon, guint button,
    guint activate_time, gpointer user_data);
static void	icon_activated(GtkStatusIcon *status_icon, gpointer user_data);
static void	hide_notification(PkuiIcon *self);
static void	backend_state_changed(PkuiBackend *backend, gpointer user_data);
static void	update_viewer_exited(GPid pid, gint status, gpointer user_data);

static void
pkui_icon_finalize(GObject *gobject)
{
	PkuiIcon	*self = PKUI_ICON(gobject);

	gtk_widget_destroy(self->priv->status_icon_popup_menu);
	g_object_unref(self->priv->status_icon_popup_menu);
	g_object_unref(self->priv->status_icon);
	if (self->priv->notification != NULL) {
		notify_notification_close(self->priv->notification, NULL);
		g_object_unref(self->priv->notification);
	}
	g_object_unref(self->priv->backend);
	g_strfreev(self->priv->update_viewer_argv);

	G_OBJECT_CLASS(pkui_icon_parent_class)->finalize(gobject);
}

static void
pkui_icon_class_init(PkuiIconClass *klass)
{
	GObjectClass	*gobject_class = G_OBJECT_CLASS(klass);

	gobject_class->finalize = pkui_icon_finalize;

	g_type_class_add_private(klass, sizeof (PkuiIconPrivate));
}

static void
pkui_icon_init(PkuiIcon *self)
{
	self->priv = PKUI_ICON_GET_PRIVATE(self);

	gtk_window_set_default_icon_name("system-software-update");

	self->priv->status_icon_popup_menu = icon_popup_menu_create(self);
	g_object_ref(self->priv->status_icon_popup_menu);
	g_object_ref_sink(GTK_OBJECT(self->priv->status_icon_popup_menu));

	self->priv->status_icon = gtk_status_icon_new();
	gtk_status_icon_set_title(self->priv->status_icon,
	    _("Software Updates"));
	gtk_status_icon_set_visible(self->priv->status_icon, FALSE);
	g_signal_connect(G_OBJECT(self->priv->status_icon), "activate",
	    G_CALLBACK(icon_activated), self);
	g_signal_connect(G_OBJECT(self->priv->status_icon), "popup-menu",
	    G_CALLBACK(icon_popup_menu_popup), self);

	self->priv->notification = NULL;
	self->priv->backend = NULL;
	self->priv->update_viewer_argv = NULL;
}

static void
exec_update_viewer(PkuiIcon *self)
{
	GError	*error = NULL;
	GPid	pid;

	g_return_if_fail(PKUI_IS_BACKEND(self->priv->backend));
	g_return_if_fail(self->priv->update_viewer_argv != NULL);

	g_debug("executing update viewer command");

	if (!g_spawn_async(NULL, self->priv->update_viewer_argv, NULL,
	    G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH, NULL, NULL, &pid,
	    &error)) {
		g_warning("Could not execute command: %s", error->message);
		g_error_free(error);
		return;
	}
	g_child_watch_add(pid, (GChildWatchFunc)update_viewer_exited, self);

	pkui_backend_set_inhibit_check(self->priv->backend, TRUE);
	hide_notification(self);
}

static void
about_dialog_show(GtkMenuItem *item, gpointer user_data)
{
	static const gchar	*copyright = "Copyright \xc2\xa9 2011 Guido "
	    "Berhoerster\nCopyright \xc2\xa9 2011 Pavol Rusnak\n";
	static const gchar	*authors[3] = {
	    "Guido Berhoerster <guido+pk-update-icon@berhoerster.name>",
	    "Pavol Rusnak <stick@gk2.sk>",
	    NULL
	};
	static const gchar	*documenters[2] = {
	    "Guido Berhoerster <guido+pk-update-icon@berhoerster.name>",
	    NULL
	};
	static const gchar	*license =
	    "Licensed under the GNU General Public License Version 2\n\n"
	    "This program is free software; you can redistribute it and/or "
	    "modify it under the terms of the GNU General Public License as "
	    "published by the Free Software Foundation; either version 2 of "
	    "the License, or (at your option) any later version.\n\n"
	    "This program is distributed in the hope that it will be useful, "
	    "but WITHOUT ANY WARRANTY; without even the implied warranty of "
	    "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU "
	    "General Public License for more details.\n\n"
	    "You should have received a copy of the GNU General Public License "
	    "along with this program; if not, write to the Free Software "
	    "Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, "
	    "MA 02110-1301 USA.";
	const gchar	*translators = _("translators");

	if (strcmp(translators, "translators") == 0)
	    translators = NULL;

	gtk_show_about_dialog (NULL, "version", VERSION, "copyright", copyright,
	    "authors", authors, "documenters", documenters,
	    "translator-credits", translators, "license", license,
	    "wrap-license", TRUE, NULL);
}

static void
backend_check_now(GtkMenuItem *menu_item, gpointer user_data)
{
	PkuiIcon	*self = PKUI_ICON(user_data);

	g_return_if_fail(PKUI_IS_BACKEND(self->priv->backend));

	pkui_backend_check_now(self->priv->backend);
}

static GtkWidget*
icon_popup_menu_create(PkuiIcon *self)
{
	GtkWidget	*popup_menu = gtk_menu_new();
	GtkWidget	*item;
	GtkWidget	*image;

	item = gtk_image_menu_item_new_with_mnemonic(_("_Check for Updates"));
	image = gtk_image_new_from_icon_name(GTK_STOCK_REFRESH,
	    GTK_ICON_SIZE_MENU);
	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
	gtk_menu_shell_append(GTK_MENU_SHELL(popup_menu), item);
	g_signal_connect(G_OBJECT(item), "activate",
	    G_CALLBACK(backend_check_now), self);

	item = gtk_image_menu_item_new_with_mnemonic(_("_About"));
	image = gtk_image_new_from_icon_name(GTK_STOCK_ABOUT,
	    GTK_ICON_SIZE_MENU);
	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
	gtk_menu_shell_append(GTK_MENU_SHELL(popup_menu), item);
	g_signal_connect(G_OBJECT(item), "activate",
	    G_CALLBACK(about_dialog_show), self);

	gtk_widget_show_all(GTK_WIDGET(popup_menu));

	return (popup_menu);
}

static void
icon_popup_menu_popup(GtkStatusIcon *status_icon, guint button,
    guint activate_time, gpointer user_data)
{
	PkuiIcon	*self = PKUI_ICON(user_data);

	gtk_menu_popup(GTK_MENU(self->priv->status_icon_popup_menu), NULL, NULL,
	    NULL, NULL, button, activate_time);
}

static void
notification_handle_action(NotifyNotification *notification, gchar *action,
    gpointer user_data)
{
	PkuiIcon	*self = PKUI_ICON(user_data);

	if (strcmp(action, "install-updates") == 0) {
		exec_update_viewer(self);
	}
}

static void
update_notification(PkuiIcon *self, guint updates_normal,
    guint updates_important)
{
	gchar	*message;
	gchar	*message_a;
	gchar	*message_b;
	gchar	*title = updates_important ?
	    /* TRANSLATORS: This is a message without number mentioned */
	    ngettext("Important Software Update", "Important Software Updates",
	    updates_important + updates_normal) :
	    /* TRANSLATORS: This is a message without number mentioned */
	    ngettext("Software Update", "Software Updates", updates_important +
	    updates_normal);
	gchar	*icon = updates_important ? "software-update-urgent" :
	    "software-update-available";
	gchar	*program = NULL;

	if (updates_important > 0) {
		if (updates_normal > 0) {
			message_a = g_strdup_printf(
			/*
			 * TRANSLATORS: This sentence contains two plurals.
			 * Texts related to these plurals are mixed. That is
			 * why it is split in three parts. Fill first two parts
			 * as you need, and use them as %s in the last one to
			 * construct a sentence. Note that if the first form of
			 * plural relates only to singular form, it is never
			 * used, and dedicated shorter sentences are used. */
			    ngettext("There is %d software update available,",
			    "There are %d software updates available,",
			    updates_normal + updates_important),
			    updates_normal + updates_important);
			/*
			 * TRANSLATORS: This is the sentence part in the
			 * middle, form of which is related to the first
			 * number. If your language does not need it, simply
			 * use it as space or so. */
			message_b = ngettext("of it", "of them",
			    updates_normal + updates_important);
			message = g_strdup_printf(
			/*
			 * TRANSLATORS: This forms the sentence. If you need to
			 * swap parts, use %3$s and %1$s etc. Plurals are
			 * related to second number. */
			    ngettext("%s %d %s is important.",
			    "%s %d %s are important.", updates_important),
			    message_a, updates_important, message_b);
			g_free(message_a);
		} else {
			message = g_strdup_printf(ngettext("There is %d "
			    "important software update available.",
			    "There are %d important software updates "
			    "available.", updates_important),
			    updates_important);
		}
	} else {
		message = g_strdup_printf(ngettext("There is %d software "
		    "update available.",
		    "There are %d software updates available.",
		    updates_normal), updates_normal);
	}

	gtk_status_icon_set_tooltip(self->priv->status_icon, message);
	gtk_status_icon_set_from_icon_name(self->priv->status_icon, icon);
	gtk_status_icon_set_visible(self->priv->status_icon, TRUE);

	if (self->priv->notification == NULL) {
		self->priv->notification = notify_notification_new(title,
		    message, icon
#if (NOTIFY_VERSION_MAJOR == 0 && NOTIFY_VERSION_MINOR < 7)
		    , NULL
#endif
		    );
	} else {
		notify_notification_update(self->priv->notification, title,
		    message, icon);
	}

	notify_notification_clear_actions(self->priv->notification);
	program = (self->priv->update_viewer_argv != NULL) ?
	    g_find_program_in_path(self->priv->update_viewer_argv[0]) : NULL;
	if (program != NULL) {
		notify_notification_add_action(self->priv->notification,
		     "install-updates", ngettext("Install Update",
		     "Install Updates", updates_normal + updates_important),
		     notification_handle_action, self, NULL);
	}

	notify_notification_set_timeout(self->priv->notification,
	    NOTIFY_EXPIRES_NEVER);
	notify_notification_set_urgency(self->priv->notification,
	    updates_important ? NOTIFY_URGENCY_CRITICAL :
	    NOTIFY_URGENCY_NORMAL);
	notify_notification_show(self->priv->notification, NULL);

	g_free(program);
	g_free(message);
}

static void
hide_notification(PkuiIcon *self)
{
	gtk_status_icon_set_visible(self->priv->status_icon, FALSE);
	notify_notification_close(self->priv->notification, NULL);
}


static void
backend_state_changed(PkuiBackend *backend, gpointer user_data)
{
	PkuiIcon	*self = PKUI_ICON(user_data);
	guint		updates_normal;
	guint		updates_important;

	g_return_if_fail(PKUI_IS_BACKEND(backend));

	updates_normal = pkui_backend_get_updates_normal(backend);
	updates_important = pkui_backend_get_updates_important(backend);
	if (updates_normal > 0 || updates_important > 0)
		update_notification(self, updates_normal, updates_important);
	else if (updates_normal + updates_important == 0)
		hide_notification(self);
}

static void
update_viewer_exited(GPid pid, gint status, gpointer user_data)
{
	PkuiIcon	*self = PKUI_ICON(user_data);

	g_return_if_fail(PKUI_IS_BACKEND(self->priv->backend));

	g_spawn_close_pid(pid);

	pkui_backend_check_now(self->priv->backend);
	pkui_backend_set_inhibit_check(self->priv->backend, FALSE);
}

static void
icon_activated(GtkStatusIcon *status_icon, gpointer user_data)
{
	PkuiIcon	*self = PKUI_ICON(user_data);
	gchar		*program = NULL;

	program = (self->priv->update_viewer_argv != NULL) ?
	    g_find_program_in_path(self->priv->update_viewer_argv[0]) :
	    NULL;
	if (program != NULL) {
		exec_update_viewer(self);
	}

	g_free(program);
}

PkuiIcon *
pkui_icon_new(guint startup_delay, guint check_interval,
    const gchar *update_viewer_command)
{
	GError		*error = NULL;
	PkuiIcon	*icon = g_object_new(PKUI_TYPE_ICON, NULL);

	if (update_viewer_command == NULL) {
		update_viewer_command = UPDATE_VIEWER_COMMAND;
	}

	if (!g_shell_parse_argv(update_viewer_command, NULL,
	    &icon->priv->update_viewer_argv, &error)) {
		g_warning("Could not parse command: %s", error->message);
		g_error_free(error);
	}

	icon->priv->backend = pkui_backend_new(startup_delay, check_interval);
	g_signal_connect(icon->priv->backend, "state-changed",
	    G_CALLBACK(backend_state_changed), icon);

	return (icon);
}