10
#include <glib/gstdio.h>
13
#include <sys/types.h>
18
#include "update-notifier.h"
20
#include "trayappletui.h"
22
#define UPGRADE_CHECKER PACKAGE_LIB_DIR"/update-notifier/apt-check"
24
// command, description, desktopfile, needs_gksu
25
const char* actions[][4] = {
26
{ "/usr/lib/update-notifier/backend_helper.py show_updates",
28
"/usr/share/applications/update-manager.desktop",
29
GINT_TO_POINTER(FALSE) },
30
{ "/usr/lib/update-notifier/backend_helper.py install_all_updates",
31
N_("Install all updates"), "/usr/share/applications/synaptic.desktop",
32
GINT_TO_POINTER(FALSE)
34
{ "/usr/lib/update-notifier/backend_helper.py check_updates",
35
N_("Check for updates"), "/usr/share/applications/synaptic.desktop",
36
GINT_TO_POINTER(FALSE) },
37
{ "/usr/lib/update-notifier/backend_helper.py start_packagemanager",
38
"/usr/share/applications/synaptic.desktop",
39
GINT_TO_POINTER(FALSE)},
46
NOTIFICATION_SHOW_UPDATES
50
g_debug_update(const char *msg, ...)
54
g_logv("update",G_LOG_LEVEL_DEBUG, msg, va);
59
gtk_status_icon_click_activate_cb (GtkWidget *widget,
62
UpdateTrayAppletPrivate *priv = (UpdateTrayAppletPrivate*)ta->user_data;
63
int index = priv->apt_is_running ? 3 : 0;
65
// on double click we get two activate signals, so slow down things
66
// here a bit to avoid double starting
67
static time_t last_action = 0;
68
if (time(NULL) - last_action > 1)
69
invoke (actions[index][0], actions[index][2], (long)actions[index][3]);
70
last_action = time(NULL);
75
update_trayicon_update_tooltip (TrayApplet *ta, int num_upgrades)
77
//g_print("update_tooltip: %p %p %p\n", ta, ta->tooltip, ta->eventbox);
80
updates = g_strdup_printf(ngettext("There is %i update available",
81
"There are %i updates available",
84
tray_applet_ui_set_tooltip_text(ta, updates);
90
cb_action(GObject *self, void *user_data)
92
TrayApplet *ta = user_data;
94
int i = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(self), "action"));
95
invoke (actions[i][0], _(actions[i][2]), (long)actions[i][3]);
97
UpdateTrayAppletPrivate *priv = (UpdateTrayAppletPrivate*)ta->user_data;
101
cb_preferences(GObject *self, void *user_data)
103
invoke("/usr/bin/software-properties-gtk",
104
"/usr/share/applications/software-properties.desktop",
109
cb_toggled_show_notifications(GObject *self, void *data)
111
TrayApplet *ta = (TrayApplet*)data;
112
UpdateTrayAppletPrivate *priv = (UpdateTrayAppletPrivate*)ta->user_data;
114
gboolean b = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(self));
115
g_settings_set_boolean(priv->settings,
116
SETTINGS_KEY_NO_UPDATE_NOTIFICATIONS, !b);
119
NotifyNotification *n = priv->active_notification;
121
notify_notification_close(n, NULL);
122
priv->active_notification = NULL;
128
update_trayicon_create_menu(TrayApplet *ta)
133
ta->menu = gtk_menu_new ();
135
for(i=0;actions[i][0]!=NULL;i++) {
136
if (!g_file_test(actions[i][2], G_FILE_TEST_EXISTS))
138
menuitem = gtk_menu_item_new_with_label (_(actions[i][1]));
140
gtk_menu_shell_append (GTK_MENU_SHELL (ta->menu), menuitem);
141
g_object_set_data(G_OBJECT(menuitem), "action", GINT_TO_POINTER(i));
142
g_signal_connect(G_OBJECT(menuitem), "activate",
143
G_CALLBACK(cb_action), ta);
146
menuitem = gtk_separator_menu_item_new();
147
gtk_menu_shell_append (GTK_MENU_SHELL (ta->menu), menuitem);
149
menuitem = gtk_check_menu_item_new_with_label(_("Show notifications"));
150
UpdateTrayAppletPrivate *priv = (UpdateTrayAppletPrivate*)ta->user_data;
151
gboolean b = g_settings_get_boolean(priv->settings,
152
SETTINGS_KEY_NO_UPDATE_NOTIFICATIONS);
153
gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), !b);
154
gtk_menu_shell_append (GTK_MENU_SHELL (ta->menu), menuitem);
155
g_signal_connect(G_OBJECT(menuitem), "toggled",
156
G_CALLBACK(cb_toggled_show_notifications), ta);
159
menuitem = gtk_image_menu_item_new_from_stock (GTK_STOCK_PREFERENCES, NULL);
160
if (g_file_test("/usr/bin/software-properties-gtk", G_FILE_TEST_EXISTS))
161
gtk_menu_shell_append (GTK_MENU_SHELL (ta->menu), menuitem);
162
g_signal_connect(G_OBJECT(menuitem), "activate",
163
G_CALLBACK(cb_preferences), (void*)ta);
165
gtk_widget_show_all (ta->menu);
168
/* this tells the trayicon that apt is downloading something */
170
update_apt_is_running(TrayApplet *ta, gboolean is_running)
172
g_debug_update("update_apt_is_running: %i\n",is_running);
176
// update internal status
177
UpdateTrayAppletPrivate *priv = (UpdateTrayAppletPrivate*)ta->user_data;
178
priv->apt_is_running = is_running;
180
// if the user wants auto-launch mode, do not show the icon
181
// the auto launch stuff will do its magic later
182
if(g_settings_get_boolean(priv->settings, SETTINGS_KEY_AUTO_LAUNCH))
186
#ifdef HAVE_APP_INDICATOR
187
// we can't do fancy stuff like the gray-out with app-indicator,
188
// so we just hide the tray thing here
189
tray_applet_ui_set_visible(ta, FALSE);
191
// gray out the icon if apt is running, we do this only once,
192
// after the first time, the storage_type changes from ICON_NAME to
194
if(gtk_status_icon_get_storage_type(ta->tray_icon) == GTK_IMAGE_ICON_NAME) {
195
GdkPixbuf *src = gtk_icon_theme_load_icon(gtk_icon_theme_get_default(),
196
gtk_status_icon_get_icon_name(ta->tray_icon),
197
gtk_status_icon_get_size(ta->tray_icon),
200
GdkPixbuf *inactive = gdk_pixbuf_copy(src);
201
gdk_pixbuf_saturate_and_pixelate(src, inactive, 0.0, FALSE);
202
gtk_status_icon_set_from_pixbuf(ta->tray_icon, inactive);
204
g_object_unref(inactive);
207
// and update the tooltip
208
tray_applet_ui_set_tooltip_text(ta->tray_icon, _("A package manager is working"));
210
tray_applet_ui_set_visible(ta->tray_icon, TRUE);
213
if(priv->active_notification != NULL) {
214
notify_notification_close(priv->active_notification, NULL);
215
priv->active_notification = NULL;
218
tray_applet_ui_set_icon(ta, ta->name);
222
// actually show the notification
224
show_notification(gpointer user_data)
226
TrayApplet *ta = (TrayApplet *)user_data;
227
UpdateTrayAppletPrivate *priv = (UpdateTrayAppletPrivate*)ta->user_data;
229
// apt is runing, no point in showing a notification
230
if(priv->apt_is_running)
233
// check if the update-icon is still visible (in the delay time a
234
// update may already have been performed)
235
if(!tray_applet_ui_get_visible(ta))
238
// now show a notification handle
240
updates = g_strdup_printf(ngettext("There is %i update available. "
241
"Click on the notification "
244
"There are %i updates available. "
245
"Click on the notification "
247
"available updates.",
250
NotifyNotification *n = notify_notification_new(
251
_("Software updates available"),
255
GdkPixbuf* pix= gtk_icon_theme_load_icon(gtk_icon_theme_get_default(),
256
GTK_STOCK_DIALOG_INFO, 48,0,NULL);
257
notify_notification_set_icon_from_pixbuf (n, pix);
259
notify_notification_set_timeout (n, 60*1000);
260
notify_notification_show(n, NULL);
261
// save the notification handle
262
priv->active_notification = n;
264
// remove this from the timeout now
270
show_error(TrayApplet *ta, gchar *error_str)
272
tray_applet_ui_set_tooltip_text(ta, error_str);
273
tray_applet_ui_set_icon(ta, "dialog-error");
274
tray_applet_ui_set_visible(ta, TRUE);
278
outdated_nag(TrayApplet *ta)
281
if ((stat("/var/lib/apt/periodic/update-success-stamp", &buf) == 0) &&
282
(time(NULL) - buf.st_mtime > OUTDATED_NAG_AGE) ) {
283
tray_applet_ui_set_visible (ta, TRUE);
284
ta->name = "gtk-dialog-warning-panel";
285
tray_applet_ui_set_icon(ta, ta->name);
286
tray_applet_ui_set_tooltip_text(ta,
287
_("The update information is outdated. "
288
"This may be caused by network "
289
"problems or by a repository that "
290
"is no longer available. "
291
"Please update manually "
292
"by clicking on this icon and then "
293
"selecting 'Check for updates' and "
294
"check if some of the listed "
301
// use ubuntu-system-service (if available) to check
302
// if the dpkg lock is taken currently or not
304
// if uncertain, return FALSE
306
dpkg_lock_is_taken ()
311
gboolean locked = FALSE;
314
proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
315
G_DBUS_PROXY_FLAGS_NONE,
316
NULL, /* GDBusInterfaceInfo */
317
"com.ubuntu.SystemService",
319
"com.ubuntu.SystemService",
320
NULL, /* GCancellable */
323
g_debug_update ("Failed to open connection to bus: %s\n", error->message);
324
g_error_free (error);
329
answer = g_dbus_proxy_call_sync (proxy, "is_package_system_locked", NULL,
330
G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
331
g_object_unref (proxy);
333
if (answer == NULL) {
334
g_debug_update ("error during dbus call: %s\n", error->message);
335
g_error_free (error);
339
if (g_strcmp0 (g_variant_get_type_string (answer), "(b)") != 0) {
340
g_debug_update ("SystemService answer in unexpected format: %s\n",
341
g_variant_get_type_string (answer));
342
g_variant_unref (answer);
346
g_variant_get (answer, "(b)", &locked);
347
g_variant_unref (answer);
349
g_debug_update ("is_package_system_locked: %i", locked);
354
// ask devicekit.Power if we run on battery
356
running_on_battery ()
358
DBusGConnection *connection;
361
gboolean on_battery = FALSE;
364
connection = dbus_g_bus_get (DBUS_BUS_SYSTEM, &error);
365
if (connection == NULL) {
366
g_debug_update ("Failed to open connection to bus: %s\n", error->message);
367
g_error_free (error);
371
proxy = dbus_g_proxy_new_for_name (connection,
372
"org.freedesktop.DeviceKit.Power",
373
"/org/freedesktop/DeviceKit/Power",
374
"org.freedesktop.DBus.Properties");
376
if (!dbus_g_proxy_call (proxy, "Get", &error,
377
G_TYPE_STRING, "org.freedesktop.DeviceKit.Power",
378
G_TYPE_STRING, "on_battery",
380
G_TYPE_BOOLEAN, &on_battery,
382
g_debug_update ("failed Get dbus call: %s\n",
383
error?error->message:"no error given");
385
g_error_free (error);
388
g_debug_update ("on_battery: %i", on_battery);
394
security_updates_are_installed_unattended()
397
GError *error = NULL;
399
// run apt_check.py in "check if we install updates unattended" mode
400
// a exit status > 0 indicates that its unattended-upgrades is used
401
if ( g_file_test ("/usr/bin/unattended-upgrades",
402
G_FILE_TEST_IS_EXECUTABLE) ) {
403
char *cmd[] = { "/usr/bin/nice",
404
"/usr/bin/ionice", "-c3",
406
"--security-updates-unattended",
409
if (g_spawn_sync("/", cmd, NULL, 0, NULL, NULL,
410
NULL, NULL, &ret, &error)) {
411
g_debug("--security-updates-unattended: %i\n", WEXITSTATUS(ret));
412
if( WEXITSTATUS(ret) > 0 )
415
g_print("error: %s\n", error->message);
421
// check if the security auto launch interval is over
422
// and if the user is not using auto install of security
425
auto_launch_security_now(UpdateTrayAppletPrivate *priv,
430
// no security updates
431
if (priv->num_security == 0)
434
// security updates, but already launched recently
435
if ((last_launch + AUTOLAUNCH_MINIMAL_SECURITY_INTERVAL) > now) {
436
g_debug_update("security updates, but update-manager was launched "
437
"within the AUTOLAUNCH_MINIMAL_SECURITY_INTERVAL\n");
441
// if security updates are installed unattended, there is nothing
443
if (security_updates_are_installed_unattended())
446
g_debug_update("security updates, auto-launching");
450
// check the logs of dpkg and apt for timestamps, the idea
451
// is to not auto launch if dpkg/apt were run manually by the
454
newest_log_file_timestamp()
458
time_t newest_log_stamp = 0;
460
char *log_glob[] = { "/var/log/dpkg.log*",
461
"/var/log/apt/term.log*",
464
for (i=0; log_glob[i] != NULL; i++)
467
if(glob(log_glob[i], 0, NULL, &pglob) != 0) {
468
g_warning("error from glob %s\n", log_glob[i]);
471
for(j=0;j < pglob.gl_pathc; j++) {
473
const char *log = pglob.gl_pathv[j];
474
if(g_stat(log, &buf) <0) {
475
g_warning("can't stat %s\n", log);
478
if(buf.st_size == 0) {
479
g_warning("log file empty (logrotate?) %s\n", log);
482
time_t mtime = buf.st_mtime;
483
g_debug_update ("mtime from %s: %i (%s)\n", log, mtime, ctime(&mtime));
484
time_t bctime = buf.st_ctime;
485
g_debug_update ("ctime from %s: %i (%s)\n", log, bctime, ctime(&bctime));
486
newest_log_stamp = MAX(MAX(mtime, bctime), newest_log_stamp);
487
g_debug_update ("last_launch from %s: %i (%s)\n", log, newest_log_stamp, ctime(&newest_log_stamp));
491
return newest_log_stamp;
494
// check if the auto launch interval is over and its
495
// time to launch again and if the dpkg lock is currently
498
auto_launch_now (UpdateTrayAppletPrivate *priv)
500
int interval_days = 0;
501
time_t last_launch = 0;
503
if (dpkg_lock_is_taken())
506
#if 0 // disabled because we need more cleverness here in order to cover
507
// the case where people always work on battery (and charge overnight)
508
if (running_on_battery())
512
// when checking for regular updates honor the
513
// regular-auto-launch-interval
514
interval_days = g_settings_get_int(priv->settings,
515
SETTINGS_KEY_AUTO_LAUNCH_INTERVAL);
516
g_debug_update ("interval_days: %i\n", interval_days);
518
if (interval_days <= 0)
521
// check last launch time
522
GSettings *um_settings = g_settings_new(SETTINGS_UM_SCHEMA);
523
last_launch = g_settings_get_int(um_settings,
524
SETTINGS_UM_KEY_LAST_LAUNCH);
525
g_debug_update ("last_launch: %i (%s)\n", last_launch, ctime(&last_launch));
526
g_object_unref(um_settings);
528
time_t now = time(NULL);
529
if (auto_launch_security_now(priv, now, last_launch))
532
time_t log_files = newest_log_file_timestamp();
533
last_launch = MAX(log_files, last_launch);
535
g_debug_update("time now %i (%s), delta: %i", now, ctime(&now), now-last_launch);
536
if ((last_launch + (24*60*60*interval_days)) < now) {
537
g_debug_update ("need to auto launch");
545
update_check (TrayApplet *ta)
550
g_debug_update ("update_check()\n");
551
UpdateTrayAppletPrivate *priv = (UpdateTrayAppletPrivate*)ta->user_data;
553
priv->num_upgrades = 0;
554
priv->num_security = 0;
556
GError *error = NULL;
558
char *cmd[] = { "/usr/bin/nice",
559
"/usr/bin/ionice", "-c3",
564
if(g_spawn_sync(NULL, cmd, NULL,
565
G_SPAWN_STDOUT_TO_DEV_NULL,
566
NULL, NULL, NULL, &ret,
567
&exit_status, &error)) {
569
// check if we are running from the live-cd, it
570
// symlinks apt-check to /bin/true
571
if(exit_status == 0 && strlen(ret) == 0 &&
572
g_file_test (UPGRADE_CHECKER, G_FILE_TEST_IS_SYMLINK) ) {
577
// read the value from childs stderr, E: indicates a problem
579
GString *error = g_string_new("");
581
g_string_append_printf(error,
582
_("An error occurred, please run "
583
"Package Manager from the "
584
"right-click menu or apt-get in "
585
"a terminal to see what is wrong.\n"
586
"The error message was: '%s'. "),
589
g_string_append(error, _("An error occurred, please run "
590
"Package Manager from the "
591
"right-click menu or apt-get in "
592
"a terminal to see what is wrong."));
593
g_string_append(error, _("This usually means that your installed "
594
"packages have unmet dependencies"));
595
show_error(ta, error->str);
596
g_string_free(error, TRUE);
602
// we get "number_of_upgrades;number_of_security_upgrades"
604
gchar **ret_array = g_strsplit(ret, ";", 2);
605
if(g_strv_length(ret_array) < 2) {
606
show_error(ta, _("A problem occurred when checking for the updates."));
608
g_strfreev(ret_array);
611
priv->num_upgrades = atoi(ret_array[0]);
612
priv->num_security = atoi(ret_array[1]);
614
g_strfreev(ret_array);
616
g_warning("Error launching %s", UPGRADE_CHECKER);
618
g_debug_update("%s returned %i (security: %i)", UPGRADE_CHECKER, priv->num_upgrades, priv->num_security);
620
if (priv->num_upgrades == 0) {
622
// check if the periodic file is very old
624
if ((stat("/var/lib/apt/periodic/update-success-stamp", &buf) == 0) &&
625
(time(NULL) - buf.st_mtime > OUTDATED_NAG_AGE) )
626
g_timeout_add_seconds(OUTDATED_NAG_WAIT, (GSourceFunc)outdated_nag, ta);
628
tray_applet_ui_set_visible (ta, FALSE);
629
if(priv->active_notification != NULL) {
630
notify_notification_close(priv->active_notification, NULL);
631
priv->active_notification = NULL;
636
if (priv->num_security == 0)
637
ta->name = "software-update-available";
639
ta->name = "software-update-urgent";
640
tray_applet_ui_set_icon(ta, ta->name);
642
// update the tooltip
643
update_trayicon_update_tooltip(ta, priv->num_upgrades);
645
// check if the user wants to see the icon or launch
646
// the default action
647
if(g_settings_get_boolean(priv->settings, SETTINGS_KEY_AUTO_LAUNCH))
649
tray_applet_ui_set_visible(ta, FALSE);
650
if (auto_launch_now(priv))
652
g_spawn_command_line_async("nice ionice -c3 update-manager "
653
"--no-focus-on-map", NULL);
658
// if we are already visible, skip the rest
659
if(tray_applet_ui_get_visible (ta))
663
tray_applet_ui_set_visible(ta, TRUE);
665
// the user does not no notification messages
666
if(g_settings_get_boolean(priv->settings,
667
SETTINGS_KEY_NO_UPDATE_NOTIFICATIONS))
670
// show the notification with some delay. otherwise on a login
671
// the origin of the window is 0,0 and that looks ugly
672
g_timeout_add_seconds(5, show_notification, ta);
679
update_tray_icon_init(TrayApplet *ta)
681
// create the private data struct
682
UpdateTrayAppletPrivate *priv = g_new0(UpdateTrayAppletPrivate, 1);
683
priv->settings = g_settings_new(SETTINGS_SCHEMA);
684
priv->apt_is_running = FALSE;
685
priv->active_notification = NULL;
686
ta->user_data = priv;
688
#ifndef HAVE_APP_INDICATOR
689
// the default action for the gtk_status_icon
690
g_signal_connect (G_OBJECT(ta->tray_icon),
692
G_CALLBACK (gtk_status_icon_click_activate_cb),
696
/* Menu initialization */
697
update_trayicon_create_menu (ta);
698
tray_applet_ui_set_menu (ta, ta->menu);
700
/* Check for updates for the first time */