# HG changeset patch # User Guido Berhoerster # Date 1319091562 -7200 # Node ID 64f05992d8ec89696eaa979475b2b2ea658b08ac # Parent dca97330d81e103f6dbed083b7dd55d4897e268f GObject-based rewrite use asynchronous packagekit-glib API use persistent menu widget and notification object update existing notification when new updates become available, close it when no updates are available show status icon when updates are available, hide it when no updates are available hide icon when gpk-update-viewer is executed, check for updates when gpk-update-viewer exits introduce a startup delay before the first check for updates is made add context menu item to manually trigger a check for updates remove context menu item for quitting pk-update-icon diff -r dca97330d81e -r 64f05992d8ec Makefile --- a/Makefile Tue Oct 11 17:36:32 2011 +0200 +++ b/Makefile Thu Oct 20 08:19:22 2011 +0200 @@ -18,7 +18,7 @@ PACKAGE = pk-update-icon APP_NAME = org.opensuse.pk-update-icon VERSION = 0.1 -OBJS = main.o notify.o packagekit.o +OBJS = main.o pkui-icon.o pkui-backend.o AUTOSTART_FILE = $(PACKAGE).desktop MOFILES := $(patsubst %.po,%.mo,$(wildcard po/*.po)) POTFILE = po/$(PACKAGE).pot diff -r dca97330d81e -r 64f05992d8ec design.txt --- a/design.txt Tue Oct 11 17:36:32 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,64 +0,0 @@ -Design -====== - -Objects: -OUIApp(GtkApplication) - - Icon *icon - - Backend *backend - - GSettings *settings - * bind preferences to settings above - + check_frequency - + check_mobile_connection - + startup_timeout - + notify_package - + notify_patch_critical - + notify_patch_recommended - + notify_patch_optional - - PreferencesDialog *preferences_dialog - -startup: - unique - one-shot timer - regular timer - hide -one-shot timer: check update -timer: 24 h check update -check update: - if battery critical: - set timer +1h - if not online: - set timer +1h - if on mobile and no allowed: - set timer +1h - run /usr/sbin/zypp-refresh-wrapper - run zypper --xmlout list-updates --type patch - run zypper --xmlout list-updates --type package - parse XML output - merge package and patch updates? - if updates: - select icon based on type of update - display icon and tooltip with number of updates - else: - hide -left click on icon: - start yast2 online_update -right click: - preferences - about -preferences: - interval (hourly/daily/weekly/never) - check on mobile internet - - -TODO: -parse xml -DBus check -check low power with upower -check connection with NetworkManager -unique application -see packagekit - - -Defer to later: -NM online and mobile check -UPower battery check diff -r dca97330d81e -r 64f05992d8ec main.c --- a/main.c Tue Oct 11 17:36:32 2011 +0200 +++ b/main.c Thu Oct 20 08:19:22 2011 +0200 @@ -1,5 +1,4 @@ /* - * Copyright (C) 2011 Pavol Rusnak * Copyright (C) 2011 Guido Berhoerster * * Licensed under the GNU General Public License Version 2 @@ -19,103 +18,47 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "main.h" -#include "notify.h" -#include "packagekit.h" #include #include #include #include - -struct UpdatesInfo info; - -GtkStatusIcon *tray_icon; +#include +#include "pkui-icon.h" -void tray_icon_on_click(GtkStatusIcon *status_icon, gpointer user_data) -{ - char *argv[2] = { "gpk-update-viewer", NULL }; - g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL); -} - -static void tray_menu_destroy(GtkWidget *menu, gpointer userdata) -{ - gtk_widget_destroy(menu); - g_object_unref(menu); -} - -void tray_icon_on_menu(GtkStatusIcon *status_icon, guint button, guint activate_time, gpointer user_data) +int +main(int argc, char **argv) { - GtkWidget *item; - GtkWidget *menu = gtk_menu_new(); - item = gtk_menu_item_new_with_mnemonic(_("_Quit")); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); - g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(gtk_main_quit), user_data); - gtk_widget_show(item); - g_object_ref(menu); - g_object_ref_sink(menu); - g_object_unref(menu); - g_signal_connect(G_OBJECT(menu), "selection-done", G_CALLBACK(tray_menu_destroy), NULL); - gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, user_data, button, activate_time); - gtk_widget_show_all(GTK_WIDGET(menu)); -} - -static GtkStatusIcon *create_tray_icon() -{ - tray_icon = gtk_status_icon_new(); - g_signal_connect(G_OBJECT(tray_icon), "activate", G_CALLBACK(tray_icon_on_click), NULL); - g_signal_connect(G_OBJECT(tray_icon), "popup-menu", G_CALLBACK(tray_icon_on_menu), NULL); - gtk_status_icon_set_from_icon_name(tray_icon, "system-software-update"); - gtk_status_icon_set_title(tray_icon, _("Software Update(s)")); - gtk_status_icon_set_visible(tray_icon, TRUE); - return tray_icon; -} - -gboolean periodic_check(gpointer user_data) -{ - query_packagekit(&info); - if (info.critical > 0) { - gtk_status_icon_set_from_icon_name(tray_icon, "software-update-urgent"); - gtk_status_icon_set_tooltip_text(tray_icon, notify_text(&info)); - } else - if (info.normal > 0) { - gtk_status_icon_set_from_icon_name(tray_icon, "software-update-available"); - gtk_status_icon_set_tooltip_text(tray_icon, notify_text(&info)); - } - return TRUE; -} - -int main(int argc, char **argv) -{ - GtkStatusIcon *tray_icon; - UniqueApp *app; - int exitval = 0; + PkuiIcon *icon; + UniqueApp *app; + int exitval = 0; setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); bind_textdomain_codeset(PACKAGE, "UTF-8"); textdomain(PACKAGE); gtk_init(&argc, &argv); + app = unique_app_new(APP_NAME, NULL); if (unique_app_is_running(app)) { - g_printerr("Another instance of pk-update-icon is already running. Exiting.\n"); + g_printerr("Another instance of pk-update-icon is already " + "running. Exiting.\n"); exitval = 1; goto out; } - tray_icon = create_tray_icon(); - init_notify(); - periodic_check(NULL); - send_notify(&info); - // update tray icon and tooltip every 2 hours - g_timeout_add_seconds(2*3600, periodic_check, NULL); + notify_init(PACKAGE); + + icon = pkui_icon_new(10, 2 * 3600); gtk_main(); - g_object_unref(tray_icon); - + g_object_unref(icon); out: g_object_unref(app); + if (notify_is_initted()) + notify_uninit (); - return exitval; + return (exitval); } diff -r dca97330d81e -r 64f05992d8ec main.h --- a/main.h Tue Oct 11 17:36:32 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2011 Pavol Rusnak - * Copyright (C) 2011 Guido Berhoerster - * - * 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. - */ - -#ifndef MAIN_H -#define MAIN_H - -struct UpdatesInfo { - int normal; - int critical; -}; - -#endif diff -r dca97330d81e -r 64f05992d8ec notify.c --- a/notify.c Tue Oct 11 17:36:32 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2011 Pavol Rusnak - * Copyright (C) 2011 Guido Berhoerster - * - * 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 "notify.h" - -void init_notify() -{ - notify_init(PACKAGE); -} - -void send_notify(struct UpdatesInfo *info) -{ - NotifyNotification *ntfy; - ntfy = notify_notification_new( - _("Software Update(s)"), - _(notify_text(info)), - info->critical > 0 ? "software-update-urgent" : "software-update-available" -#if (NOTIFY_VERSION_MAJOR == 0 && NOTIFY_VERSION_MINOR < 7) - , NULL -#endif - ); - notify_notification_set_timeout(ntfy, 3000); - notify_notification_set_urgency(ntfy, info->critical > 0 ? NOTIFY_URGENCY_CRITICAL : NOTIFY_URGENCY_NORMAL); - notify_notification_show(ntfy, NULL); -} - -const char *notify_text(struct UpdatesInfo *info) -{ - static char buf[128]; - if (info->critical > 0) { - snprintf(buf, sizeof(buf), _("There are %d software updates available, %d of them critical."), info->normal + info->critical, info->critical); - } else { - snprintf(buf, sizeof(buf), _("There are %d software updates available."), info->normal); - } - return buf; -} diff -r dca97330d81e -r 64f05992d8ec notify.h --- a/notify.h Tue Oct 11 17:36:32 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2011 Pavol Rusnak - * Copyright (C) 2011 Guido Berhoerster - * - * 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. - */ - -#ifndef NOTIFY_H -#define NOTIFY_H - -#include -#include -#include -#include "main.h" - -void init_notify(); -void send_notify(struct UpdatesInfo *info); -const char *notify_text(struct UpdatesInfo *info); - -#endif diff -r dca97330d81e -r 64f05992d8ec packagekit.c --- a/packagekit.c Tue Oct 11 17:36:32 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2011 Pavol Rusnak - * - * 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 -#include "packagekit.h" - -void process(PkPackage *pkg, struct UpdatesInfo *info) -{ - PkInfoEnum e = pk_package_get_info(pkg); - switch (e) { - case PK_INFO_ENUM_LOW: - case PK_INFO_ENUM_ENHANCEMENT: - case PK_INFO_ENUM_NORMAL: - info->normal++; - break; - case PK_INFO_ENUM_BUGFIX: - case PK_INFO_ENUM_IMPORTANT: - case PK_INFO_ENUM_SECURITY: - info->critical++; - break; - default: - break; - } -} - -void query_packagekit(struct UpdatesInfo *info) -{ - PkClient *client = NULL; - PkResults *res = NULL; - GPtrArray *list = NULL; - GFunc process_func = (GFunc)process; - client = pk_client_new(); - res = pk_client_get_updates(client, pk_bitfield_value(PK_FILTER_ENUM_NEWEST), NULL, NULL, NULL, NULL); - if (!res) { - goto out; - } - list = pk_results_get_package_array(res); - if (!list) { - goto out; - } - info->normal = 0; - info->critical = 0; - g_ptr_array_foreach(list, process_func, info); -out: - if (list != NULL) - g_ptr_array_unref(list); - if(res != NULL) - g_object_unref(res); - if(client != NULL) - g_object_unref(client); -} diff -r dca97330d81e -r 64f05992d8ec packagekit.h --- a/packagekit.h Tue Oct 11 17:36:32 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2011 Pavol Rusnak - * - * 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. - */ - -#ifndef PACKAGEKIT_H -#define PACKAGEKIT_H 1 - -#include "main.h" - -void query_packagekit(struct UpdatesInfo *info); - -#endif diff -r dca97330d81e -r 64f05992d8ec pkui-backend.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkui-backend.c Thu Oct 20 08:19:22 2011 +0200 @@ -0,0 +1,418 @@ +/* + * (C) 2011 Guido Berhoerster + * + * 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 +#include "pkui-backend.h" + +G_DEFINE_TYPE(PkuiBackend, pkui_backend, G_TYPE_OBJECT) + +#define PKUI_BACKEND_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), \ + PKUI_TYPE_BACKEND, PkuiBackendPrivate)) + +struct _PkuiBackendPrivate +{ + PkuiBackend *backend; + PkClient *pk_client; + guint periodic_check_id; + guint updates_normal; + guint updates_important; + guint previous_updates_normal; + guint previous_updates_important; + guint startup_delay; + guint check_interval; + gint64 last_check; + gboolean inhibit_check; +}; + +enum +{ + PROP_0, + + PROP_UPDATES_NORMAL, + PROP_UPDATES_IMPORTANT, + PROP_STARTUP_DELAY, + PROP_CHECK_INTERVAL, + PROP_INHIBIT_CHECK +}; + +enum +{ + STATE_CHANGED_SIGNAL, + + LAST_SIGNAL +}; + +static guint pkui_backend_signals[LAST_SIGNAL] = { 0, }; + +static gboolean periodic_check(gpointer data); + +static void +pkui_backend_set_property(GObject *object, guint property_id, + const GValue *value, GParamSpec *pspec) +{ + PkuiBackend *self = PKUI_BACKEND(object); + gboolean inhibit_check; + gint64 time_to_check; + + switch (property_id) { + case PROP_STARTUP_DELAY: + self->priv->startup_delay = g_value_get_uint(value); + + g_source_remove(self->priv->periodic_check_id); + self->priv->periodic_check_id = + g_timeout_add_seconds(self->priv->startup_delay, + (GSourceFunc)periodic_check, self); + break; + case PROP_CHECK_INTERVAL: + self->priv->check_interval = g_value_get_uint(value); + + /* + * reschedule only if the first run has been completed and + * checks are currently not inibited, otherwise the new + * interval will be picked up anyway + */ + if (!self->priv->inhibit_check && self->priv->last_check > 0) { + time_to_check = g_get_real_time() - + self->priv->last_check; + if (time_to_check <= 0) + pkui_backend_check_now(self); + else { + g_source_remove(self->priv->periodic_check_id); + self->priv->periodic_check_id = + g_timeout_add_seconds(time_to_check, + periodic_check, self); + } + } + break; + case PROP_INHIBIT_CHECK: + inhibit_check = g_value_get_boolean(value); + + /* + * when changing self->priv->inhibit_check from FALSE to TRUE + * and the first run has been completed remove the periodic + * check, when changing from TRUE to FALSE either trigger a run + * immediately when more time than self->priv->check_interval + * has passed or just reschedule the next check + */ + if (!self->priv->inhibit_check && inhibit_check && + self->priv->periodic_check_id != 0) { + g_source_remove(self->priv->periodic_check_id); + self->priv->periodic_check_id = 0; + } else if (self->priv->inhibit_check && !inhibit_check) { + self->priv->periodic_check_id = + g_timeout_add_seconds(self->priv->check_interval, + (GSourceFunc)periodic_check, self); + } + self->priv->inhibit_check = inhibit_check; + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + break; + } +} + +static void +pkui_backend_get_property(GObject *object, guint property_id, GValue *value, + GParamSpec *pspec) +{ + PkuiBackend *self = PKUI_BACKEND(object); + + switch (property_id) { + case PROP_UPDATES_NORMAL: + g_value_set_uint(value, self->priv->updates_normal); + break; + case PROP_UPDATES_IMPORTANT: + g_value_set_uint(value, self->priv->updates_important); + break; + case PROP_STARTUP_DELAY: + g_value_set_uint(value, self->priv->startup_delay); + break; + case PROP_CHECK_INTERVAL: + g_value_set_uint(value, self->priv->check_interval); + break; + case PROP_INHIBIT_CHECK: + g_value_set_boolean(value, self->priv->inhibit_check); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + break; + } +} + +static void +pkui_backend_finalize(GObject *gobject) +{ + PkuiBackend *self = PKUI_BACKEND(gobject); + + if (self->priv->pk_client != NULL) + g_object_unref(self->priv->pk_client); + + G_OBJECT_CLASS(pkui_backend_parent_class)->finalize(gobject); +} + +static void +pkui_backend_class_init(PkuiBackendClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS(klass); + GParamSpec *pspec; + + gobject_class->set_property = pkui_backend_set_property; + gobject_class->get_property = pkui_backend_get_property; + gobject_class->finalize = pkui_backend_finalize; + + pspec = g_param_spec_uint("updates-normal", "Normal updates", + "Number of normal package updates", 0, G_MAXUINT, 0, + G_PARAM_READABLE); + g_object_class_install_property(gobject_class, PROP_UPDATES_NORMAL, + pspec); + + pspec = g_param_spec_uint("updates-important", "Important updates", + "Number of important package updates", 0, G_MAXUINT, 0, + G_PARAM_READABLE); + g_object_class_install_property(gobject_class, PROP_UPDATES_IMPORTANT, + pspec); + + pspec = g_param_spec_uint("startup-delay", "Startup delay", + "Initial delay in second before the first check for new package " + "updates", 0, G_MAXUINT, 60, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + g_object_class_install_property(gobject_class, PROP_STARTUP_DELAY, + pspec); + + pspec = g_param_spec_uint("check-interval", "Check interval", + "Interval in seconds for checking for new package updates", 1, + G_MAXUINT, 3600, G_PARAM_READWRITE | G_PARAM_CONSTRUCT); + g_object_class_install_property(gobject_class, PROP_CHECK_INTERVAL, + pspec); + + pspec = g_param_spec_boolean("inhibit-check", "Inhibit check", + "Whether to inhibit checks for new package updates", FALSE, + G_PARAM_READWRITE); + g_object_class_install_property(gobject_class, PROP_INHIBIT_CHECK, + pspec); + + pkui_backend_signals[STATE_CHANGED_SIGNAL] = + g_signal_newv("state-changed", G_TYPE_FROM_CLASS(gobject_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, NULL, + NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0, NULL); + + g_type_class_add_private(klass, sizeof (PkuiBackendPrivate)); +} + +static void +pkui_backend_init(PkuiBackend *self) +{ + self->priv = PKUI_BACKEND_GET_PRIVATE(self); + + self->priv->pk_client = pk_client_new(); + self->priv->updates_normal = 0; + self->priv->updates_important = 0; + self->priv->periodic_check_id = + g_timeout_add_seconds(self->priv->startup_delay, + (GSourceFunc)periodic_check, self); + self->priv->last_check = 0; +} + +static gboolean +periodic_check(gpointer data) +{ + PkuiBackend *self = PKUI_BACKEND(data); + + pkui_backend_check_now(self); + + /* rescheduling happens after results are available */ + return (FALSE); +} + +static void +process_pk_package_info(PkPackage *pkg, gpointer *user_data) +{ + PkuiBackend *self = PKUI_BACKEND(user_data); + PkInfoEnum type_info = pk_package_get_info(pkg); + + switch (type_info) { + case PK_INFO_ENUM_LOW: + case PK_INFO_ENUM_ENHANCEMENT: + case PK_INFO_ENUM_NORMAL: + self->priv->updates_normal++; + break; + case PK_INFO_ENUM_BUGFIX: + case PK_INFO_ENUM_IMPORTANT: + case PK_INFO_ENUM_SECURITY: + self->priv->updates_important++; + break; + default: + break; + } +} + +static void +get_updates_finished(GObject *object, GAsyncResult *res, gpointer user_data) +{ + PkClient *client = PK_CLIENT(object); + PkuiBackend *self = PKUI_BACKEND(user_data); + PkResults *results = NULL; + GError *error = NULL; + GPtrArray *list = NULL; + + g_debug("PackageKit finished checking for updates"); + + results = pk_client_generic_finish(PK_CLIENT(client), res, &error); + if (results == NULL) { + g_warning("error: %s\n", error->message); + g_error_free(error); + goto out; + } + + list = pk_results_get_package_array(results); + if (list == NULL) + goto out; + self->priv->previous_updates_normal = self->priv->updates_normal; + self->priv->previous_updates_important = self->priv->updates_important; + self->priv->updates_normal = 0; + self->priv->updates_important = 0; + g_ptr_array_foreach(list, (GFunc)process_pk_package_info, self); + + if (self->priv->updates_normal != self->priv->previous_updates_normal || + self->priv->updates_important != + self->priv->previous_updates_important) { + g_debug("normal updates: %d, important updates: %d", + self->priv->updates_normal, self->priv->updates_important); + g_debug("emit state-changed"); + + g_signal_emit(self, pkui_backend_signals[STATE_CHANGED_SIGNAL], + 0); + } + +out: + if (results != NULL) + g_object_unref(results); + if (list != NULL) + g_ptr_array_unref(list); + + self->priv->last_check = g_get_real_time(); + + if (!self->priv->inhibit_check) { + if (self->priv->periodic_check_id != 0) + g_source_remove(self->priv->periodic_check_id); + self->priv->periodic_check_id = + g_timeout_add_seconds(self->priv->check_interval, + (GSourceFunc)periodic_check, self); + } +} + +PkuiBackend * +pkui_backend_new(guint startup_delay, guint check_interval) +{ + g_return_val_if_fail(check_interval > 0, NULL); + + return (g_object_new(PKUI_TYPE_BACKEND, "startup-delay", startup_delay, + "check-interval", check_interval, NULL)); +} + +guint +pkui_backend_get_updates_normal(PkuiBackend *self) +{ + guint updates_normal; + + g_return_val_if_fail(PKUI_IS_BACKEND(self), 0); + + g_object_get(G_OBJECT(self), "updates-normal", &updates_normal, NULL); + + return (updates_normal); +} + +guint +pkui_backend_get_updates_important(PkuiBackend *self) +{ + guint updates_important; + + g_return_val_if_fail(PKUI_IS_BACKEND(self), 0); + + g_object_get(G_OBJECT(self), "updates-important", &updates_important, + NULL); + + return (updates_important); +} + +void +pkui_backend_set_inhibit_check(PkuiBackend *self, gboolean inhibit_check) +{ + g_return_if_fail(PKUI_IS_BACKEND(self)); + + g_object_set(G_OBJECT(self), "inhibit-check", inhibit_check, NULL); +} + +gboolean +pkui_backend_get_inhibit_check(PkuiBackend *self) +{ + gboolean inhibit_check; + + g_return_val_if_fail(PKUI_IS_BACKEND(self), FALSE); + + g_object_get(G_OBJECT(self), "inhibit-check", &inhibit_check, NULL); + + return (inhibit_check); +} + +guint +pkui_backend_get_startup_interval(PkuiBackend *self) +{ + guint startup_interval; + + g_return_val_if_fail(PKUI_IS_BACKEND(self), 0); + + g_object_get(G_OBJECT(self), "startup-interval", &startup_interval, + NULL); + + return (startup_interval); +} + +void +pkui_backend_set_check_interval(PkuiBackend *self, guint check_interval) +{ + g_return_if_fail(PKUI_IS_BACKEND(self)); + + g_object_set(G_OBJECT(self), "check-interval", check_interval, NULL); +} + +guint +pkui_backend_get_check_interval(PkuiBackend *self) +{ + guint check_interval; + + g_return_val_if_fail(PKUI_IS_BACKEND(self), 0); + + g_object_get(G_OBJECT(self), "check-interval", &check_interval, NULL); + + return (check_interval); +} + +void +pkui_backend_check_now(PkuiBackend *self) +{ + g_return_if_fail(PKUI_IS_BACKEND(self)); + + g_debug("querying PackageKit for updates"); + + pk_client_get_updates_async(self->priv->pk_client, + pk_bitfield_value(PK_FILTER_ENUM_NONE), NULL, NULL, NULL, + get_updates_finished, self); +} diff -r dca97330d81e -r 64f05992d8ec pkui-backend.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkui-backend.h Thu Oct 20 08:19:22 2011 +0200 @@ -0,0 +1,69 @@ +/* + * (C) 2011 Guido Berhoerster + * + * 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. + */ + +#ifndef __PKUI_BACKEND_H +#define __PKUI_BACKEND_H + +#include +#include + +G_BEGIN_DECLS + +#define PKUI_TYPE_BACKEND (pkui_backend_get_type()) +#define PKUI_BACKEND(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + PKUI_TYPE_BACKEND, PkuiBackend)) +#define PKUI_IS_BACKEND(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), \ + PKUI_TYPE_BACKEND)) +#define PKUI_BACKEND_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), \ + PKUI_TYPE_BACKEND, PkuiBackendClass)) +#define PKUI_IS_BACKEND_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), \ + PKUI_TYPE_BACKEND)) +#define PKUI_BACKEND_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), \ + PKUI_TYPE_BACKEND, PkuiBackendClass)) + +typedef struct _PkuiBackend PkuiBackend; +typedef struct _PkuiBackendClass PkuiBackendClass; +typedef struct _PkuiBackendPrivate PkuiBackendPrivate; + +struct _PkuiBackend +{ + GObject parent_instance; + + PkuiBackendPrivate *priv; +}; + +struct _PkuiBackendClass +{ + GObjectClass parent_class; +}; + +PkuiBackend *pkui_backend_new(guint startup_delay, guint interval); +guint pkui_backend_get_updates_normal(PkuiBackend *self); +guint pkui_backend_get_updates_important(PkuiBackend *self); +void pkui_backend_set_inhibit_check(PkuiBackend *self, gboolean inhibit_check); +gboolean pkui_backend_get_inhibit_check(PkuiBackend *self); +void pkui_backend_set_check_interval(PkuiBackend *self, guint check_interval); +guint pkui_backend_get_check_interval(PkuiBackend *self); +guint pkui_backend_get_startup_interval(PkuiBackend *self); +void pkui_backend_check_now(PkuiBackend *self); + +G_END_DECLS + +#endif /* __PKUI_BACKEND_H */ diff -r dca97330d81e -r 64f05992d8ec pkui-icon.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkui-icon.c Thu Oct 20 08:19:22 2011 +0200 @@ -0,0 +1,267 @@ +/* + * (C) 2011 Guido Berhoerster + * + * 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 +#include +#include "pkui-backend.h" +#include "pkui-icon.h" + +G_DEFINE_TYPE(PkuiIcon, pkui_icon, G_TYPE_OBJECT) + +#define UPDATE_VIEWER_COMMAND "/usr/bin/gpk-update-viewer" + +#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; +}; + +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 backend_state_changed(PkuiBackend *backend, 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_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; +} + +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); + + 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 +update_notification(PkuiIcon *self, guint updates_normal, + guint updates_important) +{ + gchar *message; + gchar *title = updates_important ? + ngettext("Important Software Update", "Important Software Updates", + updates_important + updates_normal) : + ngettext("Software Update", "Software Updates", updates_important + + updates_normal); + gchar *icon = updates_important ? "software-update-urgent" : + "software-update-available"; + + if (updates_important > 0) + if (updates_normal > 0) + message = g_strdup_printf(ngettext("There are %d " + "software updates available, %d of them is " + "important.", "There are %d software updates " + "available, %d of them are important.", + updates_important), + updates_normal + updates_important, + updates_important); + else + message = g_strdup_printf(ngettext("There is an " + "important software update available.", "There are " + "%d important software updates available.", + updates_important), updates_important); + else + message = g_strdup_printf(ngettext("There is a 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_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(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); + static const gchar *argv[] = { UPDATE_VIEWER_COMMAND, NULL }; + GPid pid; + gboolean retval; + + g_return_if_fail(PKUI_IS_BACKEND(self->priv->backend)); + + retval = g_spawn_async(NULL, (gchar **)argv, NULL, + G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &pid, NULL); + if (!retval) { + g_warning("Could not execute" UPDATE_VIEWER_COMMAND); + return; + } + g_child_watch_add(pid, (GChildWatchFunc)update_viewer_exited, self); + + pkui_backend_set_inhibit_check(self->priv->backend, TRUE); + hide_notification(self); +} + +PkuiIcon * +pkui_icon_new(guint startup_delay, guint check_interval) +{ + PkuiIcon *icon = g_object_new(PKUI_TYPE_ICON, NULL); + + 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); +} diff -r dca97330d81e -r 64f05992d8ec pkui-icon.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkui-icon.h Thu Oct 20 08:19:22 2011 +0200 @@ -0,0 +1,60 @@ +/* + * (C) 2011 Guido Berhoerster + * + * 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. + */ + +#ifndef __PKUI_ICON_H +#define __PKUI_ICON_H + +#include +#include + +G_BEGIN_DECLS + +#define PKUI_TYPE_ICON (pkui_icon_get_type()) +#define PKUI_ICON(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + PKUI_TYPE_ICON, PkuiIcon)) +#define PKUI_IS_ICON(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), \ + PKUI_TYPE_ICON)) +#define PKUI_ICON_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), \ + PKUI_TYPE_ICON, PkuiIconClass)) +#define PKUI_IS_ICON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), \ + PKUI_TYPE_ICON)) +#define PKUI_ICON_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), \ + PKUI_TYPE_ICON, PkuiIconClass)) + +typedef struct _PkuiIcon PkuiIcon; +typedef struct _PkuiIconClass PkuiIconClass; +typedef struct _PkuiIconPrivate PkuiIconPrivate; + +struct _PkuiIcon +{ + GObject parent_instance; + PkuiIconPrivate *priv; +}; + +struct _PkuiIconClass +{ + GObjectClass parent_class; +}; + +PkuiIcon *pkui_icon_new(guint startup_delay, guint check_interval); + +G_END_DECLS + +#endif /* __PKUI_ICON_H */ diff -r dca97330d81e -r 64f05992d8ec po/POTFILES.in --- a/po/POTFILES.in Tue Oct 11 17:36:32 2011 +0200 +++ b/po/POTFILES.in Thu Oct 20 08:19:22 2011 +0200 @@ -1,4 +1,4 @@ main.c -notify.c -packagekit.c +pkui-icon.c +pkui-backend.c pk-update-icon.desktop.in diff -r dca97330d81e -r 64f05992d8ec po/de.po --- a/po/de.po Tue Oct 11 17:36:32 2011 +0200 +++ b/po/de.po Thu Oct 20 08:19:22 2011 +0200 @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: pk-update-icon 0.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-10-10 19:19+0200\n" +"POT-Creation-Date: 2011-10-19 23:05+0200\n" "PO-Revision-Date: 2011-10-10 19:21+0200\n" "Last-Translator: Guido Berhoerster \n" "Language-Team: German\n" @@ -17,28 +17,50 @@ "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: ../main.c:49 -msgid "_Quit" -msgstr "_Verlassen" +#: ../pkui-icon.c:86 +msgid "Software Updates" +msgstr "Softwareaktualisierungen" + +#: ../pkui-icon.c:115 +msgid "_Check for Updates" +msgstr "Nach Aktualisierungen _Suchen" -#: ../main.c:67 ../notify.c:32 -msgid "Software Update(s)" -msgstr "Software Update(s)" +#: ../pkui-icon.c:144 +msgid "Important Software Update" +msgid_plural "Important Software Updates" +msgstr[0] "Wichtige Aktualisierung" +msgstr[1] "Wichtige Aktualisierungen" + +#: ../pkui-icon.c:146 +msgid "Software Update" +msgid_plural "Software Updates" +msgstr[0] "Softwareaktualisierung" +msgstr[1] "Softwareaktualisierungen" -#: ../notify.c:42 -#, c-format -msgid "There are %d software updates available, %d of them critical." -msgstr "%d Software Updates sind verfügbar, %d davon sind kritisch." +#: ../pkui-icon.c:153 +msgid "There are %d software updates available, %d of them is important." +msgid_plural "" +"There are %d software updates available, %d of them are important." +msgstr[0] "Es sind %d Softwareaktualisierungen verfügbar, davon ist %d wichtig." +msgstr[1] "Es sind %d Softwareaktualisierungen verfügbar, davon sind %d wichtig." -#: ../notify.c:44 +#: ../pkui-icon.c:161 +msgid "There is an important software update available." +msgid_plural "There are %d important software updates available." +msgstr[0] "Es ist eine wichtige Softwareaktualisierung verfügbar." +msgstr[1] "Es sind %d wichtige Softwareaktualisierungen verfügbar." + +#: ../pkui-icon.c:166 #, c-format -msgid "There are %d software updates available." -msgstr "%d Software Updates sind verfügbar." +msgid "There is a software update available." +msgid_plural "There are %d software updates available." +msgstr[0] "Eine Softwareaktualisierung ist verfügbar." +msgstr[1] "Es sind %d Softwareaktualisierungen verfügbar." #: ../pk-update-icon.desktop.in.h:1 msgid "PackageKit Update Applet" -msgstr "PackageKit Update Applet" +msgstr "PackageKit Aktualisierungsapplet" #: ../pk-update-icon.desktop.in.h:2 msgid "Update Applet" -msgstr "Update Applet" +msgstr "Aktualisierungsapplet"